Subversion Repositories display

Rev

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

/*
 * Copyright (C) 2008 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 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 test.util;

import java.util.ArrayList;
import java.util.List;

import uk.me.parabola.imgfmt.app.BitReader;
import uk.me.parabola.imgfmt.app.net.NumberStyle;
import uk.me.parabola.imgfmt.app.net.Numbers;

import static uk.me.parabola.imgfmt.app.net.NumberStyle.*;


/**
 * This is a test reader of the numbering streams. Since there are multiple ways of writing
 * the same set of house numbers, the only reasonable way of testing the write process is to
 * read the bit stream back and compare with the intended numbers.
 *
 * There is no attempt at efficiency given it is for testing, but it is believed to correctly
 * read numbers from any map.
 *
 * This code is derived directly from the NetDisplay class in the display project, so see that
 * to see the development of this file.
 * The algorithm that is required to read the bit stream was partly derived by studying the
 * the released GPL code of cGPSmapper by Stanislaw Kozicki.
 *
 * @author Steve Ratcliffe
 */

public class NumberReader {

        private final BitReader br;

        // For reading the start differences and end difference numbers.
        private VarBitReader startReader;
        private VarBitReader endReader;
        private VarBitReader savedStartReader;
        private VarBitReader savedEndReader;
        private boolean doRestoreBitWidths;

        // base numbers
        private int leftBase;
        private int rightBase;

        // numbering styles
        private NumberStyle leftStyle = ODD;
        private NumberStyle rightStyle = EVEN;

        // start numbers
        private int leftStart;
        private int rightStart;

        // end numbers
        private int leftEnd;
        private int rightEnd;

        // saved end numbers
        private int leftLastEndDiff;
        private int rightLastEndDiff;

        // Numbers are a range between nodes. Keep count of them here
        private int nodeCounter;
        private int numberOfNodes;

        public NumberReader(BitReader br) {
                this.br = br;
        }

        public void setNumberOfNodes(int numberOfNodes) {
                this.numberOfNodes = numberOfNodes;
        }

        /**
         * Read the numbers into a list of Numbers classes.
         * @param swap If the default starting position of left=ODD right=EVEN should be swapped.
         * @return A list of the numbers that the input stream represents.
         */

        public List<Numbers> readNumbers(boolean swap) {
                if (swap) {
                        leftStyle = EVEN;
                        rightStyle = ODD;
                }

                getBitWidths();

                getInitialBase();

                List<Numbers> numbers = new ArrayList<>();

                // To do this properly we need to know the number of nodes I think, this is the
                // best we can do: if there are more than 8 bits left, there must be another command
                // left.  We could leave a short command at the end.
                while (nodeCounter < numberOfNodes + 1) {
                        try {
                                runCommand(numbers);
                        } catch (NumberException | ArrayIndexOutOfBoundsException e) {
                                //System.out.printf("collected %d, wanted %d\n", numbers.size(), numberOfNodes+1);
                                return numbers;
                        }
                }

                return numbers;
        }


        /**
         * Get the bit widths for the start and end differences.
         * Based on code for reading the RGN streams, but the signed bit is the
         * opposite value.
         * x is for start value differences.  y is for end value differences.
         */

        private void getBitWidths() {
                startReader = new VarBitReader(br, 5);
                endReader = new VarBitReader(br, 2);
        }

        /**
         * Decode the next command in the stream and run it.
         * @param numbers When numbers are read, they are saved here.
         */

        private void runCommand(List<Numbers> numbers) throws NumberException {
                int cmd = readCommand(); // fetch 1, 3 skip, 2 reload, 0 style

                switch (cmd) {
                case 0:
                        changeStyles();
                        break;
                case 1:
                        fetchNumbers(numbers);
                        break;
                case 2:
                        useBits();
                        break;
                case 6:
                        skipNodes();
                        break;
                default:
                        fail("unimplemented command: " + cmd);
                }
        }

        /**
         * Temporarily use a different bit width for the following number fetch.
         */

        private void useBits() {
                if (!doRestoreBitWidths) {
                        savedStartReader = startReader;
                        savedEndReader = endReader;
                }
                doRestoreBitWidths = true;

                if (br.get1()) {
                        endReader = new VarBitReader(br, 2);
                } else {
                        startReader = new VarBitReader(br, 5);
                }
        }

