/*
* Copyright (C) 2007 Steve Ratcliffe
*
* 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.
*
*
* Author: Steve Ratcliffe
* Create date: Jan 5, 2008
*/
package uk.me.parabola.imgfmt.app.net;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.app.BitWriter;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.app.trergn.Polyline;
import uk.me.parabola.log.Logger;
/**
* A road definition. This ties together all segments of a single road
* and provides street address information.
*
* This corresponds to an entry in NET1, which is linked with the
* polylines making up this road in RGN. Links to RGN are written
* via RoadIndex, while writing links from RGN to NET1 is delayed
* via setOffsetWriter.
*
* If the map includes routing, the NET1 record also points to
* a NOD2 record, written by writeNod2.
*
* Edges in the routing graph ("arcs") link to the corresponding
* road via the RoadDef, storing the NET1 offset via TableA,
* which also includes some road information.
*
* @author Elrond
* @author Steve Ratcliffe
* @author Robert Vollmert
*/
public class RoadDef
implements Comparable<RoadDef
> {
private static final Logger log =
Logger.
getLogger(RoadDef.
class);
public static final int NET_FLAG_NODINFO = 0x40
;
public static final int NET_FLAG_ADDRINFO = 0x10
;
private static final int NET_FLAG_UNK1 = 0x04
; // lock on road?
private static final int NET_FLAG_ONEWAY = 0x02
;
private static final int NOD2_FLAG_UNK = 0x01
;
private static final int NOD2_FLAG_EXTRA_DATA = 0x80
;
// first byte of Table A info in NOD 1
private static final int TABA_FLAG_TOLL = 0x80
;
private static final int TABA_MASK_CLASS = 0x70
;
private static final int TABA_FLAG_ONEWAY = 0x08
;
private static final int TABA_MASK_SPEED = 0x07
;
// second byte: access flags - order must correspond to constants
// in RoadNetwork - bits 0x08, 0x80 missing (purpose unknown)
private static final int[] ACCESS =
{
0x8000,
// emergency (net pointer bit 31)
0x4000,
// delivery (net pointer bit 30)
0x0001,
// car
0x0002,
// bus
0x0004,
// taxi
0x0010,
// foot
0x0020,
// bike
0x0040,
// truck
0x0008,
// carpool
};
// the offset in Nod2 of our Nod2 record
private int offsetNod2
;
// the offset in Net1 of our Net1 record
private int offsetNet1
;
/*
* Everything that's relevant for writing to NET1.
*/
private int netFlags = NET_FLAG_UNK1
;
// The road length units may be affected by other flags in the header as
// there is doubt as to the formula.
private int roadLength
;
// There can be up to 4 labels for the same road.
private static final int MAX_LABELS =
4;
private final Label[] labels =
new Label[MAX_LABELS
];
private int numlabels
;
private final SortedMap<Integer,
List<RoadIndex
>> roadIndexes =
new TreeMap<Integer,
List<RoadIndex
>>();
private City city
;
private Zip zip
;
private boolean paved =
true;
private boolean ferry
;
private boolean roundabout
;
private boolean linkRoad
;
private boolean synthesised
;
private boolean flareCheck
;
private boolean deadEndCheck
;
private Set<String> messageIssued
;
private final List<Offset
> rgnOffsets =
new ArrayList<Offset
>(4);
/*
* Everything that's relevant for writing out Nod 2.
*/
// This is the node associated with the road. I'm not certain about how
// this works, but in NOD2 each road has a reference to only one node.
// This is that node.
private RouteNode node
;
// the first point in the road is a node (the above routing node)
private boolean startsWithNode =
true;
// number of nodes in the road
private int nnodes
;
// always appears to be set
private int nod2Flags = NOD2_FLAG_UNK
;
// The data for Table A
private int tabAInfo
;
private int tabAAccess
;
// for diagnostic purposes
private final long id
;
private final String name
;
private List<Numbers
> numbersList
;
public RoadDef
(long id,
String name
) {
this.
id = id
;
this.
name = name
;
}
/**
* A constructor that is used when reading a file and you know the NET1 offset. When writing
* the offsetNet1 field is filled in during the writing process.
* @param id Road id
* @param net1offset The offset in the road defs section of the NET file.
* @param name The main of the road.
*/
public RoadDef
(long id,
int net1offset,
String name
) {
this.
id = id
;
this.
offsetNet1 = net1offset
;
this.
name = name
;
}
// for diagnostic purposes
public String toString
() {
// assumes id is an OSM id
String browseURL =
"http://www.openstreetmap.org/browse/way/" + id
;
if(getName
() !=
null)
return "(" + getName
() +
", " + browseURL +
")";
else
return "(" + browseURL +
")";
}
public String getName
() {
if (name
!=
null)
return name
;
if (labels
[0] !=
null)
return labels
[0].
getText();
return null;
}
public long getId
() {
return id
;
}
/**
* This is for writing to NET1.
* @param writer A writer that is positioned within NET1.
*/
void writeNet1
(ImgFileWriter writer,
int numCities,
int numZips
) {
if (numlabels ==
0)
return;
assert numlabels
> 0;
offsetNet1 = writer.
position();
NumberPreparer numbers =
null;
if (numbersList
!=
null) {
numbers =
new NumberPreparer
(numbersList
);
numbers.
fetchBitStream();
if (!numbers.
isValid()){
numbers =
null;
log.
warn("Invalid housenumbers in",
this.
toString());
}
}
writeLabels
(writer
);
if (numbers
!=
null) { // TODO combine if
if (numbers.
getSwapped())
netFlags |= 0x20
; // swapped default; left=even, right=odd
}
writer.
put((byte) netFlags
);
writer.
put3(roadLength
);
int maxlevel = writeLevelCount
(writer
);
writeLevelDivs
(writer, maxlevel
);
if((netFlags
& NET_FLAG_ADDRINFO
) !=
0) {
writer.
put((byte)0); // unknown (nearly always zero)
int code = 0xe8
; // zip and city present
if(city ==
null)
code |= 0x10
; // no city
if(zip ==
null)
code |= 0x04
; // no zip
if (numbers
!=
null) {
code
&= ~0xc0
;
if (numbers.
fetchBitStream().
getLength() > 255)
code |=
1;
}
writer.
put((byte)code
);
if(zip
!=
null) {
char zipIndex =
(char)zip.
getIndex();
if(numZips
> 255)
writer.
putChar(zipIndex
);
else
writer.
put((byte)zipIndex
);
}
if(city
!=
null) {
char cityIndex =
(char)city.
getIndex();
if(numCities
> 255)
writer.
putChar(cityIndex
);
else
writer.
put((byte)cityIndex
);
}
if (numbers
!=
null) {
BitWriter bw = numbers.
fetchBitStream();
if (bw.
getLength() > 255)
writer.
putChar((char) bw.
getLength());
else
writer.
put((byte) bw.
getLength());
writer.
put(bw.
getBytes(),
0, bw.
getLength());
}
}
if (hasNodInfo
()) {
// This is the offset of an entry in NOD2
int val = offsetNod2
;
if (val
< 0x7fff
) {
writer.
put((byte) 1);
writer.
putChar((char) val
);
} else {
writer.
put((byte) 2);
writer.
put3(val
);
}
}
}
private void writeLabels
(ImgFileWriter writer
) {
for (int i =
0; i
< numlabels
; i++
) {
Label l = labels
[i
];
int ptr = l.
getOffset();
if (i ==
(numlabels-
1))
ptr |= 0x800000
;
writer.
put3(ptr
);
}
}
public void putSortedRoadEntry
(ImgFileWriter writer,
Label label
) {
for(int i =
0; i
< labels.
length && labels
[i
] !=
null; ++i
) {
if(labels
[i
].
equals(label
)) {
writer.
put3((i
<< 22) | offsetNet1
);
return;
}
}
}
private int writeLevelCount
(ImgFileWriter writer
) {
int maxlevel = getMaxZoomLevel
();
for (int i =
0; i
<= maxlevel
; i++
) {
List<RoadIndex
> l = roadIndexes.
get(i
);
int b =
(l ==
null) ? 0 : l.
size();
assert b
< 0x80 :
"too many polylines at level " + i
;
if (i == maxlevel
)
b |= 0x80
;
writer.
put((byte) b
);
}
return maxlevel
;
}
private void writeLevelDivs
(ImgFileWriter writer,
int maxlevel
) {
for (int i =
0; i
<= maxlevel
; i++
) {
List<RoadIndex
> l = roadIndexes.
get(i
);
if (l
!=
null) {
for (RoadIndex ri : l
)
ri.
write(writer
);
}
}
}
public void addLabel
(Label l
) {
int i
;
for (i =
0; i
< MAX_LABELS
&& labels
[i
] !=
null; ++i
) {
if (l.
equals(labels
[i
])) {
// label already present
return;
}
}
if (i
< MAX_LABELS
) {
labels
[i
] = l
;
++numlabels
;
}
else
log.
warn(this.
toString() +
" discarding extra label (already have " + MAX_LABELS +
")");
}
public Label[] getLabels
() {
return labels
;
}
/**
* Add a polyline to this road.
*
* References to these are written to NET. At a given zoom
* level, we're writing these in the order we get them,
* which possibly needs to be the order the segments have
* in the road.
*/
public void addPolylineRef
(Polyline pl
) {
if(log.
isDebugEnabled())
log.
debug("adding polyline ref",
this, pl.
getSubdiv());
int level = pl.
getSubdiv().
getZoom().
getLevel();
List<RoadIndex
> l = roadIndexes.
get(level
);
if (l ==
null) {
l =
new ArrayList<RoadIndex
>(4);
roadIndexes.
put(level, l
);
}
int s = l.
size();
if (s
> 0)
l.
get(s-
1).
getLine().
setLastSegment(false);
l.
add(new RoadIndex
(pl
));
}
private int getMaxZoomLevel
() {
return roadIndexes.
lastKey();
}
public boolean connectedTo
(RoadDef other
) {
List<RoadIndex
> l = roadIndexes.
get(0);
if(l ==
null)
return false;
List<RoadIndex
> ol = other.
roadIndexes.
get(0);
if(ol ==
null)
return false;
for(RoadIndex ri : l
)
for(RoadIndex ori : ol
)
if(ri.
getLine().
sharesNodeWith(ori.
getLine()))
return true;
return false;
}
public boolean sameDiv
(RoadDef other
) {
return getStartSubdivNumber
() == other.
getStartSubdivNumber();
}
public int getStartSubdivNumber
() {
Integer key = roadIndexes.
firstKey();
return roadIndexes.
get(key
).
get(0).
getLine().
getSubdiv().
getNumber();
}
/**
* Set the road length (in meters).
*/
public void setLength
(double l
) {
// XXX: this is from test.display.NetDisplay, possibly varies
roadLength =
(int) l /
2;
}
public boolean hasHouseNumbers
() {
return numbersList
!=
null && !numbersList.
isEmpty();
}
/*
* Everything that's relevant for writing to RGN.
*/
class Offset
{
final int position
;
final int flags
;
Offset
(int position,
int flags
) {
this.
position = position
;
this.
flags = flags
;
}
int getPosition
() {
return position
;
}
int getFlags
() {
return flags
;
}
}
/**
* Add a target location in the RGN section where we should write the
* offset of this road def when it is written to NET.
*
* @param position The offset in RGN.
* @param flags The flags that should be set.
*/
public void addOffsetTarget
(int position,
int flags
) {
rgnOffsets.
add(new Offset
(position, flags
));
}
/**
* Write into the RGN the offset in net1 of this road.
* @param rgn A writer for the rgn file.
*/
void writeRgnOffsets
(ImgFileWriter rgn
) {
if (offsetNet1
>= 0x400000
)
throw new MapFailedException
("Overflow of the NET1. The tile ("
+ log.
threadTag()
+
") must be split so that there are fewer roads in it");
for (Offset off : rgnOffsets
) {
rgn.
position(off.
getPosition());
rgn.
put3(offsetNet1 | off.
getFlags());
}
}
private boolean internalNodes =
true;
/**
* Does the road have any nodes besides start and end?
*
* This affects whether we need to write extra bits in
* the bitstream in RGN.
*/
public boolean hasInternalNodes
() {
return internalNodes
;
}
public void setInternalNodes
(boolean n
) {
internalNodes = n
;
}
/**
* Set the routing node associated with this road.
*
* This implies that the road has an entry in NOD 2
* which will be pointed at from NET 1.
*/
public void setNode
(RouteNode node
) {
netFlags |= NET_FLAG_NODINFO
;
this.
node = node
;
}
private boolean hasNodInfo
() {
return (netFlags
& NET_FLAG_NODINFO
) !=
0;
}
public void setStartsWithNode
(boolean s
) {
startsWithNode = s
;
}
public void setNumNodes
(int n
) {
nnodes = n
;
}
public void setNumbersList
(List<Numbers
> numbersList
) {
if (numbersList
!=
null && !numbersList.
isEmpty()) {
this.
numbersList = numbersList
;
netFlags |= NET_FLAG_ADDRINFO
;
}
}
/**
* Write this road's NOD2 entry.
*
* Stores the writing position to be able to link here
* from NET 1 later.
*
* @param writer A writer positioned in NOD2.
*/
public void writeNod2
(ImgFileWriter writer
) {
if (!hasNodInfo
())
return;
log.
debug("writing nod2");
offsetNod2 = writer.
position();
writer.
put((byte) nod2Flags
);
writer.
put3(node.
getOffsetNod1()); // offset in nod1
// this is related to the number of nodes, but there
// is more to it...
// For now, shift by one if the first node is not a
// routing node. Supposedly, other holes are also
// possible.
// This might be unnecessary if we just make sure
// that every road starts with a node.
int nbits = nnodes
;
if (!startsWithNode
)
nbits++
;
writer.
putChar((char) nbits
);
boolean[] bits =
new boolean[nbits
];
for (int i =
0; i
< bits.
length; i++
)
bits
[i
] =
true;
if (!startsWithNode
)
bits
[0] =
false;
for (int i =
0; i
< bits.
length; i +=
8) {
int b =
0;
for (int j =
0; j
< 8 && j
< bits.
length - i
; j++
)
if (bits
[i+j
])
b |=
1 << j
;
writer.
put((byte) b
);
}
}
/*
* Everything that's relevant for writing out Table A.
*
* Storing this info in the RoadDef means that each
* arc gets the same version of the below info, which
* makes sense for the moment considering polish format
* doesn't provide for different speeds and restrictions
* for segments of roads.
*/
/**
* Return the offset of this road's NET1 entry. Assumes
* writeNet1() has been called.
*/
public int getOffsetNet1
() {
return offsetNet1
;
}
public void setToll
() {
tabAInfo |= TABA_FLAG_TOLL
;
}
public void setNoThroughRouting
() {
tabAAccess |= 0x80
;
}
public void setAccess
(boolean[] access
) {
for (int i =
0; i
< access.
length; i++
)
if (access
[i
])
tabAAccess |= ACCESS
[i
];
}
public int getTabAInfo
() {
return tabAInfo
;
}
public int getTabAAccess
() {
return tabAAccess
;
}
/*
* These affect various parts.
*/
private int roadClass = -
1;
// road class that goes in various places (really?)
public void setRoadClass
(int roadClass
) {
assert roadClass
< 0x08
;
/* for RouteArcs to get as their "destination class" */
this.
roadClass = roadClass
;
/* for Table A */
int shifted =
(roadClass
<< 4) & 0xff
;
tabAInfo |= shifted
;
/* for NOD 2 */
nod2Flags |= shifted
;
}
public int getRoadClass
() {
assert roadClass
>=
0 :
"roadClass not set";
return roadClass
;
}
public void setSpeed
(int speed
) {
assert speed
< 0x08
;
/* for Table A */
tabAInfo |= speed
;
/* for NOD 2 */
nod2Flags |=
(speed
<< 1);
}
public int getRoadSpeed
() {
return tabAInfo
& 7;
}
public void setOneway
() {
tabAInfo |= TABA_FLAG_ONEWAY
;
netFlags |= NET_FLAG_ONEWAY
;
}
public boolean isOneway
() {
return (netFlags
& NET_FLAG_ONEWAY
) !=
0;
}
public void setCity
(City city
) {
this.
city = city
;
netFlags |= NET_FLAG_ADDRINFO
;
}
public void setZip
(Zip zip
) {
this.
zip = zip
;
netFlags |= NET_FLAG_ADDRINFO
;
}
public int compareTo
(RoadDef other
) {
// sort by city name - this is used to group together
// roads that have been split into segments
if(other ==
this)
return 0;
// TODO: look at what this is doing...
if(city
!=
null && other.
city !=
null)
return city.
getName().
compareTo(other.
city.
getName());
if (hashCode
() == other.
hashCode())
return 0;
else if (hashCode
() < other.
hashCode())
return -
1;
else
return 0;
}
public City getCity
() {
return city
;
}
public boolean paved
() {
return paved
;
}
public void paved
(boolean p
) {
paved = p
;
}
public void ferry
(boolean f
) {
ferry = f
;
}
public boolean ferry
() {
return ferry
;
}
public void setRoundabout
(boolean r
) {
roundabout = r
;
}
public boolean isRoundabout
() {
return roundabout
;
}
public void setLinkRoad
(boolean lr
) {
linkRoad = lr
;
}
public boolean isLinkRoad
() {
return linkRoad
;
}
public void setSynthesised
(boolean s
) {
synthesised = s
;
}
public boolean isSynthesised
() {
return synthesised
;
}
public void doFlareCheck
(boolean fc
) {
flareCheck = fc
;
}
public boolean doFlareCheck
() {
return flareCheck
;
}
public void doDeadEndCheck
(boolean dec
) {
deadEndCheck = dec
;
}
public boolean doDeadEndCheck
() {
return deadEndCheck
;
}
public boolean messagePreviouslyIssued
(String key
) {
if(messageIssued ==
null)
messageIssued =
new HashSet<String>();
boolean previouslyIssued = messageIssued.
contains(key
);
messageIssued.
add(key
);
return previouslyIssued
;
}
}