/**
* 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 Kai Krueger
*
*/
package net.sharenav.osmToShareNav.model;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import net.sharenav.osmToShareNav.Configuration;
import net.sharenav.osmToShareNav.CreateShareNavData;
import net.sharenav.osmToShareNav.MyMath;
import net.sharenav.osmToShareNav.area.SeaGenerator;
import net.sharenav.osmToShareNav.tools.FileTools;
public class Tile {
/** The boundaries of this tile or tile tree. */
public Bounds bounds = new Bounds();
/**
* Center latitude of the tile in degrees.
* This is used as a reference point for the relative coordinates
* stored in the file.
*/
public float centerLat;
/**
* Center longitude of the tile in degrees.
* This is used as a reference point for the relative coordinates
* stored in the file.
*/
public float centerLon;
/** If this tile is a container, this holds the reference to the first sub-tile. */
public Tile t1 = null;
/** If this tile is a container, this holds the reference to the second sub-tile. */
public Tile t2 = null;
/** TODO: I love self-explaining variable names likes this! */
public int fid;
/** The type of this tile, see the TYPE_XXX constants for possible values. */
public byte type;
/** The data of this tile is for this zoom level. */
public byte zl;
public Collection<Way> ways = null;
private ArrayList<RouteNode> routeNodes = null;
public Collection<Node> nodes = new ArrayList<Node>();
int idxMin = Integer.MAX_VALUE;
int idxMax = 0;
short numMainStreetRouteNodes = 0;
public static int numTrafficSignalRouteNodes = 0;
private static int minConnectionId = 0;
public static final byte TYPE_MAP = 1;
public static final byte TYPE_CONTAINER = 2;
public static final byte TYPE_FILETILE = 4;
public static final byte TYPE_EMPTY = 3;
public static final byte TYPE_ROUTEDATA = 5;
public static final byte TYPE_ROUTECONTAINER = 6;
public static final byte TYPE_ROUTEFILE = 7;
/** TODO: Explain */
public static final float RTEpsilon = 0.012f;
public Tile() {
super();
}
public Tile(byte zl) {
this.zl = zl;
}
public Tile(byte zl, ArrayList<Way> ways, Collection<Node> nodes) {
this.zl = zl;
this.ways = ways;
this.nodes = nodes;
}
public Tile(Bounds b) {
bounds = b.clone();
}
public void dissolveTileReferences() {
if ( ways!=null)
ways.clear();
if (nodes!=null)
nodes.clear();
if (routeNodes!=null)
routeNodes.clear();
ways = null;
nodes = null;
routeNodes = null;
bounds = null;
if ( t1 != null ) {
t1.dissolveTileReferences();
t1=null;
}
if ( t2 != null ) {
t2.dissolveTileReferences();
t2=null;
}
}
/** Writes the dictionary file(s) for this tile. Walks down the tile tree to do this.
*
* @param ds Stream to use for writing if this is a container or route container tile
* @param deep Current depth of this tile
* @param fid File ID to use
* @param path Base path to use for the map data
* @throws IOException if the file could not be created or if there was an IO error
*/
public void writeTileDict(DataOutputStream ds, Integer deep,
Sequence fid, String path) throws IOException {
DataOutputStream lds;
boolean openStream;
// System.out.println("Write Tile type=" + type + " deep=" + deep + " fid=" + fid);
if ((type == TYPE_CONTAINER || type == TYPE_ROUTECONTAINER)
&& deep >= Configuration.getConfiguration().getMaxDictDepth()) {
// System.out.println("Start new Dict file");
// Write this containerTile as a FileTile. This container will be then
// placed within this new FileTile.
if (zl != CreateShareNavData.ROUTEZOOMLEVEL) {
ds.writeByte(TYPE_FILETILE);
ds.writeFloat(degToRad(bounds.minLat));
ds.writeFloat(degToRad(bounds.minLon));
ds.writeFloat(degToRad(bounds.maxLat));
ds.writeFloat(degToRad(bounds.maxLon));
} else {
ds.writeByte(TYPE_ROUTEFILE);
ds.writeFloat(degToRad(bounds.minLat - RTEpsilon));
ds.writeFloat(degToRad(bounds.minLon - RTEpsilon));
ds.writeFloat(degToRad(bounds.maxLat + RTEpsilon));
ds.writeFloat(degToRad(bounds.maxLon + RTEpsilon));
ds.writeInt(idxMin);
ds.writeInt(idxMax);
}
ds.writeShort(fid.get());
openStream = true;
FileOutputStream fo = FileTools.createFileOutputStream(
path + "/d" + zl + "/" + fid.get() + ".d");
lds = new DataOutputStream(fo);
lds.writeUTF("DictMid");
fid.inc();
deep = 1;
} else {
openStream = false;
lds = ds;
deep++;
}
switch (type) {
case TYPE_MAP:
case TYPE_ROUTEDATA:
// System.out.println("Type 1");
if (zl != CreateShareNavData.ROUTEZOOMLEVEL) {
lds.writeByte(TYPE_MAP);
lds.writeFloat(degToRad(bounds.minLat));
lds.writeFloat(degToRad(bounds.minLon));
lds.writeFloat(degToRad(bounds.maxLat));
lds.writeFloat(degToRad(bounds.maxLon));
} else {
lds.writeByte(TYPE_ROUTEDATA);
lds.writeFloat(degToRad(bounds.minLat - RTEpsilon));
lds.writeFloat(degToRad(bounds.minLon - RTEpsilon));
lds.writeFloat(degToRad(bounds.maxLat + RTEpsilon));
lds.writeFloat(degToRad(bounds.maxLon + RTEpsilon));
lds.writeInt(idxMin);
lds.writeInt(idxMax);
}
// TODO: Sometimes, the fid which is passed to this method is written,
// but here, fid from the this object is used. Is this correct?
lds.writeInt(this.fid);
// ds.writeInt(ds.size());
break;
case TYPE_CONTAINER:
case TYPE_ROUTECONTAINER:
// System.out.println("Type 2");
if (zl != CreateShareNavData.ROUTEZOOMLEVEL) {
lds.writeByte(TYPE_CONTAINER);
lds.writeFloat(degToRad(bounds.minLat));
lds.writeFloat(degToRad(bounds.minLon));
lds.writeFloat(degToRad(bounds.maxLat));
lds.writeFloat(degToRad(bounds.maxLon));
} else {
lds.writeByte(TYPE_ROUTECONTAINER);
lds.writeFloat(degToRad(bounds.minLat - RTEpsilon));
lds.writeFloat(degToRad(bounds.minLon - RTEpsilon));
lds.writeFloat(degToRad(bounds.maxLat + RTEpsilon));
lds.writeFloat(degToRad(bounds.maxLon + RTEpsilon));
lds.writeInt(idxMin);
lds.writeInt(idxMax);
}
t1.writeTileDict(lds, deep, fid, path);
t2.writeTileDict(lds, deep, fid, path);
break;
case TYPE_EMPTY:
// System.out.println("Type 3");
lds.writeByte(TYPE_EMPTY);
// case 4:
// System.out.println("Type 4");
// lds.writeByte(4);
// lds.writeFloat(degToRad(bounds.minLat));
// lds.writeFloat(degToRad(bounds.minLon));
// lds.writeFloat(degToRad(bounds.maxLat));
// lds.writeFloat(degToRad(bounds.maxLon));
// lds.writeShort(fid);
}
if (openStream) {
lds.writeUTF("END"); // Magic number
lds.close();
}
}
public float degToRad(double deg) {
return (float) (deg * (Math.PI / 180.0d));
}
/**
* Recalculate the bounds of this tile tree starting from
* the current tile in case new tiles have been added to the
* tree. Leaf tiles are assumed to have the right bound.
*
* This function updates the bounds along the way
*
* @return the bounds for the current tile tree.
*/
public Bounds recalcBounds() {
Bounds b1 = null;
Bounds b2 = null;
if (type == TYPE_MAP || type == TYPE_ROUTEDATA) {
/**
* This is a leaf of the tile tree and should have correct bounds
* anyway, so don't update and return the current bounds
*/
return bounds;
}
Bounds ret = bounds.clone();
if (t1 != null && (t1.type != TYPE_EMPTY)) {
b1 = t1.recalcBounds();
ret = b1.clone();
}
if (t2 != null && (t2.type != TYPE_EMPTY)) {
b2 = t2.recalcBounds();
if (ret != null) {
ret.extend(b2);
}
}
bounds = ret; //Update current bounds
return ret;
}
public Bounds getBounds() {
return bounds;
}
public Collection<Way> getWays() {
return ways;
}
public void setWays(LinkedList<Way> ways) {
this.ways = ways;
}
public void addWay(Way w) {
if (ways != null) {
ways.add(w);
}
}
public ArrayList<RouteNode> getRouteNodes() {
return routeNodes;
}
public void setRouteNodes(ArrayList<RouteNode> routeNodes) {
this.routeNodes = routeNodes;
}
/**
* @param routeNode
*/
public void addRouteNode(RouteNode routeNode) {
if (routeNodes == null) {
routeNodes = new ArrayList<RouteNode>();
}
routeNodes.add(routeNode);
// System.out.println("RouteNodes.add(" + routeNode + ")");
}
/**
*
*/
public void renumberRouteNode(Sequence rnSeq) {
if (type == TYPE_ROUTECONTAINER) {
if (t1 != null) {
t1.renumberRouteNode(rnSeq);
}
if (t2 != null) {
t2.renumberRouteNode(rnSeq);
}
}
if (type == TYPE_ROUTEDATA) {
boolean isOnMainStreetNet = false;
// renumber the mainStreetNet routeNodes in the first loop,
// in the second loop the remaining ones
for (int writeStreetNets = 0; writeStreetNets <= 1; writeStreetNets++) {
for (RouteNode rn : routeNodes) {
isOnMainStreetNet = rn.isOnMainStreetNet();
if (writeStreetNets == 0 && isOnMainStreetNet
||
writeStreetNets > 0 && !isOnMainStreetNet
) {
rn.id = rnSeq.get();
// if (isOnMainStreetNet) {
// System.out.println("main" + rn.id);
// } else {
// System.out.println("norm" + rn.id);
// }
rnSeq.inc();
if (isOnMainStreetNet) {
numMainStreetRouteNodes++;
}
}
}
}
}
}
public void markTrafficSignalsRouteNodes(Node n) {
if (type == TYPE_ROUTECONTAINER) {
if (t1 != null) {
t1.markTrafficSignalsRouteNodes(n);
}
if (t2 != null) {
t2.markTrafficSignalsRouteNodes(n);
}
} else if (type == TYPE_ROUTEDATA && bounds.isInOrAlmostIn(n.lat, n.lon)) {
for (RouteNode rn : routeNodes) {
if (MyMath.dist(n, rn.node) < 25) {
if (!rn.node.isNeverTrafficSignalsRouteNode()) {
rn.node.markAsTrafficSignalsRouteNode();
numTrafficSignalRouteNodes++;
// System.out.println(MyMath.dist(n, rn.node) + "Traffic Light Delay from traffic light" +
// n.toUrl() + " at " + rn.node.toUrl());
} else {
;
//System.out.println("Route node became no traffic signal delay route node because it's at a tunnel or bridge: " + rn.node.toUrl());
}
}
}
}
}
/**
* Checks for coast lines and creates one (or more) sea polygons which extend(s) to
* the boundaries of this tile.
* If this tile is a container, it makes its children create these areas.
*/
public void generateSeaPolygon() {
if (type == TYPE_CONTAINER) {
if (t1 != null) {
t1.generateSeaPolygon();
}
if (t2 != null) {
t2.generateSeaPolygon();
}
} else if (type == TYPE_MAP) {
System.out.println("info: Tile zl=" + zl + ", fid=" + fid);
SeaGenerator.generateSeaPolygon(this);
}
if (bounds.getFixPtSpan() <= 65000) {
//System.out.println("info: Checking for coast line: Tile zl=" + zl + ", fid=" + fid);
SeaGenerator.generateSeaPolygon(this);
}
}
/**
* Prints the correct sequence of RouteNodes for debugging.
* @param deep
* @param maxDeep
*/
public void printHiLo(int deep, int maxDeep) {
if (type == TYPE_ROUTECONTAINER) {
if (deep < maxDeep) {
System.out.print(":");
if (t1 == null) {
System.out.print("(empty)");
} else {
t1.printHiLo(deep + 1, maxDeep);
}
System.out.print("-");
if (t2 == null) {
System.out.print("(empty)");
} else {
t2.printHiLo(deep + 1, maxDeep);
}
System.out.print(":");
}
if (deep == maxDeep) {
System.out.print("((C)" + idxMin + "/" + idxMax + ")");
}
} else if (type == TYPE_ROUTEDATA) {
if (deep == maxDeep) {
System.out.print("((D" + fid + ")" + idxMin + "/" + idxMax + ")");
}
} else {
System.out.print(" type(" + type + ")");
}
}
/**
* Recalculates the idxMin and idxMax on RouteContainerTiles and RouteDataTiles
* @return
*/
public HiLo calcHiLo() {
if (type == TYPE_ROUTEDATA) {
HiLo retHiLo1 = new HiLo();
if (routeNodes != null) {
for (RouteNode rn: routeNodes) {
retHiLo1.extend(rn.id);
}
}
idxMin = retHiLo1.lo;
idxMax = retHiLo1.hi;
return retHiLo1;
} else if (type == TYPE_ROUTECONTAINER) {
HiLo retHiLo = new HiLo();
if (t1 != null) {
retHiLo.extend(t1.calcHiLo());
}
if (t2 != null) {
retHiLo.extend(t2.calcHiLo());
}
idxMin = retHiLo.lo;
idxMax = retHiLo.hi;
return retHiLo;
} else if (type == TYPE_EMPTY) {
return new HiLo();
} else {
throw new Error("Wrong type of tile in " + this);
}
}
/**
* @param path
* @throws IOException
*/
public void writeConnections(String path, HashMap<Long,
TurnRestriction> turnRestrictions) throws IOException {
if (t1 != null) {
t1.writeConnections(path, turnRestrictions);
// System.out.println("resolve T1 with " + idxMin + " to "+ idxMax);
}
if (t2 != null) {
t2.writeConnections(path, turnRestrictions);
// System.out.println("resolve T2 with " + idxMin + " to "+ idxMax);
}
if (routeNodes != null) {
//System.out.println("Write Routenodes " + fid + " nodes " +
// routeNodes.size() +" with " + idxMin + " to " + idxMax);
FileOutputStream cfo = FileTools.createFileOutputStream(
path + "/c/" + fid + ".d");
DataOutputStream cds = new DataOutputStream(new BufferedOutputStream(cfo));
FileOutputStream fo = FileTools.createFileOutputStream(
path + "/t" + zl + "/" + fid + ".d");
DataOutputStream nds = new DataOutputStream(new BufferedOutputStream(fo));
// write out the number of mainStreetNet RouteNodes
nds.writeShort(numMainStreetRouteNodes);
// write out the number of normalStreetNet RouteNodes
nds.writeShort(routeNodes.size() - numMainStreetRouteNodes);
cds.writeInt(minConnectionId);
short countTurnRestrictions[] = new short[2];
TurnRestriction turnWrite = null;
boolean hasTurnRestriction = false;
boolean isOnMainStreetNet = false;
int connected2 = 0;
// count how many turn restrictions we will write for this tile
// turn restrictions at mainStreetNet routeNodes are counted in the first loop,
// in the second loop the remaining ones
for (int writeStreetNets = 0; writeStreetNets <= 1; writeStreetNets++) {
for (RouteNode n : routeNodes) {
isOnMainStreetNet = n.isOnMainStreetNet();
if (writeStreetNets == 0 && isOnMainStreetNet
||
writeStreetNets > 0 && !isOnMainStreetNet
) {
turnWrite = turnRestrictions.get(n.node.id);
while (turnWrite != null) {
if (turnWrite.isComplete()) {
countTurnRestrictions[writeStreetNets]++;
}
turnWrite = turnWrite.nextTurnRestrictionAtThisNode;
}
}
}
// write counter how many turn restrictions are in this tile in mainstreet/normalNet
nds.writeShort(countTurnRestrictions[writeStreetNets]);
}
// write mainStreetNet routeNodes / turn restrictions in the first loop,
// in the second loop the remaining ones
for (int writeStreetNets = 0; writeStreetNets <= 1; writeStreetNets++) {
for (RouteNode n : routeNodes) {
isOnMainStreetNet = n.isOnMainStreetNet();
if (writeStreetNets == 0 && isOnMainStreetNet
||
writeStreetNets > 0 && !isOnMainStreetNet
) {
nds.writeFloat(MyMath.degToRad(n.node.lat));
nds.writeFloat(MyMath.degToRad(n.node.lon));
//nds.writeInt(cds.size());
hasTurnRestriction = false;
turnWrite = turnRestrictions.get(n.node.id);
while (turnWrite != null) {
if (turnWrite.isComplete()) {
countTurnRestrictions[writeStreetNets]++;
hasTurnRestriction = true;
}
turnWrite = turnWrite.nextTurnRestrictionAtThisNode;
}
connected2 = n.getConnected().length;
if (hasTurnRestriction) {
// Write indicator that this route node has turn restrictions attached
connected2 |= RouteNode.CS_FLAG_HASTURNRESTRICTIONS;
}
if (n.node.isTrafficSignalsRouteNode()) {
// Write indicator that this route node is at traffic lights
connected2 |= RouteNode.CS_FLAG_TRAFFICSIGNALS_ROUTENODE;
}
nds.writeByte((byte) connected2);
byte routeNodeWayFlags = 0;
for (Connection c : n.getConnected()) {
minConnectionId ++;
routeNodeWayFlags |= c.connTravelModes;
cds.writeInt(c.to.id);
// 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);
}
// count in which travel modes route node is used
for (int i = 0; i < TravelModes.travelModeCount; i++) {
if ( (routeNodeWayFlags & (1<<i)) != 0) {
TravelModes.getTravelMode(i).numRouteNodes++;
}
}
} // end of street net condition
} // end of routeNodes loop
// attach turn restrictions at the end of the mainstreet / normal street node data
for (RouteNode n : routeNodes) {
isOnMainStreetNet = n.isOnMainStreetNet();
if (writeStreetNets == 0 && isOnMainStreetNet
||
writeStreetNets > 0 && !isOnMainStreetNet
) {
turnWrite = turnRestrictions.get(n.node.id);
while (turnWrite != null) {
if (turnWrite.isComplete()) {
nds.writeInt(turnWrite.viaRouteNode.id);
// just a prevention against renumbered RouteNodes
if (turnWrite.viaRouteNode.id != n.id) {
System.out.println("RouteNode ID mismatch for turn restrictions");
}
nds.writeInt(turnWrite.fromRouteNode.id);
nds.writeInt(turnWrite.toRouteNode.id);
nds.writeByte(turnWrite.affectedTravelModes);
nds.writeByte(turnWrite.flags);
if (turnWrite.isViaTypeWay()) {
nds.writeByte(turnWrite.additionalViaRouteNodes.length);
for (RouteNode rn:turnWrite.additionalViaRouteNodes) {
nds.writeInt(rn.id);
}
}
}
// System.out.println(turnWrite.toString(OxParser.getWayHashMap()));
turnWrite = turnWrite.nextTurnRestrictionAtThisNode;
}
} // end of street net condition for turn restrictions
} // end of routeNodes loop for turn restrictions
} // end of writeStreetNets loop
/**
* Write a special marker, so that we can detect if something
* went wrong with decoding the variable length encoding
*/
cds.writeInt(0xdeadbeaf);
nds.close();
cds.close();
}
}
}