/* * Copyright (C) 2009. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 or * 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. */ package uk.me.parabola.imgfmt.app.trergn; import java.util.ArrayList; import java.util.Collections; import java.util.List; import uk.me.parabola.imgfmt.Utils; import uk.me.parabola.imgfmt.app.BitReader; import uk.me.parabola.imgfmt.app.BufferedImgFileReader; import uk.me.parabola.imgfmt.app.Coord; import uk.me.parabola.imgfmt.app.CoordNode; import uk.me.parabola.imgfmt.app.ImgFileReader; import uk.me.parabola.imgfmt.app.ImgReader; import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.lbl.LBLFileReader; import uk.me.parabola.imgfmt.app.lbl.POIRecord; import uk.me.parabola.imgfmt.app.net.NETFileReader; import uk.me.parabola.imgfmt.app.net.RoadDef; import uk.me.parabola.imgfmt.fs.ImgChannel; import uk.me.parabola.log.Logger; import uk.me.parabola.util.EnhancedProperties; /** * The region file. Holds actual details of points and lines etc. * * This is the view of the file when it is being read. Use {@link RGNFile} * for writing the file. * * The main focus of mkgmap is creating files, there are plenty of applications * that read and display the data, reading is implemented only to the * extent required to support creating the various auxiliary files etc. * * @author Steve Ratcliffe */ public class RGNFileReader extends ImgReader { private static final Logger log = Logger.getLogger(RGNFileReader.class); private final RGNHeader rgnHeader; private LBLFileReader lblFile; private NETFileReader netFile; public RGNFileReader(ImgChannel chan) { rgnHeader = new RGNHeader(); setHeader(rgnHeader); setReader(new BufferedImgFileReader(chan)); rgnHeader.readHeader(getReader()); } public void config(EnhancedProperties props) { //config = props; } /** * Get a list of all points for the given subdivision. This includes * both the indexed points section and the points section. * * The numbering of the points carries through the sections. * @param sd The subdivision that we are interested in. * @return A list of all points for the subdiv. */ public List<Point> pointsForSubdiv(Subdivision sd, boolean withExtType) { ArrayList<Point> list = new ArrayList<Point>(); if (sd.hasIndPoints() || sd.hasPoints()){ RgnOffsets rgnOffsets = getOffsets(sd); // Even though the indexed points are after the points, the numbering // starts with 1 for the first indexed point and carries on into the // points section. fetchPointsCommon(sd, rgnOffsets.getIndPointStart(), rgnOffsets.getIndPointEnd(), list); fetchPointsCommon(sd, rgnOffsets.getPointStart(), rgnOffsets.getPointEnd(), list); } if (withExtType && sd.getExtTypePointsSize() > 0) fetchPointsCommonExtType(sd, rgnHeader.getExtTypePointsOffset() + sd.getExtTypePointsOffset(), sd.getExtTypePointsSize(), list); return list; } /** * The indexed points and the points sections are both read just the same. */ private void fetchPointsCommon(Subdivision sd, long start, long end, List<Point> points) { position(start); ImgFileReader reader = getReader(); int number = points.size() + 1; while (position() < end) { Point p = new Point(sd); byte t = reader.get(); int val = reader.getu3(); boolean hasSubtype = false; if ((val & 0x800000) != 0) hasSubtype = true; boolean hasPoi = false; if ((val & 0x400000) != 0) hasPoi = true; Label l; int labelOffset = val & 0x3fffff; if (hasPoi) { POIRecord record = lblFile.fetchPoi(labelOffset); if (record != null) { l = record.getNameLabel(); p.setPOIRecord(record); } else l = lblFile.fetchLabel(0); } else { l = lblFile.fetchLabel(labelOffset); } p.setLabel(l); p.setDeltaLong((short)reader.getChar()); p.setDeltaLat((short)reader.getChar()); if (hasSubtype) { byte st = reader.get(); p.setType(((t & 0xff) << 8) | (st & 0xff)); //p.setHasSubtype(true); } else { p.setType(t & 0xff); } p.setNumber(number++); points.add(p); } } /** * The indexed points and the points sections are both read just the same. */ private void fetchPointsCommonExtType(Subdivision sd, long start, long end, List<Point> points) { position(start); ImgFileReader reader = getReader(); int number = points.size() + 1; while (position() < end) { Point p = new Point(sd); int type = reader.get() << 8; byte b = reader.get(); type |= 0x10000 + (b & 0x1f); p.setType(type); p.setDeltaLong((short)reader.getChar()); p.setDeltaLat((short)reader.getChar()); Label l; if ((b & 0x20) != 0 ){ int labelOffset = reader.getu3(); boolean hasPoi = (labelOffset & 0x400000) != 0; if (hasPoi) { POIRecord record = lblFile.fetchPoi(labelOffset); if (record != null) { l = record.getNameLabel(); p.setPOIRecord(record); } else l = lblFile.fetchLabel(0); } else { l = lblFile.fetchLabel(labelOffset); } p.setLabel(l); } if ((b & 0x80) != 0){ extractExtraBytes(reader, p); } p.setNumber(number++); points.add(p); } } /** * Get all the lines for a given subdivision. * @param div The subdivision we want the line from. * @return A list of lines. */ public List<Polyline> linesForSubdiv(Subdivision div) { ArrayList<Polyline> list = new ArrayList<Polyline>(); if (div.hasPolylines()){ RgnOffsets rgnOffsets = getOffsets(div); int start = rgnOffsets.getLineStart(); int end = rgnOffsets.getLineEnd(); position(start); while (position() < end) { Polyline line = new Polyline(div); readLineCommon(getReader(), div, line); list.add(line); } } if (div.getExtTypeLinesSize() > 0){ int start = rgnHeader.getExtTypeLinesOffset() + div.getExtTypeLinesOffset(); int end = start + div.getExtTypeLinesSize(); position(start); while (position() < end) { Polyline line = new Polyline(div); readLineCommonExtType(getReader(), div, line); list.add(line); } } return list; } /** * Get all the polygons for a given subdivision. */ public List<Polygon> shapesForSubdiv(Subdivision div) { ArrayList<Polygon> list = new ArrayList<Polygon>(); if (div.hasPolygons()){ RgnOffsets rgnOffsets = getOffsets(div); int start = rgnOffsets.getPolygonStart(); int end = rgnOffsets.getPolygonEnd(); position(start); while (position() < end) { Polygon line = new Polygon(div); readLineCommon(getReader(), div, line); list.add(line); } } if (div.getExtTypeAreasSize() > 0){ int start = rgnHeader.getExtTypeAreasOffset() + div.getExtTypeAreasOffset(); int end = start + div.getExtTypeAreasSize(); position(start); while (position() < end) { Polygon line = new Polygon(div); readLineCommonExtType(getReader(), div, line); list.add(line); } } return list; } /** * Since polygons are pretty much like polylines in the img format the * reading code can be shared. * * @param reader The reader for the img file. * @param div The subdivision. * @param line The line or shape that is to be populated. */ private void readLineCommon(ImgFileReader reader, Subdivision div, Polyline line) { byte type = reader.get(); if (line instanceof Polygon) line.setType(type & 0x7f); else { line.setType(type & 0x3f); line.setDirection((type & 0x40) != 0); } int labelOffset = reader.getu3(); // Extra bit (for bit stream) boolean extra = (labelOffset & 0x400000) != 0; Label label; if ((labelOffset & 0x800000) == 0) { label = lblFile.fetchLabel(labelOffset & 0x7fffff); } else { int netoff = labelOffset & 0x3fffff; labelOffset = netFile.getLabelOffset(netoff); label = lblFile.fetchLabel(labelOffset); RoadDef roadDef = new RoadDef(0, netoff, label.getText()); line.setRoadDef(roadDef); } line.setLabel(label); line.setDeltaLong((short)reader.getChar()); line.setDeltaLat((short)reader.getChar()); int len; if ((type & 0x80) == 0) len = reader.get() & 0xff; else len = reader.getChar(); int base = reader.get(); byte[] bitstream = reader.get(len); BitReader br = new BitReader(bitstream); // This reads the bit stream and adds all the points found readBitStream(br, div, line, extra, len, base); } /** * Common code to read extended type lines or polygons for a given sub division. * @param reader The reader for the img file. * @param div The subdivision. * @param line The line or shape that is to be populated. */ private void readLineCommonExtType(ImgFileReader reader, Subdivision div, Polyline line) { int type = reader.get(); type = (type & 0xff) << 8; byte b1 = reader.get(); boolean hasExtraBytes = (b1 & 0x80) != 0; boolean hasLabel = (b1 & 0x20) != 0; type |= 0x10000 + (b1 & 0x1f); line.setType(type); line.setDeltaLong((short)reader.getChar()); line.setDeltaLat((short)reader.getChar()); b1 = reader.get(); int len; // one byte or two byte length field? if ((b1 & 0x01) != 0){ len = (b1 >> 1) & 0x7f; assert len < 0x7f; } else { byte b2 = reader.get(); len = (((b2 & 0xff) << 8) + (b1 & 0xff)) >> 2; assert len >= 0x7f; } --len; // the encoded value includes the base field assert len > 0; int base = reader.get(); byte[] bitstream = reader.get(len); BitReader br = new BitReader(bitstream); // This reads the bit stream and adds all the points found, readBitStream(br, div, line, false, len, base); if (hasLabel){ int labelOffset = reader.getu3(); Label label; if ((labelOffset & 0x800000) == 0) { label = lblFile.fetchLabel(labelOffset & 0x7fffff); } else { int netoff = labelOffset & 0x3fffff; labelOffset = netFile.getLabelOffset(netoff); label = lblFile.fetchLabel(labelOffset); RoadDef roadDef = new RoadDef(0, netoff, label.getText()); line.setRoadDef(roadDef); } line.setLabel(label); } if (hasExtraBytes){ extractExtraBytes(reader, line); } } /** * Extract extra bytes. * @param reader */ void extractExtraBytes(ImgFileReader reader, MapObject o){ long pos = reader.position(); StringBuilder sb = new StringBuilder(); ArrayList<Byte> bytes = new ArrayList<Byte>(); byte b1 = reader.get(); bytes.add(b1); if ((b1 & 0xe0) != 0){ // varying length, search for 0x01 as this seems to be the terminator do{ b1 = reader.get(); bytes.add(b1); } while (b1 != 0x01); } else if ((b1 & 0xa0) != 0){ bytes.add(reader.get()); bytes.add(reader.get()); }else if ((b1 & 0x80) != 0){ bytes.add(reader.get()); } for (Byte b: bytes){ sb.append(String.format("%x", b)); } ExtTypeAttributes eta = new ExtTypeAttributes(Collections.singletonMap("extra-bytes", sb.toString()), "data from img pos " + pos); o.setExtTypeAttributes(eta); } /** * Read the bit stream for a single line in the file. * @param br The bit stream reader. * @param div The subdivision that the line is in. * @param line The line itself. * @param extra True if there is an 'extra' bit in the stream. Used for nodes. * @param len The length of the stream. * @param base The base size of the deltas. */ private void readBitStream(BitReader br, Subdivision div, Polyline line, boolean extra, int len, int base) { int currLat = line.getLat(); int currLon = line.getLong(); log.debug(String.format("Start point %.5f,%.5f", Utils.toDegrees(currLat), Utils.toDegrees(currLon))); if (extra) line.addCoord(new CoordNode(currLat, currLon, 0/* XXX */, false)); else line.addCoord(new Coord(currLat, currLon)); int xbase = 2; int n = base & 0xf; if (n <= 9) xbase += n; else xbase += (2 * n) - 9; n = (base >>> 4) & 0xf; int ybase = 2; if (n <= 9) ybase += n; else ybase += (2 * n) - 9; if (len == 0) return; boolean xneg = false; boolean xsame = br.get1(); if (xsame) { xneg = br.get1(); } else xbase++; boolean ysame = br.get1(); boolean yneg = false; if (ysame) { yneg = br.get1(); } else ybase++; if(line.hasExtendedType()) { br.get1(); } if (extra) { boolean firstextra = br.get1(); log.debug("the first extra bit is", firstextra); } // All is now prepared, read the actual deltas and decode them into // proper lat/long coords. while (br.getBitPosition() <= 8* len - ((extra ? 1:0) + xbase + ybase)) { br.getBitPosition(); int dx; if (xsame) { dx = br.get(xbase); if (xneg) dx = -dx; } else { dx = br.sget2(xbase); } int dy; if (ysame) { dy = br.get(ybase); if (yneg) dy = -dy; } else { dy = br.sget2(ybase); } boolean isnode = false; if (extra) isnode = br.get1(); currLat += dy << (24 - div.getResolution()); currLon += dx << (24 - div.getResolution()); Coord coord; if (isnode) coord = new CoordNode(currLat, currLon, 0/* XXX */, false); else coord = new Coord(currLat, currLon); line.addCoord(coord); } if (line instanceof Polygon){ // make sure that polygon is closed line.addCoord(line.getPoints().get(0)); } } /** * Get the offsets to the points, lines etc in RGN for the given subdiv. * @param sd The subdivision is needed to work out the starting points. * @return An Offsets class that allows you to obtain the offsets. */ private RgnOffsets getOffsets(Subdivision sd) { int off = sd.getStartRgnPointer(); position(rgnHeader.getDataOffset() + off); return new RgnOffsets(sd); } public void setLblFile(LBLFileReader lblFile) { this.lblFile = lblFile; } public void setNetFile(NETFileReader netFile) { this.netFile = netFile; } /** * Class to hold the start and end points of point, lines etc within * the area for a given subdivision in the RGN data. */ private class RgnOffsets { private final int pointOffset; private int pointEnd; private int indPointOffset; private int indPointEnd; private int lineOffset; private int lineEnd; private int polygonOffset; private int polygonEnd; private final int start; private int headerLen; /** * Calculate the offsets for the given subdivision. * After this is called the position will be set after any pointers that * exist at the beginning of the area. * * @param sd The subdivision. */ private RgnOffsets(Subdivision sd) { ImgFileReader reader = getReader(); start = (int) position(); pointOffset = 0; if (sd.needsIndPointPtr()) { indPointOffset = reader.getChar(); headerLen += 2; } if (sd.needsPolylinePtr()) { lineOffset = reader.getChar(); headerLen += 2; } if (sd.needsPolygonPtr()) { polygonOffset = reader.getChar(); headerLen += 2; } if (sd.hasPoints()) { if (sd.hasIndPoints()) pointEnd = indPointOffset; else if (sd.hasPolylines()) pointEnd = lineOffset; else if (sd.hasPolygons()) pointEnd = polygonOffset; else pointEnd = sd.getEndRgnPointer() - sd.getStartRgnPointer(); } if (sd.hasIndPoints()) { if (sd.hasPolylines()) indPointEnd = lineOffset; else if (sd.hasPolygons()) indPointEnd = polygonOffset; else indPointEnd = sd.getEndRgnPointer() - sd.getStartRgnPointer(); } if (sd.hasPolylines()) { if (sd.hasPolygons()) lineEnd = polygonOffset; else lineEnd = sd.getEndRgnPointer() - sd.getStartRgnPointer(); } if (sd.hasPolygons()) { polygonEnd = sd.getEndRgnPointer() - sd.getStartRgnPointer(); } } public String toString() { return String.format("rgn div offsets: %x-%x/%x-%x/%x-%x/%x-%x", pointOffset, pointEnd, indPointOffset, indPointEnd, lineOffset, lineEnd, polygonOffset, polygonEnd); } public long getPointStart() { return pointOffset == 0 ? start + headerLen : start + pointOffset; } public long getPointEnd() { return start + pointEnd; } public long getIndPointStart() { return indPointOffset == 0 ? start + headerLen : start + indPointOffset; } public long getIndPointEnd() { return start + indPointEnd; } public int getLineStart() { return lineOffset == 0? start + headerLen: start + lineOffset; } public int getLineEnd() { return start + lineEnd; } public int getPolygonStart() { return polygonOffset == 0? start + headerLen: start + polygonOffset; } public int getPolygonEnd() { return start + polygonEnd; } } }