        /**
         * Skip nodes. For parts of a road that has no numbers.
         */

        private void skipNodes() {
                boolean f = br.get1();
                int skip;
                if (f)
                        skip = 1 + br.get(10);
                else
                        skip = 1 + br.get(5);
                nodeCounter += skip;
        }

        /**
         * Read the next command from the stream. Commands are variable length in the bit
         * stream.
         * 0 - numbering style (none, odd, even, both)
         * 1 - fetch numbers
         * 2 - change bit widths
         * 6 - skip nodes
         * @return The command number
         */

        private int readCommand() {
                int cmd = 0;
                if (br.get1()) {
                        cmd |= 0x1;
                } else {
                        if (br.get1()) {
                                cmd |= 0x2;
                                if (br.get1()) {
                                        cmd |= 0x4;
                                }
                        }
                }
                return cmd;
        }

        /**
         * Read the house numbers for a stretch of road.
         *
         * The start and end positions of the the left hand side of the road is first, followed
         * by the right hand side of the road.
         *
         * The differences to the last point are stored. It is also possible to
         * @param numbers When numbers are read, they are saved here.
         */

        private void fetchNumbers(List<Numbers> numbers) {

                // If one side has no numbers, then there is only one set of numbers to calculate, but
                // changes to base are applied to both sides.
                boolean doSingleSide = (leftStyle == NONE || rightStyle == NONE);

                if (leftStyle == NONE)
                        leftBase = rightBase;

                // Check for command to copy the base number
                boolean doSameBase = false;
                if (!doSingleSide) {
                        doSameBase = br.get1();
                        if (doSameBase)
                                copyBase();
                }

                //int abc = br.get(3);
                boolean doRightOverride = false;
                if (!doSingleSide)
                        doRightOverride = !br.get1();
                boolean doReadStart = !br.get1();
                boolean doReadEnd = !br.get1();

                //item.addText("cmd: fetch numbers abc: %x", abc);

                int startDiff = 0, endDiff = leftLastEndDiff;

                if (doReadStart) {
                        startDiff = startReader.read();
                }
                if (doReadEnd) {
                        endDiff = endReader.read();
                }

                leftStart = leftBase + startDiff;
                leftEnd = leftStart + endDiff;

                leftBase = leftEnd;
                leftLastEndDiff = endDiff;

                if (doSingleSide) {
                        readSingleSide(numbers);
                        restoreReaders();
                        return;
                }

                // *** Now for the right hand side numbers ***

                // Note that endDiff falls through to this part
                // start diff falls through at least when doSameBase is in force
                if (!doSameBase)
                        startDiff = 0;

                // If we didn't read an endDiff value for the left side or right is different then
                // default to the saved value.
                if (doRightOverride || !doReadEnd)
                        endDiff = rightLastEndDiff;

                doReadStart = false;
                doReadEnd = false;

                if (!doSameBase)
                        doReadStart = !br.get1();

                if (doRightOverride)
                        doReadEnd = !br.get1();

                if (doReadStart)
                        startDiff = startReader.read();

                if (doReadEnd)
                        endDiff = endReader.read();

                rightStart = rightBase + startDiff;
                rightEnd = rightStart + endDiff;

                rightBase = rightEnd;
                rightLastEndDiff = endDiff;

                adjustValues();

                Numbers n = new Numbers();
                n.setIndex(nodeCounter);
                n.setNumbers(Numbers.LEFT, leftStyle, leftStart, leftEnd);
                n.setNumbers(Numbers.RIGHT, rightStyle, rightStart, rightEnd);

                numbers.add(n);
                nodeCounter++;

                restoreReaders();
        }

        /**
         * After a temporary bit width change.
         */

        private void restoreReaders() {
                if (doRestoreBitWidths) {
                        startReader = savedStartReader;
                        endReader = savedEndReader;
                        doRestoreBitWidths = false;
                }
        }

        /**
         * If the road has numbers on just one side, then there is a shortened reading routine.
         * The left variables are mostly used during reading regardless of which side of the
         * road has numbers. Make everything work here.
         * @param numbers The output list that the number record should be added to.
         */

