/**
* This file is part of OSM2ShareNav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* Copyright (C) 2007 Harald Mueller
* Copyright (C) 2007, 2008 Kai Krueger
* Copyright (C) 2008 sk750
*
*/
package net.sharenav.osmToShareNav;
import static net.sharenav.osmToShareNav.GetText._;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;
import java.util.TreeSet;
import net.sharenav.osmToShareNav.area.Triangle;
import net.sharenav.osmToShareNav.model.Bounds;
import net.sharenav.osmToShareNav.model.ConditionTuple;
import net.sharenav.osmToShareNav.model.Connection;
import net.sharenav.osmToShareNav.model.EntityDescription;
import net.sharenav.osmToShareNav.model.Node;
import net.sharenav.osmToShareNav.model.POIdescription;
import net.sharenav.osmToShareNav.model.WayDescription;
import net.sharenav.osmToShareNav.model.Path;
import net.sharenav.osmToShareNav.model.RouteNode;
import net.sharenav.osmToShareNav.model.Sequence;
import net.sharenav.osmToShareNav.model.Tile;
import net.sharenav.osmToShareNav.model.TravelModes;
import net.sharenav.osmToShareNav.model.Way;
import net.sharenav.osmToShareNav.model.name.Names;
import net.sharenav.osmToShareNav.model.name.WayRedirect;
import net.sharenav.osmToShareNav.model.url.Urls;
import net.sharenav.osmToShareNav.tools.FileTools;
public class CreateShareNavData implements FilenameFilter {
/**
* This class is used in order to store a tuple on a dedicated stack.
* So that it is not necessary to use the OS stack in recursion.
*/
class TileTuple {
public Tile t;
public Bounds bound;
TileTuple(Tile t, Bounds b) {
this.t = t;
this.bound = b;
}
}
public final static byte LEGEND_FLAG_IMAGE = 0x01;
public final static byte LEGEND_FLAG_SEARCH_IMAGE = 0x02;
public final static byte LEGEND_FLAG_MIN_IMAGE_SCALE = 0x04;
public final static byte LEGEND_FLAG_MIN_ONEWAY_ARROW_SCALE = LEGEND_FLAG_MIN_IMAGE_SCALE;
public final static byte LEGEND_FLAG_TEXT_COLOR = 0x08;
public final static byte LEGEND_FLAG_NON_HIDEABLE = 0x10;
// public final static byte LEGEND_FLAG_NON_ROUTABLE = 0x20; routable flag has been moved to Way
public final static byte LEGEND_FLAG_ALERT = 0x20;
public final static byte LEGEND_FLAG_MIN_DESCRIPTION_SCALE = 0x40;
public final static int LEGEND_FLAG_ADDITIONALFLAG = 0x80;
public final static int LEGEND_MAPFLAG_OUTLINE_AREA_BLOCK = 0x01;
public final static int LEGEND_MAPFLAG_TRIANGLE_AREA_BLOCK = 0x02;
public final static int LEGEND_MAPFLAG_WORDSEARCH = 0x04;
public final static int LEGEND_MAPFLAG_SOURCE_OSM_CC_BY_SA = 0x08;
public final static int LEGEND_MAPFLAG_SOURCE_OSM_ODBL = 0x10;
public final static int LEGEND_MAPFLAG_SOURCE_FI_LANDSURVEY = 0x20;
public final static int LEGEND_MAPFLAG_SOURCE_FI_DIGIROAD = 0x40;
public final static byte LEGEND_FLAG2_CLICKABLE = 0x01;
public final static byte ROUTE_FLAG_MOTORWAY = 0x01;
public final static byte ROUTE_FLAG_MOTORWAY_LINK = 0x02;
public final static byte ROUTE_FLAG_ROUNDABOUT = 0x04;
// public final static int MAX_DICT_DEEP = 5; replaced by Configuration.maxDictDepth
public final static int ROUTEZOOMLEVEL = 4;
/** The parser which parses the OSM data. The nodes, ways and relations are
* retrieved from it for further processing. */
OsmParser parser;
/** This array contains one tile for each zoom level or level of detail and
* the route tile. Each one is actually a tree of tiles because container tiles
* contain two child tiles. */
Tile tile[] = new Tile[ROUTEZOOMLEVEL + 1];
/** Output length of the route connection for statistics */
long outputLengthConns = 0;
private final String path;
Names names1;
Urls urls1;
StringBuffer sbCopiedMedias = new StringBuffer();
short mediaInclusionErrors = 0;
private final static int INODE = 1;
private final static int SEGNODE = 2;
// private Bounds[] bounds = null;
private Configuration configuration;
private int totalWaysWritten = 0;
private int totalSegsWritten = 0;
private int totalNodesWritten = 0;
private int totalPOIsWritten = 0;
private static int dictFilesWritten = 0;
private static int tileFilesWritten = 0;
private RouteData rd;
private static double MAX_RAD_RANGE = (Short.MAX_VALUE - Short.MIN_VALUE - 2000) / MyMath.FIXPT_MULT;
private String[] useLang = null;
WayRedirect wayRedirect = null;
public CreateShareNavData(OsmParser parser, String path) {
super();
this.parser = parser;
if (Configuration.getConfiguration().sourceIsApk && !Configuration.getConfiguration().mapzip) {
path = path + "/assets";
}
this.path = path;
File dir = new File(path);
wayRedirect = new WayRedirect();
// first of all, delete all data-files from a previous run or files that comes
// from the mid jar file
if (dir.isDirectory()) {
File[] files = dir.listFiles();
for (File f : files) {
if (f.getName().endsWith(".d") || f.getName().endsWith(".dat")) {
if (! f.delete()) {
System.out.println("ERROR: Failed to delete file " + f.getName());
}
}
}
}
}
/** Prepares and writes the complete map data.
*/
public void exportMapToMid() {
names1 = getNames1();
urls1 = getUrls1();
exportLegend(path);
SearchList sl = new SearchList(names1, urls1, wayRedirect);
UrlList ul = new UrlList(urls1);
sl.createNameList(path);
ul.createUrlList(path);
for (int i = 0; i <= 3; i++) {
if (configuration.verbose >= 0) {
System.out.println("Exporting tiles for zoomlevel " + i );
System.out.println("===============================");
if (!LegendParser.tileScaleLevelContainsRoutableWays[i]) {
System.out.println("Info: This tile level contains no routable ways");
}
}
long startTime = System.currentTimeMillis();
long bytesWritten = exportMapToMid(i);
long time = (System.currentTimeMillis() - startTime);
if (configuration.verbose >= 0) {
System.out.println(" Zoomlevel " + i + ": " +
Configuration.memoryWithUnit(bytesWritten) + " in " +
tileFilesWritten + " files indexed by " + dictFilesWritten +
" dictionary files");
System.out.println(" Time taken: " + time / 1000 + " seconds");
}
}
if (configuration.verbose >= 0) {
if (Configuration.attrToBoolean(configuration.useRouting) >= 0) {
System.out.println("Exporting route tiles");
System.out.println("=====================");
long startTime = System.currentTimeMillis();
long bytesWritten = exportMapToMid(ROUTEZOOMLEVEL);
long time = (System.currentTimeMillis() - startTime);
System.out.println(" " + Configuration.memoryWithUnit(bytesWritten) +
" for nodes in " + tileFilesWritten + " files, " +
Configuration.memoryWithUnit(outputLengthConns) + " for connections in " +
tileFilesWritten + " files");
System.out.println(" The route tiles have been indexed by " +
dictFilesWritten + " dictionary files");
System.out.println(" Time taken: " + time / 1000 + " seconds");
} else {
System.out.println("No route tiles to export");
}
}
// for (int x = 1; x < 12; x++) {
// System.out.print("\n" + x + " :");
// tile[ROUTEZOOMLEVEL].printHiLo(1, x);
// }
// System.exit(2);
// create search list for whole items
//sl.createSearchList(path, SearchList.INDEX_NAME);
// create search list for names, including data for housenumber matching, primary since map version 66
sl.createSearchList(path, SearchList.INDEX_BIGNAME);
// create search list for words
if (Configuration.getConfiguration().useWordSearch) {
sl.createSearchList(path, SearchList.INDEX_WORD);
// create search list for whole words / house numbers
sl.createSearchList(path, SearchList.INDEX_WHOLEWORD);
}
if (Configuration.getConfiguration().useHouseNumbers) {
sl.createSearchList(path, SearchList.INDEX_HOUSENUMBER);
}
// Output statistics for travel modes
if (configuration.verbose >= 0) {
if (Configuration.attrToBoolean(configuration.useRouting) >= 0) {
for (int i = 0; i < TravelModes.travelModeCount; i++) {
System.out.println(TravelModes.getTravelMode(i).toString());
}
}
System.out.println(" MainStreet_Net Connections: " +
TravelModes.numMotorwayConnections + " motorway " +
TravelModes.numTrunkOrPrimaryConnections + " trunk/primary " +
TravelModes.numMainStreetNetConnections + " total");
System.out.print(" Connections with toll flag:");
for (int i = 0; i < TravelModes.travelModeCount; i++) {
System.out.print(" " + TravelModes.getTravelMode(i).getName() + "(" + TravelModes.getTravelMode(i).numTollRoadConnections + ")" );
}
System.out.println("");
System.out.println("Total ways: "+ totalWaysWritten
+ ", segments: " + totalSegsWritten
+ ", nodes: " + totalNodesWritten
+ ", POI: " + totalPOIsWritten);
}
}
private Names getNames1() {
Names na = new Names();
for (Way w : parser.getWays()) {
na.addName(w, wayRedirect);
}
for (Node n : parser.getNodes()) {
na.addName(n, wayRedirect);
}
if (configuration.verbose >= 0) {
System.out.println("Found " + na.getNames().size() + " names, " +
na.getCanons().size() + " canon");
}
na.calcNameIndex();
return (na);
}
private Urls getUrls1() {
Urls na = new Urls();
for (Way w : parser.getWays()) {
na.addUrl(w);
na.addPhone(w);
}
for (Node n : parser.getNodes()) {
na.addUrl(n);
na.addPhone(n);
}
if (configuration.verbose >= 0) {
System.out.println("found " + na.getUrls().size() + " urls, including phones ");
}
na.calcUrlIndex();
return (na);
}
private void exportLegend(String path) {
FileOutputStream foi;
String outputMedia;
Configuration.mapFlags = 0L;
// FIXME add GUI for telling map data source(s)
if (Configuration.getConfiguration().sourceOSMODbL) {
Configuration.mapFlags |= LEGEND_MAPFLAG_SOURCE_OSM_ODBL;
}
if (Configuration.getConfiguration().sourceOSMCC) {
Configuration.mapFlags |= LEGEND_MAPFLAG_SOURCE_OSM_CC_BY_SA;
}
if (Configuration.getConfiguration().sourceFILandSurvey) {
Configuration.mapFlags |= LEGEND_MAPFLAG_SOURCE_FI_LANDSURVEY;
}
if (Configuration.getConfiguration().sourceFIDigiroad) {
Configuration.mapFlags |= LEGEND_MAPFLAG_SOURCE_FI_DIGIROAD;
}
if (Configuration.getConfiguration().writeTriangleAreaFormat) {
Configuration.mapFlags |= LEGEND_MAPFLAG_TRIANGLE_AREA_BLOCK;
}
if (Configuration.getConfiguration().getOutlineAreaFormat()) {
Configuration.mapFlags |= LEGEND_MAPFLAG_OUTLINE_AREA_BLOCK;
}
if (Configuration.getConfiguration().useWordSearch) {
Configuration.mapFlags |= LEGEND_MAPFLAG_WORDSEARCH;
}
try {
FileTools.createPath(new File(path + "/dat"));
foi = new FileOutputStream(path + "/legend.dat");
DataOutputStream dsi = new DataOutputStream(foi);
dsi.writeShort(Configuration.MAP_FORMAT_VERSION);
Configuration config = Configuration.getConfiguration();
/**
* Write application version
*/
dsi.writeUTF(config.getVersion());
/**
* Write bundle date
*/
dsi.writeUTF(config.getBundleDate());
/**
* Note if additional information is included that can enable editing of OSM data
*/
dsi.writeBoolean(config.enableEditingSupport);
/* Note what languages are enabled
*/
useLang = configuration.getUseLang().split("[;,]", 200);
String useLangName[] = configuration.getUseLangName().split("[;,]", 200);
if (useLangName.length != useLang.length) {
System.out.println("");
System.out.println(" Warning: useLang count " + useLang.length +
" different than useLangName count " + useLangName.length +
" - ignoring useLangNames");
System.out.println("");
useLangName = useLang;
}
// make all available languages the same for now
for (int i = 1; i <= 5 ; i++) {
dsi.writeShort(useLang.length);
for (int j = 0 ; j < useLang.length ; j++) {
dsi.writeUTF(useLang[j]);
dsi.writeUTF(useLangName[j]);
}
}
// remove unneeded .loc files
if (!Configuration.getConfiguration().allLang) {
String langs = configuration.getUseLang() + ",en";
removeFilesWithExt(path, "loc", langs.split("[;,]", 200));
}
// remove class files (midlet/android code) if building just the map
if (!configuration.getMapName().equals("") || configuration.mapzip) {
removeFilesWithExt(path, "class", null);
removeFilesWithExt(path, "dex", null);
removeFilesWithExt(path, "xml", null);
}
/**
* Flags if urls and phones are in the map
*/
dsi.writeBoolean(config.useUrlTags);
dsi.writeBoolean(config.usePhoneTags);
/**
* Writing colors
*/
dsi.writeShort((short) Configuration.COLOR_COUNT);
for (int i = 0; i < Configuration.COLOR_COUNT; i++) {
if (Configuration.COLORS_AT_NIGHT[i] != -1) {
dsi.writeInt(0x01000000 | Configuration.COLORS[i]);
dsi.writeInt(Configuration.COLORS_AT_NIGHT[i]);
} else {
dsi.writeInt(Configuration.COLORS[i]);
}
}
/**
* Write Tile Scale Levels
*/
for (int i = 0; i < 4; i++) {
if (LegendParser.tileScaleLevelContainsRoutableWays[i]) {
dsi.writeInt(LegendParser.tileScaleLevel[i]);
} else {
dsi.writeInt( -LegendParser.tileScaleLevel[i] );
}
}
/**
* Write Travel Modes
*/
dsi.writeByte(TravelModes.travelModeCount);
for (int i = 0; i < TravelModes.travelModeCount; i++) {
dsi.writeUTF(_(TravelModes.getTravelMode(i).getName()));
dsi.writeShort(TravelModes.getTravelMode(i).maxPrepareMeters);
dsi.writeShort(TravelModes.getTravelMode(i).maxInMeters);
dsi.writeShort(TravelModes.getTravelMode(i).maxEstimationSpeed);
dsi.writeByte(TravelModes.getTravelMode(i).travelModeFlags);
}
/**
* Writing POI legend data
*/
/**
* // polish.api.bigstyles * Are there more way or poi styles than 126
*/
//System.err.println("Big styles:" + config.bigStyles);
// polish.api.bigstyles
// backwards compatibility - use "0" as a marker that we use a short for # of styles
if (config.bigStyles) {
dsi.writeByte((byte) 0);
dsi.writeShort(config.getPOIDescs().size());
} else {
dsi.writeByte(config.getPOIDescs().size());
}
for (EntityDescription entity : config.getPOIDescs()) {
POIdescription poi = (POIdescription) entity;
byte flags = 0;
byte flags2 = 0;
if (poi.image != null && !poi.image.equals("")) {
flags |= LEGEND_FLAG_IMAGE;
}
if (poi.searchIcon != null) {
flags |= LEGEND_FLAG_SEARCH_IMAGE;
}
if (poi.minEntityScale != poi.minTextScale) {
flags |= LEGEND_FLAG_MIN_IMAGE_SCALE;
}
if (poi.textColor != 0) {
flags |= LEGEND_FLAG_TEXT_COLOR;
}
if (!poi.hideable) {
flags |= LEGEND_FLAG_NON_HIDEABLE;
}
if (poi.alert) {
flags |= LEGEND_FLAG_ALERT;
}
if (poi.clickable) {
flags2 |= LEGEND_FLAG2_CLICKABLE;
}
// polish.api.bigstyles
if (config.bigStyles) {
dsi.writeShort(poi.typeNum);
//System.out.println("poi typenum: " + poi.typeNum);
} else {
dsi.writeByte(poi.typeNum);
}
if (flags2 != 0) {
flags |= LEGEND_FLAG_ADDITIONALFLAG;
}
dsi.writeByte(flags);
if (flags2 != 0) {
dsi.writeByte(flags2);
}
dsi.writeUTF(_(poi.description));
dsi.writeBoolean(poi.imageCenteredOnNode);
dsi.writeInt(poi.minEntityScale);
if ((flags & LEGEND_FLAG_IMAGE) > 0) {
outputMedia = copyMediaToMid(poi.image, path, "png");
dsi.writeUTF(outputMedia);
}
if ((flags & LEGEND_FLAG_SEARCH_IMAGE) > 0) {
outputMedia = copyMediaToMid(poi.searchIcon, path, "png");
dsi.writeUTF(outputMedia);
}
if ((flags & LEGEND_FLAG_MIN_IMAGE_SCALE) > 0) {
dsi.writeInt(poi.minTextScale);
}
if ((flags & LEGEND_FLAG_TEXT_COLOR) > 0) {
dsi.writeInt(poi.textColor);
}
if (config.enableEditingSupport) {
int noKVpairs = 1;
if (poi.specialisation != null) {
for (ConditionTuple ct : poi.specialisation) {
if (!ct.exclude) {
noKVpairs++;
}
}
}
dsi.writeShort(noKVpairs);
dsi.writeUTF(poi.key);
dsi.writeUTF(poi.value);
if (poi.specialisation != null) {
for (ConditionTuple ct : poi.specialisation) {
if (!ct.exclude) {
dsi.writeUTF(ct.key);
dsi.writeUTF(ct.value);
}
}
}
}
// System.out.println(poi);
}
/**
* Writing Way legend data
*/
// polish.api.bigstyles
if (config.bigStyles) {
System.out.println("waydesc size: " + Configuration.getConfiguration().getWayDescs().size());
// backwards compatibility - use "0" as a marker that we use a short for # of styles
dsi.writeByte((byte) 0);
dsi.writeShort(Configuration.getConfiguration().getWayDescs().size());
} else {
dsi.writeByte(Configuration.getConfiguration().getWayDescs().size());
}
for (EntityDescription entity : Configuration.getConfiguration().getWayDescs()) {
WayDescription way = (WayDescription) entity;
byte flags = 0;
byte flags2 = 0;
if (!way.hideable) {
flags |= LEGEND_FLAG_NON_HIDEABLE;
}
if (way.alert) {
flags |= LEGEND_FLAG_ALERT;
}
if (way.clickable) {
flags2 |= LEGEND_FLAG2_CLICKABLE;
}
if (way.image != null && !way.image.equals("")) {
flags |= LEGEND_FLAG_IMAGE;
}
if (way.searchIcon != null) {
flags |= LEGEND_FLAG_SEARCH_IMAGE;
}
if (way.minOnewayArrowScale != 0) {
flags |= LEGEND_FLAG_MIN_ONEWAY_ARROW_SCALE;
}
if (way.minDescriptionScale != 0) {
flags |= LEGEND_FLAG_MIN_DESCRIPTION_SCALE;
}
// polish.api.bigstyles
if (config.bigStyles) {
dsi.writeShort(way.typeNum);
} else {
dsi.writeByte(way.typeNum);
}
if (flags2 != 0) {
flags |= LEGEND_FLAG_ADDITIONALFLAG;
}
dsi.writeByte(flags);
if (flags2 != 0) {
dsi.writeByte(flags2);
}
byte routeFlags = 0;
if (way.value.equalsIgnoreCase("motorway")) {
routeFlags |= ROUTE_FLAG_MOTORWAY;
}
if (way.value.equalsIgnoreCase("motorway_link")) {
routeFlags |= ROUTE_FLAG_MOTORWAY_LINK;
}
dsi.writeByte(routeFlags);
dsi.writeUTF(_(way.description));
dsi.writeInt(way.minEntityScale);
dsi.writeInt(way.minTextScale);
if ((flags & LEGEND_FLAG_IMAGE) > 0) {
outputMedia = copyMediaToMid(way.image, path, "png");
dsi.writeUTF(outputMedia);
}
if ((flags & LEGEND_FLAG_SEARCH_IMAGE) > 0) {
outputMedia = copyMediaToMid(way.searchIcon, path, "png");
dsi.writeUTF(outputMedia);
}
dsi.writeBoolean(way.isArea);
if (way.lineColorAtNight != -1) {
dsi.writeInt(0x01000000 | way.lineColor);
dsi.writeInt(way.lineColorAtNight);
} else {
dsi.writeInt(way.lineColor);
}
if (way.boardedColorAtNight != -1) {
dsi.writeInt(0x01000000 | way.boardedColor);
dsi.writeInt(way.boardedColorAtNight);
} else {
dsi.writeInt(way.boardedColor);
}
dsi.writeByte(way.wayWidth);
dsi.writeInt(way.wayDescFlags);
if ((flags & LEGEND_FLAG_MIN_ONEWAY_ARROW_SCALE) > 0) {
dsi.writeInt(way.minOnewayArrowScale);
}
if ((flags & LEGEND_FLAG_MIN_DESCRIPTION_SCALE) > 0) {
dsi.writeInt(way.minDescriptionScale);
}
if (config.enableEditingSupport) {
int noKVpairs = 1;
if (way.specialisation != null) {
for (ConditionTuple ct : way.specialisation) {
if (!ct.exclude) {
noKVpairs++;
}
}
}
dsi.writeShort(noKVpairs);
dsi.writeUTF(way.key);
dsi.writeUTF(way.value);
if (way.specialisation != null) {
for (ConditionTuple ct : way.specialisation) {
if (!ct.exclude) {
dsi.writeUTF(ct.key);
dsi.writeUTF(ct.value);
}
}
}
}
// System.out.println(way);
}
if (Configuration.attrToBoolean(configuration.useIcons) < 0) {
if (configuration.verbose >= 0) {
System.out.println("Icons disabled - removing icon files from bundle.");
}
removeUnusedIconSizes(path, true);
} else {
// show summary for copied icon files
if (configuration.verbose >= 0) {
System.out.println("Icon inclusion summary:");
System.out.println(" " + FileTools.copyDir("icon", path, true, true) +
" internal icons replaced from " + "icon" +
System.getProperty("file.separator") + " containing " +
FileTools.countFiles("icon") + " files");
}
// if useIcons == small or useIcons == big rename the corresponding icons to normal icons
if ((!Configuration.getConfiguration().sourceIsApk) && Configuration.attrToBoolean(configuration.useIcons) == 0) {
renameAlternativeIconSizeToUsedIconSize(configuration.useIcons + "_");
}
if (!Configuration.getConfiguration().sourceIsApk) {
removeUnusedIconSizes(path, false);
}
}
/**
* Copy sounds for all sound formats to bundle
*/
String soundFormat[] = configuration.getUseSounds().split("[;,]", 10);
// write sound format infos
dsi.write((byte) soundFormat.length);
for (int i = 0; i < soundFormat.length; i++) {
dsi.writeUTF(soundFormat[i].trim());
}
/**
* write all sound files in each sound directory for all sound formats
*/
String soundFileDirectoriesHelp[] = configuration.getSoundFiles().split("[;,]", 10);
String soundDirsFound = "";
for (int i = 0; i < soundFileDirectoriesHelp.length; i++) {
// test existence of dir
InputStream is = null;
try {
is = new FileInputStream(configuration.getStyleFileDirectory() + soundFileDirectoriesHelp[i].trim() + "/syntax.cfg");
} catch (Exception e) {
// try internal syntax.cfg
try {
is = getClass().getResourceAsStream("/media/" + soundFileDirectoriesHelp[i].trim() + "/syntax.cfg");
} catch (Exception e2) {
;
}
}
if (is != null) {
if (soundDirsFound.equals("")) {
soundDirsFound = soundFileDirectoriesHelp[i];
} else {
soundDirsFound = soundDirsFound + ";" + soundFileDirectoriesHelp[i];
}
} else {
System.out.println ("ERROR: syntax.cfg not found in the " + soundFileDirectoriesHelp[i].trim() + " directory");
}
}
String soundFileDirectories[] = soundDirsFound.split("[;,]", 10);
dsi.write((byte) soundFileDirectories.length);
for (int i = 0; i < soundFileDirectories.length; i++) {
String destSoundPath = path + "/" + soundFileDirectories[i].trim();
// System.out.println("create sound directory: " + destSoundPath);
FileTools.createPath(new File(destSoundPath));
dsi.writeUTF(soundFileDirectories[i].trim());
// create soundSyntax for current sound directory
RouteSoundSyntax soundSyn = new RouteSoundSyntax(configuration.getStyleFileDirectory(),
soundFileDirectories[i].trim(), destSoundPath + "/syntax.dat");
String soundFile;
Object soundNames[] = soundSyn.getSoundNames();
for (int j = 0; j < soundNames.length ; j++) {
soundFile = (String) soundNames[j];
soundFile = soundFile.toLowerCase();
for (int k = 0; k < soundFormat.length; k++) {
outputMedia = copyMediaToMid(soundFile + "." +
soundFormat[k].trim(), destSoundPath, soundFileDirectories[i].trim());
}
}
removeUnusedSoundFormats(destSoundPath);
}
// show summary for copied media files
try {
if (configuration.verbose >= 0 || mediaInclusionErrors != 0) {
if (sbCopiedMedias.length() != 0) {
System.out.println("External media inclusion summary:");
sbCopiedMedias.append("\r\n");
} else {
System.out.println("No external media included.");
}
sbCopiedMedias.append(" Media Sources for external medias\r\n");
sbCopiedMedias.append(" referenced in " + configuration.getStyleFileName() + " have been:\r\n");
sbCopiedMedias.append(" " +
(configuration.getStyleFileDirectory().length() == 0 ?
"Current directory" : configuration.getStyleFileDirectory()) +
" and its png and " + configuration.getSoundFiles() + " subdirectories");
System.out.println(sbCopiedMedias.toString());
}
if (mediaInclusionErrors != 0) {
System.out.println("");
System.out.println(" WARNING: " + mediaInclusionErrors +
" media files could NOT be included - see details above");
System.out.println("");
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
dsi.writeFloat((float)Configuration.mapPrecisionInMeters);
dsi.writeLong(Configuration.mapFlags);
dsi.close();
foi.close();
if (Configuration.attrToBoolean(configuration.useRouting) < 0) {
System.out.println("Routing disabled - removing routing sound files from package:");
for (int i = 0; i < Configuration.SOUNDNAMES.length; i++) {
if (";CONNECT;DISCONNECT;DEST_REACHED;SPEED_LIMIT;".indexOf(";" + Configuration.SOUNDNAMES[i] + ";") == -1) {
removeSoundFile(Configuration.SOUNDNAMES[i]);
}
}
}
} catch (FileNotFoundException fnfe) {
System.err.println("Unhandled FileNotFoundException: " + fnfe.getMessage());
fnfe.printStackTrace();
} catch (IOException ioe) {
System.err.println("Unhandled IOException: " + ioe.getMessage());
ioe.printStackTrace();
}
}
public void renameAlternativeIconSizeToUsedIconSize(String prefixToRename) {
File dir = new File(path);
File[] iconsToRename = dir.listFiles(this);
if (iconsToRename != null) {
for (File file : iconsToRename) {
if (file.getName().startsWith(prefixToRename)) {
File fileRenamed = new File(path + "/" + file.getName().substring(prefixToRename.length()));
if (fileRenamed.exists()) {
fileRenamed.delete();
}
file.renameTo(fileRenamed);
// System.out.println("Rename " + file.getName() + " to " + fileRenamed.getName());
}
}
}
}
public void removeUnusedIconSizes(String path, boolean deleteAllIcons) {
File dir = new File(path);
File[] iconsToDelete = dir.listFiles(this);
if (iconsToDelete != null) {
for (File file : iconsToDelete) {
if (file.getName().matches("(small|big|large|huge)_(is?|r)_.*\\.png")
|| (deleteAllIcons && file.getName().matches("(is?|r)_.*\\.png") && !file.getName().equalsIgnoreCase("i_bg.png"))
) {
//System.out.println("Delete " + file.getName());
file.delete();
}
}
}
}
public boolean accept(File directory, String filename) {
return filename.endsWith(".png") || filename.endsWith(".amr") || filename.endsWith(".mp3") || filename.endsWith(".wav");
}
public void removeUnusedSoundFormats(String path) {
File dir = new File(path);
File[] sounds = dir.listFiles(this);
String soundFormat[] = configuration.getUseSounds().split("[;,]", 10);
String soundMatch = ";";
for (int i = 0; i < soundFormat.length; i++) {
soundMatch += soundFormat[i].trim() + ";";
}
int deletedFiles = 0;
if (sounds != null) {
for (File file : sounds) {
if (
file.getName().matches(".*\\.amr") && soundMatch.indexOf(";amr;") == -1
||
file.getName().matches(".*\\.mp3") && soundMatch.indexOf(";mp3;") == -1
||
file.getName().matches(".*\\.wav") && soundMatch.indexOf(";wav;") == -1
) {
//System.out.println("Delete " + file.getName());
file.delete();
deletedFiles++;
}
}
}
if (deletedFiles > 0) {
System.out.println(deletedFiles + " files of unused sound formats removed");
}
}
// remove files with a certain extension from dir, except strings with basename list in exceptions
public void removeFilesWithExt(String path, String ext, String exceptions[]) {
//System.out.println ("Removing files from " + path + " with ext " + ext + " exceptions: " + exceptions);
File dir = new File(path);
String[] files = dir.list();
int deletedFiles = 0;
int retainedFiles = 0;
File file = null;
if (files != null) {
for (String name : files) {
boolean remove = false;
file = new File(name);
if (name.matches(".*\\." + ext)) {
remove = true;
if (exceptions != null) {
for (String basename : exceptions) {
//System.out.println ("Testing filename " + file.getName() + " for exception " + basename);
if (file.getName().startsWith(basename)) {
remove = false;
//System.out.println ("Test for string " + file.getName() + " exception " + basename + " matched");
retainedFiles++;
}
}
}
if (remove) {
file = new File(path, name);
file.delete();
deletedFiles++;
}
} else {
//System.out.println ("checking if it's a dir: " + name);
file = new File(path, name);
try {
if (file.isDirectory()) {
//System.out.println ("checking subdir: " + file + " (" + file.getCanonicalPath() + ")");
removeFilesWithExt(file.getCanonicalPath(), ext, exceptions);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
if (retainedFiles > 0) {
System.out.println("retained " + retainedFiles + " files with extension " + ext);
}
if (deletedFiles > 0) {
System.out.println("deleted " + deletedFiles + " files with extension " + ext);
}
}
private void removeSoundFile(String soundName) {
final String soundFormat[] = { "amr", "wav", "mp3" };
String soundFile;
for (int i = 0; i < soundFormat.length; i++) {
soundFile = soundName.toLowerCase() + "." + soundFormat[i];
File target = new File(path + "/" + soundFile);
if (target.exists()) {
target.delete();
System.out.println(" - removed " + soundFile);
}
}
}
/* Copies the given file in mediaPath to destDir.
* - If you specify a filename only it will look for the file in this order:
* 1. current directory 2. additional source subdirectory 3.internal file
* - For file names only preceded by a single "/", Osm2ShareNav will always assume
* you want to explicitly use the internal media file.
* - Directory path information as part of source media path is allowed,
* however the media file will ALWAYS be copied to destDir root.
* - Remembers copied files in sbCopiedMedias (adds i.e. "(REPLACED)" for replaced files)
*/
private String copyMediaToMid(String mediaPath, String destDir, String additionalSrcPath) {
// output filename is just the name part of the imagePath filename preceded by "/"
int iPos = mediaPath.lastIndexOf("/");
String realMediaPath = configuration.getStyleFileDirectory() + mediaPath;
String outputMediaName;
// System.out.println("Processing: " + configuration.getStyleFileDirectory() +
// additionalSrcPath + "/" + mediaPath);
// if no "/" is contained look for file in current directory and /png
if (iPos == -1) {
outputMediaName = "/" + mediaPath;
// check if file exists in current directory of Osm2ShareNav / the style file
if (! (new File(realMediaPath).exists())) {
// check if file exists in current directory of Osm2ShareNav / the style file + "/png" or "/sound"
realMediaPath = configuration.getStyleFileDirectory() + additionalSrcPath + "/" + mediaPath;
// System.out.println("checking for realMediaPath: " + realMediaPath);
if (! (new File(realMediaPath).exists())) {
// System.out.println("realMediaPath not found: " + realMediaPath);
// if not check if we can use the version included in Osm2ShareNav.jar
if (CreateShareNavData.class.getResource("/media/" + additionalSrcPath + "/" + mediaPath) == null) {
// if not check if we can use the internal image file
if (!(new File(path + outputMediaName).exists())) {
// append media name if first media or " ," + media name for the following ones
sbCopiedMedias.append( (sbCopiedMedias.length() == 0) ? mediaPath : ", " + mediaPath);
sbCopiedMedias.append("(ERROR: file not found)");
mediaInclusionErrors++;
}
return outputMediaName;
} else {
/**
* Copy the file from Osm2ShareNav.jar to the destination directory
*/
try {
BufferedInputStream bis = new BufferedInputStream(
CreateShareNavData.class.getResourceAsStream("/media/" +
additionalSrcPath + "/" + mediaPath)
);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(destDir + outputMediaName));
byte[] buf = new byte[4096];
while (bis.available() > 0) {
int len = bis.read(buf);
bos.write(buf, 0, len);
}
bos.flush();
bos.close();
bis.close();
} catch (IOException ioe) {
ioe.printStackTrace();
sbCopiedMedias.append((sbCopiedMedias.length() == 0) ? mediaPath
: ", " + mediaPath);
sbCopiedMedias.append("(ERROR: file not found)");
mediaInclusionErrors++;
}
return outputMediaName;
}
}
}
// if the first and only "/" is at the beginning its the explicit syntax for internal images
} else if (iPos == 0) {
if (!(new File(path + mediaPath).exists())) {
// append media name if first media or " ," + media name for the following ones
sbCopiedMedias.append( (sbCopiedMedias.length() == 0) ? mediaPath : ", " + mediaPath);
sbCopiedMedias.append("(ERROR: INTERNAL media file not found)");
mediaInclusionErrors++;
}
return mediaPath;
// else it's an external file with explicit path
} else {
outputMediaName = mediaPath.substring(iPos);
}
// append media name if first media or " ," + media name for the following ones
sbCopiedMedias.append( (sbCopiedMedias.length() == 0) ? mediaPath : ", " + mediaPath);
try {
// System.out.println("Copying " + mediaPath + " as " + outputMediaName + " into the bundle");
FileChannel fromChannel = new FileInputStream(realMediaPath).getChannel();
// Copy Media file
try {
// check if output file already exists
boolean alreadyExists = (new File(destDir + outputMediaName).exists());
FileChannel toChannel = new FileOutputStream(destDir + outputMediaName).getChannel();
fromChannel.transferTo(0, fromChannel.size(), toChannel);
toChannel.close();
if(alreadyExists) {
sbCopiedMedias.append("(REPLACED " + outputMediaName + ")");
}
}
catch (Exception e) {
sbCopiedMedias.append("(ERROR accessing destination file " + destDir + outputMediaName + ")");
mediaInclusionErrors++;
e.printStackTrace();
}
fromChannel.close();
}
catch (Exception e) {
System.err.println("Error accessing source file: " + mediaPath);
sbCopiedMedias.append("(ERROR accessing source file " + mediaPath + ")");
mediaInclusionErrors++;
e.printStackTrace();
}
return outputMediaName;
}
/** Prepares and writes the whole tile data for the specified zoom level to the
* files for dict and tile data.
* The tile tree's root tile is put into the member array 'tile'.
*
* @param zl Zoom level or level of detail
* @return Number of bytes written
*/
private long exportMapToMid(int zl) {
// System.out.println("Total ways : " + parser.ways.size() + " Nodes : " +
// parser.nodes.size());
OsmParser.printMemoryUsage(1);
long outputLength = 0;
try {
FileOutputStream fo = new FileOutputStream(path + "/dat/dict-" + zl + ".dat");
DataOutputStream ds = new DataOutputStream(fo);
// magic number
ds.writeUTF("DictMid");
Bounds allBound = new Bounds();
for (Way w1 : parser.getWays()) {
if (w1.getZoomlevel(configuration) != zl) {
continue;
}
w1.used = false;
allBound.extend(w1.getBounds());
}
if (zl == ROUTEZOOMLEVEL) {
// for RouteNodes
for (Node n : parser.getNodes()) {
n.used = false;
if (n.routeNode == null) {
continue;
}
allBound.extend(n.lat, n.lon);
}
} else {
for (Node n : parser.getNodes()) {
if (n.getZoomlevel(configuration) != zl) {
continue;
}
allBound.extend(n.lat, n.lon);
}
}
tile[zl] = new Tile((byte) zl);
Sequence tileSeq = new Sequence();
tile[zl].ways = parser.getWays();
tile[zl].nodes = parser.getNodes();
// create the tiles and write the content
outputLength += exportTile(tile[zl], tileSeq, allBound);
tileFilesWritten = tileSeq.get();
if (tile[zl].type != Tile.TYPE_ROUTECONTAINER && tile[zl].type != Tile.TYPE_CONTAINER) {
/*
* We must have so little data, that it completely fits within one tile.
* Never the less, the top tile should be a container tile
*/
Tile ct = new Tile((byte)zl);
ct.t1 = tile[zl];
ct.t2 = new Tile((byte)zl);
ct.t2.type = Tile.TYPE_EMPTY;
if (zl == ROUTEZOOMLEVEL) {
ct.type = Tile.TYPE_ROUTECONTAINER;
} else {
ct.type = Tile.TYPE_CONTAINER;
}
tile[zl] = ct;
}
tile[zl].recalcBounds();
if (zl == ROUTEZOOMLEVEL) {
long startTime = System.currentTimeMillis();
for (Node n : parser.getDelayingNodes()) {
if (n != null) {
if (n.isTrafficSignals()) {
tile[zl].markTrafficSignalsRouteNodes(n);
}
} else {
// this should not happen anymore because trafficSignalCount gets decremented now when the node id is duplicate
System.out.println("Warning: Delaying node is NULL");
}
}
parser.freeUpDelayingNodes();
long time = (System.currentTimeMillis() - startTime);
System.out.println(" Applied " + parser.trafficSignalCount +
" traffic signals to " + Tile.numTrafficSignalRouteNodes +
" route nodes, took " + time + " ms");
Sequence rnSeq = new Sequence();
tile[zl].renumberRouteNode(rnSeq);
tile[zl].calcHiLo();
tile[zl].writeConnections(path, parser.getTurnRestrictionHashMap());
tile[zl].type = Tile.TYPE_ROUTECONTAINER;
}
Sequence s = new Sequence();
tile[zl].writeTileDict(ds, 1, s, path);
dictFilesWritten = s.get();
// Magic number
ds.writeUTF("END");
ds.close();
fo.close();
} catch (FileNotFoundException fnfe) {
System.err.println("Unhandled FileNotFoundException: " + fnfe.getMessage());
fnfe.printStackTrace();
} catch (IOException ioe) {
System.err.println("Unhandled IOException: " + ioe.getMessage());
ioe.printStackTrace();
}
tile[zl].dissolveTileReferences();
tile[zl]=null;
return outputLength;
}
/** Prepares and writes the tile's node and way data.
* It splits the tile, creating two sub tiles, if necessary and continues to
* prepare and write their data down the tree.
* For writing, it calls writeRenderTile() or writeRouteTile().
*
* @param t Tile to export
* @param tileSeq
* @param tileBound Bounds to use
* @return Number of bytes written
* @throws IOException if there is
*/
private long exportTile(Tile t, Sequence tileSeq, Bounds tileBound) throws IOException {
Bounds realBound = new Bounds();
ArrayList<Way> ways;
Collection<Node> nodes;
int maxSize;
int maxWays = 0;
boolean unsplittableTile;
boolean tooLarge;
long outputLength = 0;
/*
* Using recursion can cause a stack overflow on large projects,
* so we need an explicit stack that can grow larger.
*/
Stack<TileTuple> expTiles = new Stack<TileTuple>();
byte [] out = new byte[1];
expTiles.push(new TileTuple(t, tileBound));
byte [] connOut = new byte[1];
// System.out.println("Exporting Tiles");
while (!expTiles.isEmpty()) {
TileTuple tt = expTiles.pop();
unsplittableTile = false;
tooLarge = false;
t = tt.t;
tileBound = tt.bound;
// System.out.println("try create tile for " + t.zl + " " + tileBound);
ways = new ArrayList<Way>();
nodes = new ArrayList<Node>();
realBound = new Bounds();
if (t.zl != ROUTEZOOMLEVEL) {
// Reduce the content of 'ways' and 'nodes' to all relevant elements
// in the given bounds and create the binary map representation
maxSize = configuration.getMaxTileSize();
maxWays = configuration.getMaxTileWays(t.zl);
ways = getWaysInBound(t.ways, t.zl, tileBound, realBound);
if (realBound.getFixPtSpan() > 65000
// && (t.nodes.size() == nodes.size()) && (t.ways.size() == ways.size())
&& (tileBound.maxLat - tileBound.minLat < 0.001)) {
System.out.println("ERROR: Tile spacially too large (" +
MAX_RAD_RANGE + "tileBound: " + tileBound);
System.out.println("ERROR:: Could not reduce tile size for tile " + t);
System.out.println(" t.ways=" + t.ways.size() + ", t.nodes=" + t.nodes.size());
System.out.println(" realBound=" + realBound);
System.out.println(" tileBound.maxLat " + tileBound.maxLat
+ " tileBound.minLat: " + tileBound.minLat);
for (Way w : t.ways) {
System.out.println(" Way: " + w);
}
System.out.println("Trying to recover, but at least some map data is lost");
realBound = tileBound;
}
nodes = getNodesInBound(t.nodes, t.zl, tileBound);
// System.out.println("found " + nodes.size() + " node and " +
// ways.size() + " ways maxSize=" + maxSize + " maxWay=" + maxWays);
for (Node n : nodes) {
realBound.extend(n.lat, n.lon);
}
if (ways.size() == 0) {
t.type = Tile.TYPE_EMPTY;
}
int mostlyInBound = ways.size();
addWaysCompleteInBound(ways, t.ways, t.zl, realBound);
//System.out.println("ways.size : " + ways.size() + " mostlyInBound: " + mostlyInBound);
if (ways.size() > 2 * mostlyInBound) {
// System.out.println("ways.size > 2 * mostlyInBound, mostlyInBound: " + mostlyInBound);
realBound = new Bounds();
ways = getWaysInBound(t.ways, t.zl, tileBound, realBound);
// add nodes as well to the bound HMu: 29.3.2010
for (Node n : nodes) {
realBound.extend(n.lat, n.lon);
}
}
if (ways.size() <= maxWays) {
t.bounds = realBound.clone();
if (t.bounds.getFixPtSpan() > 65000) {
// System.out.println("Tile spacially too large (" +
// MAX_RAD_RANGE + ": " + t.bounds);
tooLarge = true;
// TODO: Doesn't this mean that tile data which should be
// processed is dropped? I think createMidContent() is never
// called for it.
} else {
t.centerLat = (t.bounds.maxLat + t.bounds.minLat) / 2;
t.centerLon = (t.bounds.maxLon + t.bounds.minLon) / 2;
// TODO: Isn't this run for tiles which will be split down in
// this method (below comment "Tile is too large, try to split it.")?
out = createMidContent(ways, nodes, t);
}
}
/**
* If the number of nodes and ways in the new tile is the same, and the bound
* has already been shrunk to less than 0.001°, then give up and declare it a
* unsplittable tile and just live with the fact that this tile is too big.
* Otherwise we can get into an endless loop of trying to split up this tile.
*/
if ((t.nodes.size() == nodes.size()) && (t.ways.size() == ways.size())
&& (tileBound.maxLat - tileBound.minLat < 0.001)
&& (tileBound.maxLon - tileBound.minLon < 0.001))
{
System.out.println("WARNING: Could not reduce tile size for tile " + t);
System.out.println(" t.ways=" + t.ways.size() + ", t.nodes=" + t.nodes.size());
System.out.println(" t.bounds=" + t.bounds);
System.out.println(" tileBound.maxLat " + tileBound.maxLat
+ " tileBound.minLat: " + tileBound.minLat);
System.out.println(" tileBound.maxLon " + tileBound.maxLon
+ " tileBound.minLon: " + tileBound.minLon);
for (Way w : t.ways) {
System.out.println(" Way: " + w);
}
unsplittableTile = true;
if (tooLarge) {
out = createMidContent(ways, nodes, t);
}
}
t.nodes = nodes;
t.ways = ways;
//t.generateSeaPolygon();
// TODO: Check if createMidContent() should be here.
} else {
// Route Nodes
maxSize = configuration.getMaxRouteTileSize();
nodes = getRouteNodesInBound(t.nodes, tileBound, realBound);
byte[][] erg = createMidContent(nodes, t);
out = erg[0];
connOut = erg[1];
t.nodes = nodes;
}
if (unsplittableTile && tooLarge) {
System.out.println("ERROR: Tile is unsplittable, but too large. Can't deal with this! Will try to recover, but some map data has not been processed and the map will probably have errors.");
}
// Split tile if more then 255 Ways or binary content > MAX_TILE_FILESIZE but not if only one Way
// System.out.println("out.length=" + out.length + " ways=" + ways.size());
boolean tooManyWays = ways.size() > maxWays;
boolean tooManyBytes = out.length > maxSize;
if ((!unsplittableTile) && ((tooManyWays || (tooManyBytes && ways.size() != 1) || tooLarge))) {
// Tile is too large, try to split it.
// System.out.println("create Subtiles size=" + out.length + " ways=" + ways.size());
t.bounds = realBound.clone();
if (t.zl != ROUTEZOOMLEVEL) {
t.type = Tile.TYPE_CONTAINER;
} else {
t.type = Tile.TYPE_ROUTECONTAINER;
}
t.t1 = new Tile(t.zl, ways, nodes);
t.t2 = new Tile(t.zl, ways, nodes);
t.setRouteNodes(null);
// System.out.println("split tile because it`s too big, tooLarge=" + tooLarge +
// " tooManyWays=" + tooManyWays + " tooManyBytes=" + tooManyBytes);
if ((tileBound.maxLat-tileBound.minLat) > (tileBound.maxLon-tileBound.minLon)) {
// split to half latitude
float splitLat = (tileBound.minLat + tileBound.maxLat) / 2;
Bounds nextTileBound = tileBound.clone();
nextTileBound.maxLat = splitLat;
expTiles.push(new TileTuple(t.t1, nextTileBound));
nextTileBound = tileBound.clone();
nextTileBound.minLat = splitLat;
expTiles.push(new TileTuple(t.t2, nextTileBound));
} else {
// split to half longitude
float splitLon = (tileBound.minLon + tileBound.maxLon) / 2;
Bounds nextTileBound = tileBound.clone();
nextTileBound.maxLon = splitLon;
expTiles.push(new TileTuple(t.t1, nextTileBound));
nextTileBound = tileBound.clone();
nextTileBound.minLon = splitLon;
expTiles.push(new TileTuple(t.t2, nextTileBound));
}
t.ways = null;
t.nodes = null;
// System.gc();
} else {
// Tile has the right size or is not splittable, so it can be written.
// System.out.println("use this tile, will write " + out.length + " bytes");
if (ways.size() > 0 || nodes.size() > 0) {
// Write as dataTile
t.fid = tileSeq.next();
if (t.zl != ROUTEZOOMLEVEL) {
writeRenderTile(t, tileBound, realBound, nodes, out);
outputLength += out.length;
} else {
writeRouteTile(t, tileBound, realBound, nodes, out);
outputLength += out.length;
outputLengthConns += connOut.length;
}
} else {
//Write as empty box
// System.out.println("this is an empty box");
t.type = Tile.TYPE_EMPTY;
}
}
}
return outputLength;
}
/**
* @param t
* @param tileBound
* @param realBound
* @param nodes
* @param out
* @throws FileNotFoundException
* @throws IOException
*/
private void writeRouteTile(Tile t, Bounds tileBound, Bounds realBound,
Collection<Node> nodes, byte[] out) {
//System.out.println("Writing render tile " + t.zl + ":" + t.fid +
// " nodes:" + nodes.size());
t.type = Tile.TYPE_MAP;
t.bounds = tileBound.clone();
t.type = Tile.TYPE_ROUTEDATA;
for (RouteNode n:t.getRouteNodes()) {
n.node.used = true;
}
}
/**
* Writes the byte array to a file for the file t i.e. the name of the file is
* derived from the zoom level and fid of this tile.
* Also marks all ways of t and sets the fid of these ways and of all nodes in 'nodes'.
* Plus it sets the type of t to Tile.TYPE_MAP, sets its bounds to realBound and
* updates totalNodesWritten, totalWaysWritten, totalSegsWritten and totalPOIsWritten.
*
* @param t Tile to work on
* @param tileBound Bounds of tile will be set to this if it's a route tile
* @param realBound Bounds of tile will be set to this
* @param nodes Nodes to update with the fid
* @param out Byte array to write to the file
* @throws FileNotFoundException if file could not be created
* @throws IOException if an IO error occurs while writing the file
*/
private void writeRenderTile(Tile t, Bounds tileBound, Bounds realBound,
Collection<Node> nodes, byte[] out)
throws FileNotFoundException, IOException {
// System.out.println("Writing render tile " + t.zl + ":" + t.fid +
// " ways:" + t.ways.size() + " nodes:" + nodes.size());
totalNodesWritten += nodes.size();
totalWaysWritten += t.ways.size();
//TODO: Is this safe to comment out??
//Hmu: this was used to have a defined order of drawing. Small ways first, highways last.
//Collections.sort(t.ways);
for (Way w: t.ways) {
totalSegsWritten += w.getLineCount();
}
if (t.zl != ROUTEZOOMLEVEL) {
for (Node n : nodes) {
if (n.getType(null) > -1 ) {
totalPOIsWritten++;
}
}
}
t.type = Tile.TYPE_MAP;
// RouteTiles will be written later because of renumbering
if (t.zl != ROUTEZOOMLEVEL) {
t.bounds = realBound.clone();
String lpath = path + "/t" + t.zl ;
FileOutputStream fo = FileTools.createFileOutputStream(lpath + "/" + t.fid + ".d");
DataOutputStream tds = new DataOutputStream(fo);
tds.write(out);
tds.close();
fo.close();
// mark nodes as written to MidStorage
for (Node n : nodes) {
if (n.fid) {
System.out.println("DATA DUPLICATION: This node has been written already! " + n);
}
n.fid = true;
}
// mark ways as written to MidStorage
for (Iterator<Way> wi = t.ways.iterator(); wi.hasNext(); ) {
Way w1 = wi.next();
w1.used = true;
// triangles can be cleared but not set to null because of SearchList.java
if ( w1.triangles != null ) {
w1.triangles.clear();
}
}
} else {
t.bounds = tileBound.clone();
t.type = Tile.TYPE_ROUTEDATA;
for (RouteNode n:t.getRouteNodes()) {
n.node.used = true;
}
}
}
/** Collects all ways from parentWays which
* 1) have a type >= 1 (whatever that means)
* 2) belong to the zoom level zl
* 3) aren't already marked as used and
* 4) are mostly inside the boundaries of targetBounds.
* realBound is extended to cover all these ways.
*
* @param parentWays the collection that will be used for search
* @param zl the level of detail
* @param targetBounds bounds used for the search
* @param realBound bounds to extend to cover all ways found
* @return LinkedList of all ways which meet the described conditions.
*/
private ArrayList<Way> getWaysInBound(Collection<Way> parentWays, int zl,
Bounds targetBounds, Bounds realBound) {
ArrayList<Way> ways = new ArrayList<Way>();
// System.out.println("Searching for ways mostly in " + targetTile + " from " +
// parentWays.size() + " ways");
// Collect all ways that are in this rectangle
for (Way w1 : parentWays) {
// polish.api.bigstyles
short type = w1.getType();
if (type < 1) {
continue;
}
if (w1.getZoomlevel(configuration) != zl) {
continue;
}
if (w1.used) {
continue;
}
Bounds wayBound = w1.getBounds();
if (targetBounds.isMostlyIn(wayBound)) {
realBound.extend(wayBound);
ways.add(w1);
}
}
// System.out.println("getWaysInBound found " + ways.size() + " ways");
return ways;
}
/** Collects all ways from parentWays which
* 1) have a type >= 1 (whatever that means)
* 2) belong to the zoom level zl
* 3) aren't already marked as used
* 4) are completely inside the boundaries of targetTile.
* Ways are only added once to the list.
*
* @param ways Initial list of ways to which to add
* @param parentWays the list that will be used for search
* @param zl the level of detail
* @param targetBounds bounds used for the search
* @return The list 'ways' plus the ways found
*/
private ArrayList<Way> addWaysCompleteInBound(ArrayList<Way> ways,
Collection<Way> parentWays, int zl, Bounds targetBounds) {
// collect all way that are in this rectangle
// System.out.println("Searching for ways total in " + targetBounds +
// " from " + parentWays.size() + " ways");
//This is a bit of a hack. We should probably propagate the TreeSet through out,
//But that needs more effort and time than I currently have. And this way we get
//rid of a O(n^2) bottle neck
TreeSet<Way> waysTS = new TreeSet<Way>(ways);
for (Way w1 : parentWays) {
// polish.api.bigstyles
short type = w1.getType();
if (type < 1) {
continue;
}
if (w1.getZoomlevel(configuration) != zl) {
continue;
}
if (w1.used) {
continue;
}
if (waysTS.contains(w1)) {
continue;
}
Bounds wayBound = w1.getBounds();
if (targetBounds.isCompleteIn(wayBound)) {
waysTS.add(w1);
ways.add(w1);
}
}
// System.out.println("addWaysCompleteInBound found " + ways.size() + " ways");
return ways;
}
/**
* Find all nodes out of the given collection that are within the bounds and in the correct zoom level.
*
* @param parentNodes the collection that will be used for search
* @param zl the level of detail
* @param targetBound the target boundaries
* @return Collection of the nodes found
*/
public Collection<Node> getNodesInBound(Collection<Node> parentNodes, int zl, Bounds targetBound) {
Collection<Node> nodes = new LinkedList<Node>();
for (Node node : parentNodes) {
//Check to see if the node has already been written to MidStorage
//If yes, then ignore the node here, to prevent duplicate nodes
//due to overlapping tiles
if (node.fid) {
continue;
}
if (node.getType(configuration) < 0) {
continue;
}
if (node.getZoomlevel(configuration) != zl) {
continue;
}
if (! targetBound.isIn(node.lat, node.lon)) {
continue;
}
nodes.add(node);
}
// System.out.println("getNodesInBound found " + nodes.size() + " nodes");
return nodes;
}
public Collection<Node> getRouteNodesInBound(Collection<Node> parentNodes,
Bounds targetBound, Bounds realBound) {
Collection<Node> nodes = new LinkedList<Node>();
for (Node node : parentNodes) {
if (node.routeNode == null) {
continue;
}
if (! targetBound.isIn(node.lat, node.lon)) {
continue;
}
// System.out.println(node.used);
if (! node.used) {
realBound.extend(node.lat, node.lon);
nodes.add(node);
// node.used = true;
}
}
return nodes;
}
/**
* Create the data-content for a route-tile, containing a list of nodes and a list
* of connections from each node.
* @param interestNodes list of all Nodes that should be included in this tile
* @param t the tile that holds the meta-data
* @return in array[0][] the file-format for all nodes and in array[1][] the
* file-format for all connections within this tile.
* @throws IOException
*/
public byte[][] createMidContent(Collection<Node> interestNodes, Tile t) throws IOException {
ByteArrayOutputStream nfo = new ByteArrayOutputStream();
DataOutputStream nds = new DataOutputStream(nfo);
ByteArrayOutputStream cfo = new ByteArrayOutputStream();
DataOutputStream cds = new DataOutputStream(cfo);
nds.writeByte(0x54); // magic number
nds.writeShort(interestNodes.size());
for (Node n : interestNodes) {
writeRouteNode(n, nds, cds);
if (n.routeNode != null) {
t.addRouteNode(n.routeNode);
}
}
nds.writeByte(0x56); // magic number
byte [][] ret = new byte[2][];
ret[0] = nfo.toByteArray();
ret[1] = cfo.toByteArray();
nds.close();
cds.close();
nfo.close();
cfo.close();
return ret;
}
/**
* Create the Data-content for a SingleTile in memory. This will later directly
* be written to disk if the byte array is not too big, otherwise this tile will
* be split in smaller tiles.
* @param ways A collection of ways that are chosen to be in this tile.
* @param interestNodes all additional nodes like places, parking and so on
* @param t the tile, holds the metadata for this area.
* @return a byte array that represents a file content. This could be written
* directly to disk.
* @throws IOException
*/
public byte[] createMidContent(Collection<Way> ways, Collection<Node> interestNodes, Tile t) throws IOException {
Map<Long, Node> wayNodes = new HashMap<Long, Node>();
int ren = 0;
// reset all used flags of all Nodes that are part of ways in <code>ways</code>
for (Way way : ways) {
for (Node n : way.getNodes()) {
n.used = false;
}
}
// mark all interestNodes as used
for (Node n1 : interestNodes) {
n1.used = true;
}
// find all nodes that are part of a way but not in interestNodes
for (Way w1 : ways) {
if (w1.isArea()) {
if (Configuration.getConfiguration().getTriangleAreaFormat()) {
if (w1.checkTriangles() != null) {
for (Triangle tri : w1.checkTriangles()) {
for (int lo = 0; lo < 3; lo++) {
addUnusedNode(wayNodes, tri.getVert()[lo].getNode());
}
}
}
}
if (Configuration.getConfiguration().getOutlineAreaFormat()) {
for (Node n : w1.getOutlineNodes()) {
addUnusedNode(wayNodes, n);
}
ArrayList<Path> holes = w1.getHoles();
if (holes != null) {
for (Path hole : holes) {
for (Node n : hole.getNodes()) {
addUnusedNode(wayNodes, n);
}
}
}
}
} else {
for (Node n : w1.getNodes()) {
addUnusedNode(wayNodes, n);
}
}
}
// Create a byte arrayStream which holds the Singletile-Data,
// this is created in memory and written later if file is
// not too big.
ByteArrayOutputStream fo = new ByteArrayOutputStream();
DataOutputStream ds = new DataOutputStream(fo);
ds.writeByte(0x54); // Magic number
ds.writeFloat(MyMath.degToRad(t.centerLat));
ds.writeFloat(MyMath.degToRad(t.centerLon));
ds.writeShort(interestNodes.size() + wayNodes.size());
ds.writeShort(interestNodes.size());
for (Node n : interestNodes) {
n.renumberdId = (short) ren++;
//The exclusion of nodes is not perfect, as there
//is a time between adding nodes to the write buffer
//and before marking them as written, so we might
//still hit the case when a node is written twice.
//Warn about this fact to fix this correctly at a
//later stage
if (n.fid) {
System.out.println("WARNING: Writing interest node twice, " + n);
}
writeNode(n, ds, INODE, t);
}
for (Node n : wayNodes.values()) {
n.renumberdId = (short) ren++;
writeNode(n, ds, SEGNODE, t);
}
ds.writeByte(0x55); // Magic number
if (Configuration.getConfiguration().writeTriangleAreaFormat) {
ds.writeShort(ways.size());
for (Way w : ways) {
w.write(ds, names1, urls1, t, false);
}
ds.writeByte(0x56); // Magic number
} else {
// triangulate anyway, as it may be needed for fallback to
// triangle format, just don't write the data
// FIXME: later only triangulate when fallback required
//for (Way w : ways) {
// Bounds b = w.getBounds();
// if (w.isArea()) {
// if (w.triangles == null) {
// w.triangulate();
// }
// }
//}
}
if (Configuration.getConfiguration().getOutlineAreaFormat()) {
ds.writeShort(ways.size());
for (Way w : ways) {
w.write(ds, names1, urls1, t, true);
}
ds.writeByte(0x57); // Magic number
}
ds.close();
byte[] ret = fo.toByteArray();
fo.close();
return ret;
}
/**
* Adds the node n and its ID to the map wayNodes if it's an unused node and if
* it isn't already in the map.
*
* @param wayNodes
* @param n
*/
private void addUnusedNode(Map<Long, Node> wayNodes, Node n) {
Long id = new Long(n.id);
if ((!wayNodes.containsKey(id)) && !n.used) {
wayNodes.put(id, n);
}
}
/* FIXME: This is not actually the data written to the file system but rather is used to calculate route tile sizes
* The actual route data is written in Tile.writeConnections() and sizes from this data should be used
*/
private void writeRouteNode(Node n, DataOutputStream nds, DataOutputStream cds) throws IOException {
nds.writeByte(4);
nds.writeFloat(MyMath.degToRad(n.lat));
nds.writeFloat(MyMath.degToRad(n.lon));
nds.writeInt(cds.size());
nds.writeByte(n.routeNode.getConnected().length);
for (Connection c : n.routeNode.getConnected()) {
cds.writeInt(c.to.node.renumberdId);
// set or clear flag for additional byte (connTravelModes is transferred from wayTravelMode were this is Connection.CONNTYPE_TOLLROAD, so clear it if not required)
c.connTravelModes |= Connection.CONNTYPE_CONNTRAVELMODES_ADDITIONAL_BYTE;
if (c.connTravelModes2 == 0) {
c.connTravelModes ^= Connection.CONNTYPE_CONNTRAVELMODES_ADDITIONAL_BYTE;
}
// write out wayTravelModes flag
cds.writeByte(c.connTravelModes);
if (c.connTravelModes2 != 0) {
cds.writeByte(c.connTravelModes2);
}
for (int i = 0; i < TravelModes.travelModeCount; i++) {
// only store times for available travel modes of the connection
if ( (c.connTravelModes & (1 << i)) !=0 ) {
/**
* If we can't fit the values into short,
* we write an int. In order for the other
* side to know if we wrote an int or a short,
* we encode the length in the top most (sign) bit
*/
int time = c.times[i];
if (time > Short.MAX_VALUE) {
cds.writeInt(-1 * time);
} else {
cds.writeShort((short) time);
}
}
}
if (c.length > Short.MAX_VALUE) {
cds.writeInt(-1 * c.length);
} else {
cds.writeShort((short) c.length);
}
cds.writeByte(c.startBearing);
cds.writeByte(c.endBearing);
}
}
private void writeNode(Node n, DataOutputStream ds, int type, Tile t) throws IOException {
int flags = 0;
int flags2 = 0;
int nameIdx = -1;
int urlIdx = -1;
int phoneIdx = -1;
Configuration config = Configuration.getConfiguration();
if (type == INODE) {
if (! "".equals(n.getName())) {
flags += Constants.NODE_MASK_NAME;
nameIdx = names1.getNameIdx(n.getName());
if (nameIdx >= Short.MAX_VALUE) {
flags += Constants.NODE_MASK_NAMEHIGH;
}
}
if (config.useUrlTags) {
if (! "".equals(n.getUrl())) {
flags += Constants.NODE_MASK_URL;
urlIdx = urls1.getUrlIdx(n.getUrl());
if (urlIdx >= Short.MAX_VALUE) {
flags += Constants.NODE_MASK_URLHIGH;
}
}
}
if (config.usePhoneTags) {
if (! "".equals(n.getPhone())) {
flags2 += Constants.NODE_MASK2_PHONE;
phoneIdx = urls1.getUrlIdx(n.getPhone());
if (phoneIdx >= Short.MAX_VALUE) {
flags2 += Constants.NODE_MASK2_PHONEHIGH;
}
}
}
if (n.getType(configuration) != -1) {
flags += Constants.NODE_MASK_TYPE;
}
}
if (flags2 != 0) {
flags += Constants.NODE_MASK_ADDITIONALFLAG;
}
ds.writeByte(flags);
if (flags2 != 0) {
ds.writeByte(flags2);
}
/**
* Convert coordinates to relative fixpoint (integer) coordinates
* The reference point is the center of the tile.
* With 16bit shorts, this should allow for tile sizes of
* about 65 km in width and with 1 m accuracy at the equator.
*/
double tmpLat = (MyMath.degToRad(n.lat - t.centerLat)) * MyMath.FIXPT_MULT;
double tmpLon = (MyMath.degToRad(n.lon - t.centerLon)) * MyMath.FIXPT_MULT;
if ((tmpLat > Short.MAX_VALUE) || (tmpLat < Short.MIN_VALUE)) {
// see https://sourceforge.net/tracker/index.php?func=detail&aid=3556775&group_id=192084&atid=939974 for details of how this relates to area outlines
System.err.println("WARNING: Numeric overflow of latitude, " + tmpLat + " for node: " + n.id + ", trying to handle");
if (tmpLat > Short.MAX_VALUE) {
tmpLat = Short.MAX_VALUE - 10;
}
if (tmpLat < Short.MIN_VALUE) {
tmpLat = Short.MIN_VALUE + 10;
}
}
if ((tmpLon > Short.MAX_VALUE) || (tmpLon < Short.MIN_VALUE)) {
System.err.println("WARNING: Numeric overflow of longitude, " + tmpLon + " for node: " + n.id + ", trying to handle");
if (tmpLon > Short.MAX_VALUE) {
tmpLon = Short.MAX_VALUE - 10;
}
if (tmpLon < Short.MIN_VALUE) {
tmpLon = Short.MIN_VALUE + 10;
}
}
ds.writeShort((short)tmpLat);
ds.writeShort((short)tmpLon);
if ((flags & Constants.NODE_MASK_NAME) > 0) {
if ((flags & Constants.NODE_MASK_NAMEHIGH) > 0) {
ds.writeInt(nameIdx);
} else {
ds.writeShort(nameIdx);
}
}
if ((flags & Constants.NODE_MASK_URL) > 0) {
if ((flags & Constants.NODE_MASK_URLHIGH) > 0) {
ds.writeInt(urlIdx);
} else {
ds.writeShort(urlIdx);
}
}
if ((flags2 & Constants.NODE_MASK2_PHONE) > 0) {
if ((flags2 & Constants.NODE_MASK2_PHONEHIGH) > 0) {
ds.writeInt(phoneIdx);
} else {
ds.writeShort(phoneIdx);
}
}
if ((flags & Constants.NODE_MASK_TYPE) > 0) {
// polish.api.bigstyles
if (Configuration.getConfiguration().bigStyles) {
ds.writeShort(n.getType(configuration));
} else {
ds.writeByte(n.getType(configuration));
}
if (configuration.enableEditingSupport) {
if (n.id > Integer.MAX_VALUE) {
// FIXME enable again after Relations.java doesn't use fake ids
//System.err.println("WARNING: Node OSM-ID won't fit in 32 bits for way " + n);
ds.writeInt(-1);
} else {
ds.writeInt((int)n.id);
}
}
}
}
/**
* @param c
*/
public void setConfiguration(Configuration c) {
this.configuration = c;
}
/**
* @param rd
*/
public void setRouteData(RouteData rd) {
this.rd = rd;
}
}