Subversion Repositories display

Rev

Rev 360 | 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.check;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.BitReader;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.net.Numbers;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.imgfmt.app.trergn.Zoom;

import test.display.Displayer;
import test.display.Section;
import test.elements.Line;
import test.util.NumberReader;

/**
 * Checks the consistency of the NET file.
 *
 * 1. For each road def, lookup the rgn line by subdiv and line number. Check that
 * the name of each is the same. The name could happen to be the same and the better
 * check would be against the NET pointer in the rgn section, but this would require
 * more code changes.
 *
 * @author Steve Ratcliffe
 */

public class NetCheck extends CommonCheck {
        private static final int RD_HAS_NOD_INFO = 0x40;
        private static final int RD_HAS_STREET_ADDRESS_INFO = 0x10;

        private static final int U_FLAG_1 = 0x04;
        private static final int U_FLAG_2 = 0x08;
        private static final int U_FLAG_3 = 0x10;

        private Subdivision[][] subdivisions = new Subdivision[16][];

        private Map<Integer, String> roadNames = new HashMap<>();
        private ArrayList<Integer> roadIndexes = new ArrayList<>();
        private byte mult1;

        private final SortedSet<Integer> roadOffsets = new TreeSet<>();
        private int lowLevel;

        protected void print() {
                readHeaderLen();
                readNetHeader();

                openLbl();
                if (lbl.getCities().size() > 255)
                        citySize = 2;
                if (lbl.getZips().size() > 255)
                        zipSize = 2;

                openTre();

                openNet();
                openRgn();

                loadRoadIndex();
                findRoads();
                readRoadDefs();
                checkRoadIndex();
        }

        private void loadRoadIndex() {
                Section section = sections.get(2);
                int recordSize = section.getRecordSize();
                if (recordSize > 3)
                        assert false : "not implemented, unknown record size " + recordSize;

                long end = section.getEnd() - section.getStart();

                reader.position(section.getStart());
                for (int pos = 0; pos < end; pos += recordSize)
                        roadIndexes.add(reader.getu3() & 0x3fffff);

                Collections.sort(roadIndexes);
        }

        /**
         * Check the road index.
         *
         * Each pointer should point to an entry in NET 1.
         *
         * The entries in the section should be sorted according the defined sort
         * order.
         *
         * Note: the top two bits are an index into the array of road names that
         * can be associated with a road.
         */

        private void checkRoadIndex() {
                Section section = sections.get(2);
                int recordSize = section.getRecordSize();
                if (recordSize > 3)
                        assert false : "not implemented, unknown record size " + recordSize;

                long end = section.getEnd() - section.getStart();

                reader.position(section.getStart());
                for (int pos = 0; pos < end; pos += recordSize) {
                        int value = reader.getu3();

                        //int offset = value & 0x3fffff;
                        //int strNumber = (value >>> 22) & 0x3;

                        if (!roadNames.containsKey(value)) {
                                error("NET 3 not valid: 0x%06x", value);
                        }
                }
        }

        /**
         * We have to find all the roads positions before we start.
         */

        private void findRoads() {
                openTre();
                openRgn();
                openNet();

                Zoom[] levels = tre.getMapLevels();
                lowLevel = levels[levels.length - 1].getLevel();
                Subdivision[] subdivisions = tre.subdivForLevel(lowLevel);
                for (Subdivision div : subdivisions) {
                        List<Line> lines = rgn.linesForSubdiv(div);
                        for (Line line : lines) {
                                if (line.hasNet())
                                        roadOffsets.add(line.getNetOffset());
                        }
                }
        }

        /**
         * Read in a road def (entry in NET 1) so it can be checked.
         */