        private void readSingleSide(List<Numbers> numbers) {
                rightBase = leftBase;
                rightStart = leftStart;
                rightEnd = leftEnd;
                rightLastEndDiff = leftLastEndDiff;
                adjustValues();

                Numbers n = new Numbers();
                n.setIndex(nodeCounter);
                if (leftStyle == NONE)
                        n.setNumbers(Numbers.RIGHT, rightStyle, rightStart, rightEnd);
                else
                        n.setNumbers(Numbers.LEFT, leftStyle, leftStart, leftEnd);
                numbers.add(n);
                nodeCounter++;
        }

        /**
         * When it is known if the numbers are odd or even, then a shorter bitstream is made
         * by taking advantage of that fact. This leaves the start and end points needing
         * adjustment to made them odd or even as appropriate.
         */

        private void adjustValues() {
                int ldirection = 1; // direction start is adjusted in; end in the opposite direction.
                if (leftStart < leftEnd)
                        leftEnd--;
                else if (leftStart > leftEnd) {
                        leftEnd++;
                        ldirection = -1;
                }

                int rdirection = 1; // direction start is adjusted in; end in the opposite direction.
                if (rightStart < rightEnd)
                        rightEnd--;
                else if (rightStart > rightEnd) {
                        rightEnd++;
                        rdirection = -1;
                }

                if (leftStyle == EVEN) {
                        if ((leftStart & 1) == 1) leftStart += ldirection;
                        if ((leftEnd & 1) == 1) leftEnd -= ldirection;
                } else if (leftStyle == ODD) {
                        if ((leftStart & 1) == 0) leftStart+=ldirection;
                        if ((leftEnd & 1) == 0) leftEnd-=ldirection;
                }
                if (rightStyle == EVEN) {
                        if ((rightStart & 1) == 1) rightStart+=rdirection;
                        if ((rightEnd & 1) == 1) rightEnd-=rdirection;
                } else if (rightStyle == ODD) {
                        if ((rightStart & 1) == 0) rightStart+=rdirection;
                        if ((rightEnd & 1) == 0) rightEnd-=rdirection;
                }
        }

        /**
         * Copy one of the bases to the other so they have the same value.
         * The source is determined by reading a bit from the input.
         */

        private void copyBase() {
                boolean f2 = br.get1();
                if (f2) {
                        rightBase = leftBase;
                } else {
                        leftBase = rightBase;
                }
        }

        /**
         * Change the numbering styles for this section of roads.
         */

        private void changeStyles() {
                leftStyle = fromInt(br.get(2));
                rightStyle = fromInt(br.get(2));
        }

        /**
         * Get the initial base value. The first number for this section of road (although a diff
         * can be applied to it).
         *
         * @throws NumberException
         */

        private void getInitialBase() {
                int extra = 0;
                boolean b1 = br.get1();
                if (!b1)
                        extra = br.get(4);

                leftBase = br.get(5 + extra);
                rightBase = leftBase;
        }

        /**
         * For cases that are not implemented yet.
         */

        private void fail(String s) throws NumberException {
                System.out.printf("ABANDON: %s\n", s);
                remainingBits();
                throw new NumberException();
        }

        /**
         * Just print out any remaining bits.
         *
         * Was mostly used during development, before the whole stream was decoded.
         */

        private void remainingBits() {
                StringBuilder sb = new StringBuilder();
                while (br.getBitPosition() < br.getNumberOfBits()) {
                        sb.insert(0, br.get1() ? "1" : "0");
                }
                System.out.print(sb.toString());
        }

}

/**
 * Reads integers with specified numbers of bits and optionally with sign bits.
 */

class VarBitReader {
        private final boolean signed;   // read as signed values
        private final boolean negative; // all values are read as positive and then negated
        private final int width;        // the number of bits
        private final int off;    // a value to be added to width to get the true number to read.
        private final BitReader br;

        public VarBitReader(BitReader br, int off) {
                this.br = br;
                this.off = off;
                negative = br.get1();
                signed = br.get1();
                width = br.get(4);
        }

        public int read() {
                int val;
                if (signed) {
                        val = br.sget(width + off + 1);
                } else {
                        val = br.get(width + off);
                }

                if (negative)
                        val = -val;
                return val;
        }

        public String toString() {
                return String.format("sign=%b neg=%b width=%d+%d", signed, negative, width, off);
        }
}


class NumberException extends RuntimeException {
}