/* * 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.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.BitSet; import java.util.Collections; 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; import uk.me.parabola.mkgmap.general.CityInfo; import uk.me.parabola.mkgmap.general.ZipCodeInfo; /** * 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 { 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; just documentation // 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; just documentation private static final int TABA_FLAG_ONEWAY = 0x08; // private static final int TABA_MASK_SPEED = 0x07; just documentation private static final int TABAACCESS_FLAG_CARPOOL = 0x0008; private static final int TABAACCESS_FLAG_NOTHROUGHROUTE = 0x0080; // second byte: access flags, bits 0x08, 0x80 are set separately private static final int TABAACCESS_FLAG_NO_EMERGENCY = 0x8000; private static final int TABAACCESS_FLAG_NO_DELIVERY = 0x4000; private static final int TABAACCESS_FLAG_NO_CAR = 0x0001; private static final int TABAACCESS_FLAG_NO_BUS = 0x0002; private static final int TABAACCESS_FLAG_NO_TAXI = 0x0004; private static final int TABAACCESS_FLAG_NO_FOOT = 0x0010; private static final int TABAACCESS_FLAG_NO_BIKE = 0x0020; private static final int TABAACCESS_FLAG_NO_TRUCK = 0x0040; // true if road should not be added to NOD private boolean skipAddToNOD; // 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 allowed vehicles in mkgmap internal format private byte mkgmapAccess; // 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<>(); private boolean paved = true; private boolean ferry; private boolean roundabout; private boolean linkRoad; private boolean synthesised; private boolean flareCheck; private Set<String> messageIssued; private final List<Offset> rgnOffsets = new ArrayList<>(); // for the NOD2 bit stream private BitSet nod2BitSet; /* * 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; private List<City> cityList; private List<Zip> zipList; private int nodeCount; 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].toString(); 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; Zip zip = getZips().isEmpty() ? null : getZips().get(0); City city = getCities().isEmpty() ? null: getCities().get(0); offsetNet1 = writer.position(); NumberPreparer numbers = null; if (numbersList != null) { numbers = new NumberPreparer(numbersList, zip, city, numCities, numZips); if (!numbers.prepare()){ numbers = null; log.warn("Invalid housenumbers in",this.toString()); } } writeLabels(writer); if (numbers != null && 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) { nodeCount--; if (nodeCount + 2 != nnodes){ log.error("internal error? The nodeCount doesn't match value calculated by RoadNetWork:",this); } writer.put((byte) (nodeCount & 0xff)); // lo bits of node count int code = ((nodeCount >> 8) & 0x3); // top bits of node count int len, flag; ByteArrayOutputStream zipBuf = null, cityBuf = null; len = (numbers == null) ? 0: numbers.zipWriter.getBuffer().size(); if (len > 0){ zipBuf = numbers.zipWriter.getBuffer(); flag = (len > 255) ? 1 : 0; } else flag = (zip == null) ? 3 : 2; code |= flag << 2; len = (numbers == null) ? 0: numbers.cityWriter.getBuffer().size(); if (len > 0){ cityBuf = numbers.cityWriter.getBuffer(); flag = (len > 255) ? 1 : 0; } else flag = (city == null) ? 3 : 2; code |= flag << 4; len = (numbers == null) ? 0 : numbers.fetchBitStream().getLength(); if (len > 0){ flag = (len > 255) ? 1 : 0; } else flag = 3; code |= flag << 6; writer.put((byte)code); // System.out.printf("%d %d %d\n", (code >> 2 & 0x3), (code >> 4 & 0x3), (code >> 6 & 0x3)); if (zipBuf != null){ len = zipBuf.size(); if (len > 255) writer.putChar((char) len); else writer.put((byte) len); writer.put(zipBuf.toByteArray()); } else { if(zip != null) { char zipIndex = (char)zip.getIndex(); if(numZips > 255) writer.putChar(zipIndex); else writer.put((byte)zipIndex); } } if (cityBuf != null){ len = cityBuf.size(); if (len > 255) writer.putChar((char) len); else writer.put((byte) len); writer.put(cityBuf.toByteArray()); } else { 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 must(!) 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<>(); roadIndexes.put(level, l); } l.add(new RoadIndex(pl)); if (level == 0) { nodeCount += pl.getNodeCount(hasHouseNumbers()); } } 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 lenInMeter) { roadLength = NODHeader.metersToRaw(lenInMeter); } 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; /** * Does the road have any nodes besides start and end? * These can be number nodes or routing nodes. * 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) { if (skipAddToNOD) return; netFlags |= NET_FLAG_NODINFO; this.node = node; } public RouteNode getNode(){ return 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; } } public List<Numbers> getNumbersList() { return numbersList; } /** * 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; if (skipAddToNOD){ // should not happen log.error("internal error: writeNod2 called for roaddef with skipAddToNOD=true"); 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. // If the road has house numbers, we count also // the number nodes, and these get a 0 in the bit stream. int nbits = nnodes; if (!startsWithNode) nbits++; writer.putChar((char) nbits); boolean[] bits = new boolean[nbits]; if (hasHouseNumbers()){ int off = startsWithNode ? 0 :1; for (int i = 0; i < bits.length; i++){ if (nod2BitSet.get(i)) bits[i+off] = true; } } else { 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; } /** * Flag that a toll must be payed when using this road. */ public void setToll() { tabAInfo |= TABA_FLAG_TOLL; } /** * Flag that the road has a carpool lane.<br> * Warning: This bit does not seem to work. Maybe it does not control * the carpool flag. */ public void setCarpoolLane() { tabAAccess |= TABAACCESS_FLAG_CARPOOL; } /** * Sets the flag that routing is allowed only if the route starts or * end on this road. */ public void setNoThroughRouting() { tabAAccess |= TABAACCESS_FLAG_NOTHROUGHROUTE; } /** * @return allowed vehicles in mkgmap format */ public byte getAccess() { return mkgmapAccess; } /** * Set allowed vehicles * @param mkgmapAccess bit mask in mkgmap format */ public void setAccess(byte mkgmapAccess) { this.mkgmapAccess = mkgmapAccess; // translate internal format to that used in TableA //clear the corresponding bits tabAAccess &= ~(0xc077); if (mkgmapAccess == (byte) 0xff) return; // all vehicles allowed if ((mkgmapAccess & AccessTagsAndBits.FOOT) == 0) tabAAccess |= TABAACCESS_FLAG_NO_FOOT; if ((mkgmapAccess & AccessTagsAndBits.BIKE) == 0) tabAAccess |=TABAACCESS_FLAG_NO_BIKE; if ((mkgmapAccess & AccessTagsAndBits.CAR) == 0) tabAAccess |=TABAACCESS_FLAG_NO_CAR; if ((mkgmapAccess & AccessTagsAndBits.DELIVERY) == 0) tabAAccess |=TABAACCESS_FLAG_NO_DELIVERY; if ((mkgmapAccess & AccessTagsAndBits.TRUCK) == 0) tabAAccess |=TABAACCESS_FLAG_NO_TRUCK; if ((mkgmapAccess & AccessTagsAndBits.BUS) == 0) tabAAccess |=TABAACCESS_FLAG_NO_BUS; if ((mkgmapAccess & AccessTagsAndBits.TAXI) == 0) tabAAccess |=TABAACCESS_FLAG_NO_TAXI; if ((mkgmapAccess & AccessTagsAndBits.EMERGENCY) == 0) tabAAccess |=TABAACCESS_FLAG_NO_EMERGENCY; } 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 addCityIfNotPresent(City city) { if (city == null){ log.error("trying to add null value to city list in road",this); return; } netFlags |= NET_FLAG_ADDRINFO; if (cityList == null){ cityList = new ArrayList<>(2); } if (cityList.contains(city) == false) cityList.add(city); } public void addZipIfNotPresent(Zip zip) { if (zip == null){ log.error("trying to add null value to zip list in road",this); return; } netFlags |= NET_FLAG_ADDRINFO; if (zipList == null){ zipList = new ArrayList<>(2); } if (zipList.contains(zip) == false) zipList.add(zip); } public List<City> getCities(){ if (cityList == null) return Collections.emptyList(); return cityList; } public List<Zip> getZips(){ if (zipList == null) return Collections.emptyList(); return zipList; } 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 boolean messagePreviouslyIssued(String key) { if(messageIssued == null) messageIssued = new HashSet<>(); boolean previouslyIssued = messageIssued.contains(key); messageIssued.add(key); return previouslyIssued; } public void setNod2BitSet(BitSet bs) { if (skipAddToNOD) return; nod2BitSet = bs; } public boolean skipAddToNOD() { return skipAddToNOD; } public void skipAddToNOD(boolean skip) { this.skipAddToNOD = skip; } public void resetImgData() { zipList = null; cityList = null; if (numbersList != null){ for (Numbers num : numbersList){ for (int side = 0; side < 2; side++){ boolean left = side == 0; CityInfo ci = num.getCityInfo(left); if (ci != null) ci.setImgCity(null); ZipCodeInfo z = num.getZipCodeInfo(left); if (z != null) z.setImgZip(null); } } } } }