        private void readRoadDefs() {
                Section section = sections.get(0);
                long start = section.getStart();

                reader.position(start);

                Iterator<Integer> iterator = roadOffsets.iterator();
                if (!iterator.hasNext())
                        return;

                // First will be at zero
                int nextPos = iterator.next();
                assert nextPos == 0;

                int ridx = 0;
                while (reader.position() < section.getEnd() && iterator.hasNext()) {

                        long pos = reader.position() - start;

                        while (mult1 != 0 && (reader.position() & (2 * mult1 - 1)) != 0)
                                reader.get(); // skip padding

                        Def def = new Def();
                        def.name = "NO NAME SET";
                        def.netoff = (int) (reader.position() - start);

                        int labnum = 0;
                        int labval;
                        do {
                                labval = reader.getu3();
                                String labstr = fetchLabel(labval & ~0xc00000);
                                if (labstr != null)
                                        roadNames.put(def.netoff + (labnum << 22), labstr);

                                // we just save the first one for display purposes
                                if (labnum == 0)
                                        def.name = labstr;

                                labnum ++;
                        } while ((labval & 0x800000) == 0);

                        int flags = reader.get() & 0xff; // flags
                        reader.getu3(); //road len

                        readLevelDivs(def);
                        if ((flags & RD_HAS_STREET_ADDRESS_INFO) == RD_HAS_STREET_ADDRESS_INFO) {
                                int nblocks = reader.get() & 0xff;
                                int addrFlags = reader.get() & 0xff;

                                nblocks += ((addrFlags & 0x3) << 8);
                                def.nblocks = nblocks;

                                readAddrInfo(addrFlags >> 2, "zip", zipSize);
                                readAddrInfo(addrFlags >> 4, "city", citySize);
                                List<Numbers> numbers = readAddrInfo(addrFlags >> 6,
                                                "numbers", (nblocks << 8) + flags);

                                if (numbers != null && !numbers.isEmpty()) {
                                        def.rnodNumber = numbers.get(numbers.size() - 1).getRnodNumber();
                                }
                        }

                        if ((flags & RD_HAS_NOD_INFO) == RD_HAS_NOD_INFO) {
                                int nfollow = reader.get() & 0xff;

                                int nodSize = (nfollow & 0x3) + 1;
                                reader.get(nodSize);

                                if ((nfollow & 0xfc) != 0) {
                                        int counter = (nfollow >> 5) & 0x3;
                                        if (counter > 0) {
                                                while (counter-- > 0) {
                                                        int uflag = reader.get() & 0xff;
                                                        //int uflag = d.byteValue("uflag %x") & 0xff;

                                                        //d.item().addText("flag1=%b flag2=%b flag3=%b lo=%d hi=%d", (uflag&U_FLAG_1)!=0, (uflag&U_FLAG_2)!=0, (uflag&U_FLAG_3) !=0, uflag&0x3, (uflag>>>4));

                                                        // Variable length (?) bit command.
                                                        // 0 read number of bytes as given in lo bits, append to uflag and shift down
                                                        // 10 shift down uflag, add one, read this many bytes
                                                        // 11x shift down uflag, read 1, combine. read that many bytes.

                                                        int lo = uflag & 0x3;
                                                        if ((uflag & U_FLAG_1) == 0) {
                                                                int idx = reader.getUint((uflag & 0x3)+1);
                                                                idx += (uflag>>>3);

                                                        } else {
                                                                int len;
                                                                if ((uflag & U_FLAG_2) == U_FLAG_2) {
                                                                        len = (reader.get() & 0xff) << 3;
                                                                        len += (uflag >>> 5);
                                                                } else {
                                                                        len = 1 + (uflag >>> 4);
                                                                }

                                                                if (len > 0) {
                                                                        reader.get(len);
                                                                        //printAddrInfo(d, 0, "city", len);
                                                                } else {
                                                                        // XXX TODO
                                                                }
                                                        }
                                                }
                                        } else if ((nfollow & 0x80) == 0x80) {
                                                reader.get(8); // XXX
                                        }
                                }
                        }

                        checkRoadDef(def);

                        nextPos = iterator.next();
                        int len = (int) (nextPos - (reader.position() - start));
                        if (len < 0)
                                reader.position(reader.position() + len);
                        else
                                reader.get(len);
                }
        }

        /**
         * Check a road def (entry in NET1).
         *
         * All RGN lines that this points to should have a reference back to this NET1 entry.
         *
         * If there is more than one line in a given level for this road def, then they
         * should join up end-to-start. (This is not a hard requirement, full details are not
         * known).
         *
         * When lines join, both ends should be marked with the node-flag.
         *
         * @param def
         */

