Subversion Repositories mkgmap

Rev

Rev 3408 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;
                }
        }
}