/* * 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()); l.write(getWriter(), encodedText); 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 POIIndex createPOIIndex(String name, int poiIndex, Subdivision group, int type) { return 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 getCodePage() { return lblHeader.getCodePage(); } }