        private void checkRoadDef(Def def) {
                info("Road: %s 0x%x", def.name, def.netoff);
                Line lastLine = null;
                int lastLevel = -1;
                int countInLevel = 0;
                int nodeCount = 0;
                for (Div d : def.segments) {
                        Subdivision div = getSubdivision(d.level, d.div);

                        List<Line> polylines = rgn.linesForSubdiv(div);
                        Line line;
                        try {
                                line = polylines.get(d.line - 1);
                        } catch (Exception e) {
                                error("line %s %d/%d not found", def.name, d.div, d.line);
                                return;
                        }

                        if (lastLevel == d.level) {
                                countInLevel++;
                        } else {
                                countInLevel = 0;
                        }

                        info(" - %d/%d/%d Type=%x part=%d", d.level, d.div, d.line, line.getType(), countInLevel);

                        //StringBuilder sb = new StringBuilder();
                        Formatter fmt = new Formatter();
                        for (Coord co : line.getCoords()) {
                                fmt.format("   - %s(%.5f,%.5f) %b\n", co, Utils.toDegrees(co.getLatitude()), Utils.toDegrees(co.getLongitude()), co instanceof CoordNode);

                                if (co.getId() > 0)
                                        nodeCount++;
                        }
                        trace(fmt.toString());

                        if (line.getNetOffset() != def.netoff) {
                                error("road net1 offset=0x%x, line net1 offset=0x%x: %s %d/%d/%d", def.netoff,
                                                line.getNetOffset(), def, d.level, d.div, d.line);
                        }

                        if (lastLine != null && lastLevel == d.level) {

                                if (lastLine.getLastPoint().equals(line.getFirstPoint())) {
                                        // This test is not valid unless we have an independent test of which points
                                        // are nodes.  Downgraded to a trace until that can be done.
                                        if (!(lastLine.getLastPoint() instanceof CoordNode) && lastLine.hasNodeFlags() && line.hasNodeFlags()) {
                                                trace("Last point of a joined line without node-flag %s",
                                                                line.getLastPoint().getId() != 0);
                                        }
                                } else {
                                        // Lines don't join

                                        // TODO: this check is not really valid, so it may be removed or improved.
                                        // This is not really an error since this is seen in real maps. In many cases
                                        // when it happens, one of lat or longitude is the same, so downgrade to info
                                        // in that case.
                                        if (lastLine.getLastPoint().getLatitude() == line.getFirstPoint().getLatitude()
                                                        || lastLine.getLastPoint().getLongitude() == line.getFirstPoint().getLongitude())
                                        {
                                                info("warning: last %s, first %s", lastLine.getLastPoint(),
                                                                line.getFirstPoint());

                                        } else {
                                                error("lines do not join: last %s, first %s", lastLine.getLastPoint(),
                                                                line.getFirstPoint());
                                        }
                                }
                        }

                        lastLine = line;
                        lastLevel = d.level;
                }

                if (lastLine != null && lastLine.getLastPoint().getId() != 0)
                        error("last point of line in group has node-flag set");

                trace(" -- nblocks=%d, ncount=%d", def.nblocks, nodeCount);
                if (def.nblocks != null) {
                        if (nodeCount != def.nblocks) {
                                error("node-count=%d, nblocks=%d [%s] %x",
                                                nodeCount, def.nblocks, def.name, def.netoff);
                        }

                        if (def.rnodNumber != null && def.nblocks < def.rnodNumber) {
                                error("housenumbers: final-rnode=%d, nblocks=%d [%s] %x",
                                                def.rnodNumber, def.nblocks, def.name, def.netoff);
                        }
                }
        }

        private void readNetHeader() {
                Displayer d = new Displayer(reader);
                readSection(d, "road defs", 1, false, false);
                mult1 = reader.get();

                readSection(d, "segmented roads", 2, false, false);
                reader.get();

                readSection(d, "road index", 3, true, false);
        }

        private Subdivision[] getSubdivisions(int level) {
                if (subdivisions[level] == null)
                        subdivisions[level] = tre.subdivForLevel(level + lowLevel);

                return subdivisions[level];
        }

        private Subdivision getSubdivision(int level, int n) {
                Subdivision[] divs = getSubdivisions(level);
                return divs[n - divs[0].getNumber()];
        }

        private List<Numbers> readAddrInfo(int flags, String name, int size) {
                switch (flags & 0x3) {
                case 3:
                        return null;
                case 2:
                        if (size == 2)
                                reader.getChar();
                        else
                                reader.get();
                        break;
                case 1:
                        int n = reader.getChar();
                        reader.get(n);
                        break;
                case 0:
                        n = reader.get() & 0xff;
                        byte[] bytes = reader.get(n << mult1);
                        if (name.equals("numbers")) {
                                int rflags = size & 0xff;
                                int nblocks = size >> 8;

                                BitReader br = new BitReader(bytes);
                                NumberReader nr = new NumberReader(br);
                                nr.setNumberOfNodes(nblocks);
                                return nr.readNumbers((rflags & 0x20) != 0);
                        }
                        break;
                }
                return null;
        }

        private void readLevelDivs(Def def) {
                int[] counts = new int[8];
                int ncounts = 0;
                boolean last = false;
                while (!last) {
                        byte b = reader.get();
                        if ((b & 0x80) == 0x80)
                                last = true;

                        counts[ncounts++] = b & 0x7f;
                }

                List<Div> divs = new ArrayList<>();
                for (int i = 0; i < ncounts; i++) {
                        int n = counts[i];
                        while (n-- > 0) {
                                Div div = new Div();
                                div.level = i;
                                div.line = reader.get() & 0xff;
                                div.div = reader.getChar();
                                divs.add(div);
                        }
                }
                def.segments = divs;
        }

        public static void main(String[] args) {
                runMain(NetCheck.class, "NET", args);
        }

        class Def implements Comparable<Def> {
                private String name;
                private int netoff;

                private List<Div> segments;
                private Integer nblocks;
                private Integer rnodNumber;

                /**
                 * Only compares the net1 offset, since that is the only thing we are interested
                 * in currently.
                 */

                public int compareTo(Def o) {
                        if (netoff == o.netoff)
                                return 0;
                        else if (netoff < 0)
                                return -1;
                        else
                                return 1;
                }

                public String toString() {
                        return String.format("0x%06x: %s", netoff, name);
                }
        }

        class Div {
                private int level;
                private int div;
                private int line;
        }
}