Subversion Repositories display

Rev

Rev 539 | 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.text.Collator;
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.NumberStyle;
import uk.me.parabola.imgfmt.app.net.Numbers;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.imgfmt.app.trergn.Zoom;
import uk.me.parabola.mkgmap.srt.SrtTextReader;

import test.display.Displayer;
import test.display.Section;
import test.display.check.Log;
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.get3u() & 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();
                int codepage = lbl.getCodePage();
                Sort sort = SrtTextReader.sortForCodepage(codepage);
                Collator collator = sort.getCollator();
                collator.setStrength(Collator.PRIMARY);
                reader.position(section.getStart());
                String lastName = null;
                int lastValue = 0;
                for (int pos = 0; pos < end; pos += recordSize) {
                        int value = reader.get3u();

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

                        String name = roadNames.get(value);
                        info("Street name: %d %s", value, name);
                        if (name == null) {
                                error("NET 3 not valid: 0x%06x", value);
                        } else if (lastName != null){
                                Name n1 = new Name(lastName);
                                Name n2 = new Name(name);
                                int cmp = collator.compare(n1.partialName(), n2.partialName());
                                if (cmp > 0) {
                                        error("Unsorted partial names /%s/%s/ values %x/%x", lastName, name, lastValue, value);
                                } else if (cmp == 0) {
                                        cmp = collator.compare(lastName, name);
                                        if (cmp > 0) {
                                                error("Initial part unsorted /%s/%s/ values %x/%x", lastName, name, lastValue, value);
                                        }
                                }
                        }

                        //if (lastValue >= value)
                        //      info("Values unsorted %x/%x", lastValue & 0x3fffff, offset);
                        lastName = name;
                        lastValue = 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;

                while (reader.position() < section.getEnd()) {

                        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.get3u();
                                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.get1u(); // flags
                        reader.get3u(); //road len

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

                                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.nodeIndex = numbers.get(numbers.size() - 1).getIndex();
                                        def.numbers = numbers;
                                }
                        }

                        if ((flags & RD_HAS_NOD_INFO) == RD_HAS_NOD_INFO) {
                                int nfollow = reader.get1u();

                                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.get1u();
                                                        //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.getNu((uflag & 0x3)+1);
                                                                idx += (uflag>>>3);

                                                        } else {
                                                                int len;
                                                                if ((uflag & U_FLAG_2) == U_FLAG_2) {
                                                                        len = reader.get1u() << 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);

                        if (!iterator.hasNext())
                                break;
                        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;
                List<Div> segments = def.segments;
                for (int s = 0; s < segments.size(); s++) {
                        Div d = segments.get(s);
                        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);

                        try (Formatter fmt = new Formatter();) {
                                List<Coord> coords = line.getCoords();
                                for (int c = 0; c < coords.size(); c++) {
                                        Coord co = coords.get(c);
                                        if (Log.isTrace()) {
                                                fmt.format("   - %s(%.5f,%.5f) %b %n", co, Utils.toDegrees(co.getLatitude()),
                                                                Utils.toDegrees(co.getLongitude()), co instanceof CoordNode);
                                        }

                                        boolean first = s == 0 && c == 0;
                                        boolean firstInContinuationLine = s > 0 && c == 0;

                                        if (co.getId() > 0 && !first && !firstInContinuationLine)
                                                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.nodeIndex != null && def.nblocks < def.nodeIndex)
                                error("housenumbers: final-rnode=%d, nblocks=%d [%s] %x", def.nodeIndex, def.nblocks, def.name, def.netoff);
                }

                // Check no negative or zero numbers, that must be wrong.
                if (def.numbers != null) {
                        for (Numbers n : def.numbers) {
                                if (n.getLeftNumberStyle() != NumberStyle.NONE) {
                                        if (n.getLeftStart() <= 0 || n.getLeftEnd() <= 0) {
                                                error("housenumbers: zero or negative: %s %d: left=%d,%d", def.name, def.netoff, n.getLeftStart(), n.getLeftEnd());
                                        }
                                }
                                if (n.getRightNumberStyle() != NumberStyle.NONE) {
                                        if (n.getRightStart() <= 0 || n.getRightEnd() <= 0) {
                                                error("housenumbers: zero or negative: %s %d: right=%d,%d", def.name, def.netoff, n.getRightStart(), n.getRightEnd());
                                        }
                                }
                        }
                }
        }

        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.get2u();
                        else
                                reader.get();
                        break;
                case 1:
                        int n = reader.get2u();
                        reader.get(n);
                        break;
                case 0:
                        n = reader.get1u();
                        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.get1u();
                                div.div = reader.get2u();
                                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 nodeIndex;
                private List<Numbers> numbers;

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

        private class Name {
                private final String name;
                private int prefix;
                private int suffix;
                private int b;
                private int s;
                private boolean hasOrders;

                Name(String name) {
                        this.name = name;

                        if (name.charAt(0) < 7)
                                prefix = 1;

                        int sep = name.indexOf(0x1e);
                        if (sep < 0)
                                sep = name.indexOf(0x1b);

                        if (sep > 0)
                                prefix = sep + 1;

                        sep = name.indexOf(0x1f);
                        if (sep < 0)
                                sep = name.indexOf(0x1c);
                        if (sep > 0)
                                suffix = sep;
                }

                public String getName() {
                        return name;
                }

                String partialName() {
                        if (suffix > 0)
                                return name.substring(prefix, suffix);
                        else
                                return name.substring(prefix);
                }

                private String initialPart() {
                        int off = prefix;
                        if (off == 0)
                                return "";

                        return name.substring(0, off);
                }

                private String suffixPart() {
                        if (suffix == 0)
                                return "";
                        return name.substring(suffix, name.length());
                }

                public boolean equals(Object o) {
                        if (this == o)
                                return true;
                        if (o == null || getClass() != o.getClass())
                                return false;

                        Name name1 = (Name) o;

                        if (!name.equals(name1.name))
                                return false;

                        return true;
                }

                public int getB() {
                        return b;
                }

                public void setB(int b) {
                        this.b = b;
                }

                public int getS() {
                        return s;
                }

                public void setS(int s) {
                        this.s = s;
                }

                public int hashCode() {
                        return name.hashCode();
                }

                public String toString() {
                        return name;
                }

        }

}