Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2006 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 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.
 *
 *
 * Author: Steve Ratcliffe
 * Create date: 03-Dec-2006
 */

package uk.me.parabola.imgfmt.app.lbl;

import java.util.HashMap;
import java.util.Map;

import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.BufferedImgFileWriter;
import uk.me.parabola.imgfmt.app.Exit;
import uk.me.parabola.imgfmt.app.ImgFile;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.labelenc.BaseEncoder;
import uk.me.parabola.imgfmt.app.labelenc.CharacterEncoder;
import uk.me.parabola.imgfmt.app.labelenc.CodeFunctions;
import uk.me.parabola.imgfmt.app.labelenc.EncodedText;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;

/**
 * The file that holds all the labels for the map.
 *
 * Would be quite simple, but there are a number of sections that hold country,
 * region, city, etc. records.
 *
 * To begin with I shall only support regular labels.
 *
 * @author Steve Ratcliffe
 */

public class LBLFile extends ImgFile {
        private static final Logger log = Logger.getLogger(LBLFile.class);

        private CharacterEncoder textEncoder = CodeFunctions.getDefaultEncoder();

        private final Map<EncodedText, Label> labelCache = new HashMap<>();

        private final LBLHeader lblHeader = new LBLHeader();

        private final PlacesFile places = new PlacesFile();
        private Sort sort;

        // Shift value for the label offset.
        private final int offsetMultiplier = 1;

        public LBLFile(ImgChannel chan, Sort sort) {
                this.sort = sort;
                lblHeader.setSort(sort);
                lblHeader.setOffsetMultiplier(offsetMultiplier);
                setHeader(lblHeader);

                setWriter(new BufferedImgFileWriter(chan));

                position(LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength());

                // The zero offset is for no label.
                getWriter().put((byte) 0);
                alignForNext();

                places.init(this, lblHeader.getPlaceHeader());
                places.setSort(sort);
                labelCache.put(BaseEncoder.NO_TEXT, Label.NULL_OUT_LABEL);
        }

        public void write() {
                writeBody();
        }

        public void writePost() {
                // Now that the body is written all the required offsets will be set up
                // inside the header, so we can go back and write it.
                ImgFileWriter writer = getWriter();
                getHeader().writeHeader(writer);

                // Text can be put between the header and the body of the file.
                writer.put(Utils.toBytes(sort.getDescription()));
                writer.put((byte) 0);
                assert writer.position() == LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength();
        }

        private void writeBody() {
                // The label section has already been written, but we need to record
                // its size before doing anything else.
                lblHeader.setLabelSize(getWriter().position() - (LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength()));
                places.write(getWriter());
        }

        public void setCharacterType(String cs, boolean forceUpper) {
                log.info("encoding type " + cs);
                CodeFunctions cfuncs = CodeFunctions.createEncoderForLBL(cs);
               
                lblHeader.setEncodingType(cfuncs.getEncodingType());
                textEncoder = cfuncs.getEncoder();
                if (forceUpper && textEncoder instanceof BaseEncoder) {
                        BaseEncoder baseEncoder = (BaseEncoder) textEncoder;
                        baseEncoder.setUpperCase(true);
                }
        }

        public void setEncoder(int encodingType, int codepage ) {
                CodeFunctions cfuncs = CodeFunctions.createEncoderForLBL(encodingType, codepage);
               
                lblHeader.setEncodingType(cfuncs.getEncodingType());
                textEncoder = cfuncs.getEncoder();
        }
       
        /**
         * Add a new label with the given text.  Labels are shared, so that identical
         * text is always represented by the same label.
         *
         * @param text The text of the label, it will be in uppercase.
         * @return A reference to the created label.
         */

        public Label newLabel(String text) {
                EncodedText encodedText = textEncoder.encodeText(text);

                Label l = labelCache.get(encodedText);
                if (l == null) {
                        l = new Label(encodedText.getChars());
                        labelCache.put(encodedText, l);

                        l.setOffset(getNextLabelOffset());
                        if (encodedText.getLength() > 0)
                                getWriter().put(encodedText.getCtext(), 0, encodedText.getLength());

                        alignForNext();

                        if (l.getOffset() > 0x3fffff)
                                throw new MapFailedException("Overflow of LBL section");
                }

                return l;
        }

        /**
         * Align for the next label.
         *
         * Only has any effect when offsetMultiplier is not zero.
         */

        private void alignForNext() {
                // Align ready for next label
                while ((getCurrentLabelOffset() & ((1 << offsetMultiplier) - 1)) != 0)
                        getWriter().put((byte) 0);
        }

        private int getNextLabelOffset() {
                return getCurrentLabelOffset() >> offsetMultiplier;
        }

        private int getCurrentLabelOffset() {
                return position() - (LBLHeader.HEADER_LEN + lblHeader.getSortDescriptionLength());
        }

        public POIRecord createPOI(String name) {
                return places.createPOI(name);
        }

        public POIRecord createExitPOI(String name, Exit exit) {
                return places.createExitPOI(name, exit);
        }

        public void createPOIIndex(String name, int poiIndex, Subdivision group, int type) {
                places.createPOIIndex(name, poiIndex, group, type);
        }
       
        public Country createCountry(String name, String abbr) {
                return places.createCountry(name, abbr);
        }
       
        public Region createRegion(Country country, String region, String abbr) {
            return places.createRegion(country, region, abbr);
        }
       
        public City createCity(Region region, String city, boolean unique) {
                return places.createCity(region, city, unique);
        }

        public City createCity(Country country, String city, boolean unique) {
                return places.createCity(country, city, unique);
        }

        public Zip createZip(String code) {
                return places.createZip(code);
        }

        public Highway createHighway(Region region, String name) {
                return places.createHighway(region, name);
        }

        public ExitFacility createExitFacility(int type, char direction, int facilities, String description, boolean last) {
                return places.createExitFacility(type, direction, facilities, description, last);
        }

        public void allPOIsDone() {
                places.allPOIsDone();
        }

        public void setSort(Sort sort) {
                this.sort = sort;
                lblHeader.setSort(sort);
                places.setSort(sort);
        }

        public int numCities() {
                return places.numCities();
        }

        public int numZips() {
                return places.numZips();
        }

        public int numHighways() {
                return places.numHighways();
        }

        public int numExitFacilities() {
                return places.numExitFacilities();
        }

        public int getCodePage() {
                return lblHeader.getCodePage();
        }
}