Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2009.
 *
 * 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 uk.me.parabola.imgfmt.app.net;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFile;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.lbl.LBLFileReader;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.fs.ImgChannel;

/**
 * Read the NET file.
 */

public class NETFileReader extends ImgFile {
        private final NETHeader netHeader = new NETHeader();

        // To begin with we only need LBL offsets.
        private final Map<Integer, Integer> offsetLabelMap = new HashMap<Integer, Integer>();
        private List<Integer> offsets;

        private List<City> cities;
        private int citySize;

        private List<Zip> zips;
        private int zipSize;
        private LBLFileReader labels;

        public NETFileReader(ImgChannel chan) {
                setHeader(netHeader);

                setReader(new BufferedImgFileReader(chan));
                netHeader.readHeader(getReader());

                readLabelOffsets();
        }

        /**
         * Get the label offset, given the NET offset.
         * @param netOffset An offset into NET 1, as found in the road entries in
         * RGN for example.
         * @return The offset into LBL as found in NET 1.
         */

        public int getLabelOffset(int netOffset) {
                Integer off = offsetLabelMap.get(netOffset);
                if (off == null)
                        return 0;
                else
                        return off;
        }

        /**
         * Get the list of roads from the net section.
         *
         * Saving the bare minimum that is needed, please improve.
         * @return A list of RoadDefs. Note that currently not everything is
         * populated in the road def so it can't be written out as is.
         */

        public List<RoadDef> getRoads() {
                ImgFileReader reader = getReader();
                int start = netHeader.getRoadDefinitionsStart();

                List<RoadDef> roads = new ArrayList<RoadDef>();
                int record = 0;
                for (int off : offsets) {
                        reader.position(start + off);

                        RoadDef road = new RoadDef(++record, off, null);
                        readLabels(reader, road);
                        byte netFlags = reader.get();
                        /*int len =*/ reader.getu3();

                        int[] counts = new int[24];
                        int level = 0;
                        while (level < 24) {
                                int n = reader.get();
                                counts[level++] = (n & 0x7f);
                                if ((n & 0x80) != 0)
                                        break;
                        }

                        for (int i = 0; i < level; i++) {
                                int c = counts[i];
                                for (int j = 0; j < c; j++) {
                                        /*byte b =*/ reader.get();
                                        /*char sub =*/ reader.getChar();
                                }
                        }

                        if ((netFlags & RoadDef.NET_FLAG_ADDRINFO) != 0) {
                                char flags2 = reader.getChar();

                                int zipFlag = (flags2 >> 10) & 0x3;
                                int cityFlag = (flags2 >> 12) & 0x3;
                                int numberFlag = (flags2 >> 14) & 0x3;

                                road.setZip(fetchZipCity(reader, zipFlag, zips, zipSize));
                                road.setCity(fetchZipCity(reader, cityFlag, cities, citySize));

                                fetchNumber(reader, numberFlag);
                        }

                        if ((netFlags & RoadDef.NET_FLAG_NODINFO) != 0) {
                                int nodFlags = reader.get();
                                int nbytes = nodFlags & 0x3;
                                if (nbytes > 0) {
                                        /*int nod = */reader.getUint(nbytes+1);
                                }
                        }

                        roads.add(road);
                }
                return roads;
        }

        /**
         * Fetch a zip or a city.
         * @param <T> Can be city or zip.
         * @return The found City or Zip.
         */

        private <T> T fetchZipCity(ImgFileReader reader, int flag, List<T> list, int size) {
                T item = null;
                if (flag == 2) {
                        // fetch city/zip index
                        int ind = (size == 2)? reader.getChar(): (reader.get() & 0xff);
                        if (ind != 0)
                                item = list.get(ind-1);
                } else if (flag == 3) {
                        // there is no item
                } else if (flag == 0) {
                        // Skip over these
                        int n = reader.get();
                        reader.get(n);
                } else if (flag == 1) {
                        // Skip over these
                        int n = reader.getChar();
                        reader.get(n);
                } else {
                        assert false : "flag is " + flag;
                }
                return item;
        }

        /**
         * Fetch a block of numbers.
         * @param reader The reader.
         * @param numberFlag The flag that says how the block is formatted.
         */

        private void fetchNumber(ImgFileReader reader, int numberFlag) {
                int n = 0;
                if (numberFlag == 0) {
                        n = reader.get();
                } else if (numberFlag == 1) {
                        n = reader.getChar();
                } else if (numberFlag == 3) {
                        // There is no block
                        return;
                } else {
                        // Possible but don't know what to do in this context
                        assert false;
                }
                if (n > 0)
                        reader.get(n);
        }

        private void readLabels(ImgFileReader reader, RoadDef road) {
                for (int i = 0; i < 4; i++) {
                        int lab = reader.getu3();
                        Label label = labels.fetchLabel(lab & 0x7fffff);
                        road.addLabel(label);
                        if ((lab & 0x800000) != 0)
                                break;
                }
        }

        /**
         * The first field in NET 1 is a label offset in LBL.  Currently we
         * are only interested in that to convert between a NET 1 offset and
         * a LBL offset.
         */

        private  void readLabelOffsets() {
                ImgFileReader reader = getReader();
                offsets = readOffsets();
                int start = netHeader.getRoadDefinitionsStart();
                for (int off : offsets) {
                        reader.position(start + off);
                        int labelOffset = reader.getu3();
                        // TODO what if top bit is not set?, there can be more than one name and we will miss them
                        offsetLabelMap.put(off, labelOffset & 0x7fffff);
                }
        }

        /**
         * NET 3 contains a list of all the NET 1 record start positions.  They
         * are in alphabetical order of name.  So read them in and sort into
         * memory address order.
         * @return A list of start offsets in NET 1, sorted by increasing offset.
         */

        private List<Integer> readOffsets() {
                int start = netHeader.getSortedRoadsStart();
                int end = netHeader.getSortedRoadsEnd();
                ImgFileReader reader = getReader();
                reader.position(start);

                List<Integer> offsets = new ArrayList<Integer>();
                while (reader.position() < end) {
                        int net1 = reader.getu3();

                        // The offset is stored in the bottom 22 bits. The top 2 bits are an index into the list
                        // of lbl pointers in the net1 entry. Since we pick up all the labels at a particular net1
                        // entry we only need one of the offsets so pick the first one.
                        int idx = (net1 >> 22) & 0x3;
                        if (idx == 0)
                                offsets.add((net1 & 0x3fffff) << netHeader.getRoadShift());
                }

                // Sort in address order in the hope of speeding up reading.
                Collections.sort(offsets);
                return offsets;
        }

        public void setCities(List<City> cities) {
                this.cities = cities;
                this.citySize = cities.size() > 255? 2: 1;
        }

        public void setZips(List<Zip> zips) {
                this.zips = zips;
                this.zipSize = zips.size() > 255? 2: 1;
        }

        public void setLabels(LBLFileReader labels) {
                this.labels = labels;
        }
}