Subversion Repositories mkgmap

Rev

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

/*
 * 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 uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.log.Logger;

/**
 * An arc joins two nodes within a {@link RouteCenter}.  This may be renamed
 * to a Segment.
 * The arc also references the road that it is a part of.
 *
 * There are also links between nodes in different centers.
 *
 * @author Steve Ratcliffe
 */

public class RouteArc {
        private static final Logger log = Logger.getLogger(RouteArc.class);
       
        // Flags A
        private static final int FLAG_NEWDIR = 0x80;
        private static final int FLAG_FORWARD = 0x40;
        private static final int MASK_DESTCLASS = 0x7;
        public static final int MASK_CURVE_LEN = 0x38;

        // Flags B
        private static final int FLAG_LAST_LINK = 0x80;
        private static final int FLAG_EXTERNAL = 0x40;

        private int offset;

        private int initialHeading; // degrees
        private final int finalHeading; // degrees

        private final RoadDef roadDef;

        // The nodes that this arc comes from and goes to
        private final RouteNode source;
        private final RouteNode dest;

        // The index in Table A describing this arc.
        private byte indexA;
        // The index in Table B that this arc goes via, if external.
        private byte indexB;
       
        private byte flagA;
        private byte flagB;

        private boolean haveCurve;
        private int length;
        private final int pointsHash;
        private final boolean curveEnabled;

        /**
         * Create a new arc.
         *
         * @param roadDef The road that this arc segment is part of.
         * @param source The source node.
         * @param dest The destination node.
         * @param initialHeading The initial heading (signed degrees)
         */

        public RouteArc(RoadDef roadDef,
                                        RouteNode source, RouteNode dest,
                                        int initialHeading, int finalHeading,
                                        double length,
                                        boolean curveEnabled,
                                        int pointsHash) {
                this.roadDef = roadDef;
                this.source = source;
                this.dest = dest;
                this.initialHeading = initialHeading;
                this.finalHeading = finalHeading;
                this.length = convertMeters(length);
                this.curveEnabled = curveEnabled;
                this.pointsHash = pointsHash;
        }

        public int getInitialHeading() {
                return initialHeading;
        }

        public void setInitialHeading(int ih) {
                initialHeading = ih;
        }

        public int getFinalHeading() {
                return finalHeading;
        }

        public RouteNode getSource() {
                return source;
        }

        public RouteNode getDest() {
                return dest;
        }

        public int getLength() {
                return length;
        }

        public int getPointsHash() {
                return pointsHash;
        }

        /**
         * Provide an upper bound for the written size in bytes.
         */

        public int boundSize() {

                int[] lendat = encodeLength();

                // 1 (flagA) + 1-2 (offset) + 1 (indexA) + 1 (initialHeading)
                int size = 5 + lendat.length;
                if(haveCurve)
                        size += encodeCurve().length;
                return size;
        }

        /**
         * Is this an arc within the RouteCenter?
         */

        public boolean isInternal() {
                // we might check that setInternal has been called before
                return (flagB & FLAG_EXTERNAL) == 0;
        }

        public void setInternal(boolean internal) {
                if (internal)
                        flagB &= ~FLAG_EXTERNAL;
                else
                        flagB |= FLAG_EXTERNAL;
        }


        /**
         * Set this arc's index into Table A.
         */

        public void setIndexA(byte indexA) {
                this.indexA = indexA;
        }

        /**
         * Get this arc's index into Table A.
         *
         * Required for writing restrictions (Table C).
         */

        public byte getIndexA() {
                return indexA;
        }

        /**
         * Set this arc's index into Table B. Applies to external arcs only.
         */

        public void setIndexB(byte indexB) {
                assert !isInternal() : "Trying to set index on internal arc.";
                this.indexB = indexB;
        }

        /**
         * Get this arc's index into Table B.
         *
         * Required for writing restrictions (Table C).
         */

        public byte getIndexB() {
                return indexB;
        }
         

        private static int convertMeters(double l) {
                // XXX: really a constant factor?
                // this factor derived by looking at a variety
                // of arcs in an IMG of Berlin; 1/4 of
                // what used to be here
                double factor = 3.28 / 16;
                return (int) (l * factor);
        }

        public void write(ImgFileWriter writer) {
                offset = writer.position();
                if(log.isDebugEnabled())
                        log.debug("writing arc at", offset, ", flagA=", Integer.toHexString(flagA));

                // fetch destination class -- will have been set correctly by now
                setDestinationClass(dest.getNodeClass());

                // determine how to write length and curve bit
                int[] lendat = encodeLength();

                writer.put(flagA);

                if (isInternal()) {
                        // space for 14 bit node offset, written in writeSecond.
                        writer.put(flagB);
                        writer.put((byte) 0);
                } else {
                        if(indexB >= 0x3f) {
                                writer.put((byte) (flagB | 0x3f));
                                writer.put(indexB);
                        }
                        else
                                writer.put((byte) (flagB | indexB));
                }

                writer.put(indexA);

                if(log.isDebugEnabled())
                        log.debug("writing length", length);
                for (int aLendat : lendat)
                        writer.put((byte) aLendat);

                writer.put((byte)(256 * initialHeading / 360));

                if (haveCurve) {
                        int[] curvedat = encodeCurve();
                        for (int aCurvedat : curvedat)
                                writer.put((byte) aCurvedat);
                }
        }

