/*
* Copyright (C) 2008
*
* 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.
*
* 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.
*
* Create date: 07-Jul-2008
*/
package uk.me.parabola.imgfmt.app.net;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import static uk.me.parabola.imgfmt.app.net.AccessTagsAndBits.*;
/**
* A restriction in the routing graph.
*
* A routing restriction has two or more arcs.
* The first arc is the "from" arc, the last is the "to" arc,
* and other arc is a "via" arc.
*
* A from-to-via restriction says you can't go along arc "to"
* if you came to node to.getSource() == from.getSource()
* via the inverse arc of "from". We're using the inverse of
* "from" since that has the information we need for writing
* the Table C entry.
*
* @author Robert Vollmert
*/
public class RouteRestriction {
//private static final Logger log = Logger.getLogger(RouteRestriction.class);
// first three bytes of the header -- might specify the type of restriction
// and when it is active
private static final byte RESTRICTION_TYPE = 0x05; // 0x07 spotted, meaning?
// To specify that a node is given by a relative offset instead
// of an entry to Table B.
private static final int F_INTERNAL = 0x8000;
// the arcs
private final List<RouteArc> arcs;
private final RouteNode viaNode;
// offset in Table C
private byte offsetSize;
private int offsetC;
// last restriction in a node
private boolean last;
// mask that specifies which vehicle types the restriction doesn't apply to
private final byte exceptMask;
private final byte flags; // meaning of bits 0x01 and 0x10 are not clear
private final static byte F_EXCEPT_FOOT = 0x02;
private final static byte F_EXCEPT_EMERGENCY = 0x04;
private final static byte F_MORE_EXCEPTIONS = 0x08;
private final static byte EXCEPT_CAR = 0x01;
private final static byte EXCEPT_BUS = 0x02;
private final static byte EXCEPT_TAXI = 0x04;
private final static byte EXCEPT_DELIVERY = 0x10;
private final static byte EXCEPT_BICYCLE = 0x20;
private final static byte EXCEPT_TRUCK = 0x40;
/**
*
* @param viaNode the node to which this restriction is related
* @param traffArcs the arcs that describe the "forbidden" path
* @param mkgmapExceptMask the exception mask in the mkgmap format
*/
public RouteRestriction(RouteNode viaNode, List<RouteArc> traffArcs, byte mkgmapExceptMask) {
this.viaNode = viaNode;
this.arcs = new ArrayList<>(traffArcs);
for (int i = 0; i < arcs.size(); i++){
RouteArc arc = arcs.get(i);
assert arc.getDest() != viaNode;
}
byte flags = 0;
if ((mkgmapExceptMask & FOOT) != 0)
flags |= F_EXCEPT_FOOT;
if ((mkgmapExceptMask & EMERGENCY) != 0)
flags |= F_EXCEPT_EMERGENCY;
exceptMask = translateExceptMask(mkgmapExceptMask);
if(exceptMask != 0)
flags |= F_MORE_EXCEPTIONS;
int numArcs = arcs.size();
assert numArcs < 8;
flags |= ((numArcs) << 5);
this.flags = flags;
}
/**
* Translate the mkgmap internal representation of vehicles to the one used in the img format
* @param mkgmapExceptMask
* @return
*/
private byte translateExceptMask(byte mkgmapExceptMask) {
byte mask = 0;
if ((mkgmapExceptMask & CAR) != 0)
mask |= EXCEPT_CAR;
if ((mkgmapExceptMask & BUS) != 0)
mask |= EXCEPT_BUS;
if ((mkgmapExceptMask & TAXI) != 0)
mask |= EXCEPT_TAXI;
if ((mkgmapExceptMask & DELIVERY) != 0)
mask |= EXCEPT_DELIVERY;
if ((mkgmapExceptMask & BIKE) != 0)
mask |= EXCEPT_BICYCLE;
if ((mkgmapExceptMask & TRUCK) != 0)
mask |= EXCEPT_TRUCK;
return mask;
}
private int calcOffset(RouteNode node, int tableOffset) {
int offset = tableOffset - node.getOffsetNod1();
assert offset >= 0 : "node behind start of tables";
assert offset < 0x8000 : "node offset too large";
return offset | F_INTERNAL;
}
public List<RouteArc> getArcs(){
return arcs;
}
/**
* Writes a Table C entry with 3 or more nodes.
*
* @param writer The writer.
* @param tableOffset The offset in NOD 1 of the tables area.
*
*/
public void write(ImgFileWriter writer, int tableOffset) {
writer.put(RESTRICTION_TYPE);
writer.put(flags);
writer.put((byte)0); // meaning ?
if(exceptMask != 0)
writer.put(exceptMask);
int numArcs = arcs.size();
int[] offsets = new int[numArcs+1];
int pos = 0;
boolean viaWritten = false;
for (int i = 0; i < numArcs; i++){
RouteArc arc = arcs.get(i);
// the arcs must have a specific order and direction
// first arc: dest is from node , last arc: dest is to node
// if there only two arcs, both will have the via node as source node.
// For more n via nodes, the order is like this:
// from <- via(1) <- via(2) <- ... <- this via node -> via( n-1) -> via(n) -> to
if (arc.isInternal())
offsets[pos++] = calcOffset(arc.getDest(), tableOffset);
else
offsets[pos++] = arc.getIndexB();
if (arc.getSource() == viaNode){
// there will be two nodes with source node = viaNode, but we write the source only once
if (!viaWritten){
offsets[pos++] = calcOffset(viaNode, tableOffset);
viaWritten = true;
}
}
}
for (int offset : offsets)
writer.putChar((char) offset);
for (RouteArc arc: arcs)
writer.put(arc.getIndexA());
}
/**
* Write this restriction's offset within Table C into a node record.
*/
public void writeOffset(ImgFileWriter writer) {
assert 0 < offsetSize && offsetSize <= 2 : "illegal offset size";
int offset = offsetC;
if (offsetSize == 1) {
assert offset < 0x80;
if (last)
offset |= 0x80;
writer.put((byte) offset);
} else {
assert offset < 0x8000;
if (last)
offset |= 0x8000;
writer.putChar((char) offset);
}
}
/**
* Size in bytes of the Table C entry.
*/
public int getSize() {
int size = 3; // header length
if(exceptMask != 0)
++size;
size += arcs.size() + (arcs.size()+1) * 2;
return size;
}
public void setOffsetC(int offsetC) {
this.offsetC = offsetC;
}
public int getOffsetC() {
return offsetC;
}
public void setOffsetSize(byte size) {
offsetSize = size;
}
public void setLast() {
last = true;
}
}