/* * 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 func.lib; 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<Numbers>(); // 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 { }