        /**
         * Second pass over the nodes in this RouteCenter.
         * Node offsets are now all known, so we can write the pointers
         * for internal arcs.
         */

        public void writeSecond(ImgFileWriter writer) {
                if (!isInternal())
                        return;

                writer.position(offset + 1);
                char val = (char) (flagB << 8);
                int diff = dest.getOffsetNod1() - source.getOffsetNod1();
                assert diff < 0x2000 && diff >= -0x2000
                        : "relative pointer too large for 14 bits (source offset = " + source.getOffsetNod1() + ", dest offset = " + dest.getOffsetNod1() + ")";
                val |= diff & 0x3fff;

                // We write this big endian
                if(log.isDebugEnabled())
                        log.debug("val is", Integer.toHexString((int)val));
                writer.put((byte) (val >> 8));
                writer.put((byte) val);
        }

        /*
         * length and curve flag are stored in a variety of ways, involving
         * 1. flagA & 0x38 (3 bits)
         * 2. 1-3 bytes following the possible Table A index
         *
         * There's even more different encodings supposedly.
         */

        private int[] encodeLength() {

                // update haveCurve
                haveCurve = (curveEnabled && finalHeading != initialHeading);

                if (length >= (1 << 14)) {
                        log.error("Way " + roadDef.getName() + " (id " + roadDef.getId() + ") contains an arc whose length (" + length + " units) is too big to be encoded so the way will not be routable");
                        length = (1 << 14) - 1;
                }

                // clear existing bits in case length or final heading have
                // been changed
                flagA &= ~0x38;
                int[] lendat;
                if(length < 0x200) {
                        // 9 bit length optional curve
                        if(haveCurve)
                                flagA |= 0x20;
                        flagA |= (length >> 5) & 0x08; // top bit of length
                        lendat = new int[1];               // one byte of data
                        lendat[0] = length;                        // bottom 8 bits of length
                }
                else if(haveCurve) {
                        // 15 bit length with curve
                        flagA |= 0x38;                           // all three bits set
                        lendat = new int[2];             // two bytes of data
                        lendat[0] = (length & 0x7f); // 0x80 not set, 7 low bits of length
                        lendat[1] = (length >> 7) & 0xff; // 8 more bits of length
                }
                else {
                        // 14 bit length no curve
                        flagA |= 0x38;           // all three bits set
                        lendat = new int[2]; // two bytes of data
                        lendat[0] = 0x80 | (length & 0x3f); // 0x80 set, 0x40 not set, 6 low bits of length
                        lendat[1] = (length >> 6) & 0xff; // 8 more bits of length
                }

                return lendat;
        }

        /**
         * Encode the curve data into a sequence of bytes.
         *
         * 1 or 2 bytes show up in practice, but they're not at
         * all well understood yet.
         */

        private int[] encodeCurve() {
                // most examples of curve data are a single byte that encodes
                // the final heading of the arc. The bits appear to be
                // reorganised into the order 21076543 (i.e. the top 5 bits
                // are shifted down to the bottom).  Unfortunately, it's not
                // that simple because sometimes the curve is encoded using 2
                // bytes. The presence of the 2nd byte is indicated by the top
                // 3 bits of the first byte all being zero. As the encoding of
                // the 2-byte variant is not yet understood, for the moment,
                // if the resulting value would have the top 3 bits all zero,
                // we set what we hope is the LSB so that it becomes valid
                // 1-byte curve data
                int heading = 256 * finalHeading / 360;
                int encodedHeading = ((heading & 0xf8) >> 3) | ((heading & 0x07) << 5);
                if((encodedHeading & 0xe0) == 0) {
                        // hack - set a bit (hopefully, the LSB) to force 1-byte
                        // encoding
                        encodedHeading |= 0x20;
                }
                int[] curveData = new int[1];
                curveData[0] = encodedHeading;
                return curveData;
        }

        public RoadDef getRoadDef() {
                return roadDef;
        }

        public void setNewDir() {
                flagA |= FLAG_NEWDIR;
        }

        public void setForward() {
                flagA |= FLAG_FORWARD;
        }

        public boolean isForward() {
                return (flagA & FLAG_FORWARD) != 0;
        }

        public void setLast() {
                flagB |= FLAG_LAST_LINK;
        }

        protected void setDestinationClass(int destinationClass) {
                if(log.isDebugEnabled())
                        log.debug("setting destination class", destinationClass);
                flagA |= (destinationClass & MASK_DESTCLASS);
        }
}