/* * 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.map; import uk.me.parabola.imgfmt.FileExistsException; import uk.me.parabola.imgfmt.FileNotWritableException; import uk.me.parabola.imgfmt.FileSystemParam; import uk.me.parabola.imgfmt.Utils; import uk.me.parabola.imgfmt.app.Area; import uk.me.parabola.imgfmt.app.ImgFile; import uk.me.parabola.imgfmt.app.Label; import uk.me.parabola.imgfmt.app.labelenc.CodeFunctions; import uk.me.parabola.imgfmt.app.lbl.LBLFile; import uk.me.parabola.imgfmt.app.net.NETFile; import uk.me.parabola.imgfmt.app.net.NODFile; import uk.me.parabola.imgfmt.app.srt.Sort; import uk.me.parabola.imgfmt.app.trergn.InternalFiles; import uk.me.parabola.imgfmt.app.trergn.MapObject; import uk.me.parabola.imgfmt.app.trergn.PointOverview; import uk.me.parabola.imgfmt.app.trergn.PolygonOverview; import uk.me.parabola.imgfmt.app.trergn.PolylineOverview; import uk.me.parabola.imgfmt.app.trergn.RGNFile; import uk.me.parabola.imgfmt.app.trergn.Subdivision; import uk.me.parabola.imgfmt.app.trergn.TREFile; import uk.me.parabola.imgfmt.app.trergn.Zoom; import uk.me.parabola.imgfmt.fs.DirectoryEntry; import uk.me.parabola.imgfmt.fs.FileSystem; import uk.me.parabola.imgfmt.sys.ImgFS; import uk.me.parabola.log.Logger; import uk.me.parabola.mkgmap.combiners.OverviewBuilder; import uk.me.parabola.util.Configurable; import uk.me.parabola.util.EnhancedProperties; /** * Holder for a complete map. A map is made up of several files which * include at least the TRE, LBL and RGN files. * * It is the interface for all information about the whole map, such as the * point overviews etc. Subdivision will hold the map elements. * * <p>Needless to say, it has nothing to do with java.util.Map and given * how it has turned out, with all reading functionality in MapReader * it would have been better named MapWriter. * * @author Steve Ratcliffe */ public class Map implements InternalFiles, Configurable { private static final Logger log = Logger.getLogger(Map.class); private String filename; private String mapName; private FileSystem fileSystem; private TREFile treFile; private RGNFile rgnFile; private LBLFile lblFile; private NETFile netFile; private NODFile nodFile; // Use createMap() or loadMap() instead of creating a map directly. private Map() { } /** * Create a complete map. This consists of (at least) three * files that all have the same basename and different extensions. * * @param mapname The name of the map. This is an 8 digit number as a * string. * @param params Parameters that describe the file system that the map * will be created in. * @return A map object that holds together all the files that make it up. * @throws FileExistsException If the file already exists and we do not * want to overwrite it. * @throws FileNotWritableException If the file cannot * be opened for write. */ public static Map createMap(String mapname, String outputdir, FileSystemParam params, String mapnumber, Sort sort) throws FileExistsException, FileNotWritableException { Map m = new Map(); m.mapName = mapname; String outFilename = Utils.joinPath(outputdir, mapname, "img"); FileSystem fs = ImgFS.createFs(outFilename, params); m.filename = outFilename; m.fileSystem = fs; m.rgnFile = new RGNFile(m.fileSystem.create(mapnumber + ".RGN")); m.treFile = new TREFile(m.fileSystem.create(mapnumber + ".TRE")); m.lblFile = new LBLFile(m.fileSystem.create(mapnumber + ".LBL"), sort); int mapid; try { mapid = Integer.parseInt(mapnumber); } catch (NumberFormatException e) { mapid = 0; } m.treFile.setMapId(mapid); m.fileSystem = fs; return m; } public void config(EnhancedProperties props) { // we don't want routing info in the overview map (for now) if (!OverviewBuilder.isOverviewImg(mapName)){ try { if (props.containsKey("route")) { addNet(); addNod(); } else if (props.containsKey("net")) { addNet(); } } catch (FileExistsException e) { log.warn("Could not add NET and/or NOD sections"); } } treFile.config(props); } private void addNet() throws FileExistsException { netFile = new NETFile(fileSystem.create(mapName + ".NET")); } private void addNod() throws FileExistsException { nodFile = new NODFile(fileSystem.create(mapName + ".NOD"), true); } /** * Set the area that the map covers. * @param area The outer bounds of the map. */ public void setBounds(Area area) { treFile.setBounds(area); } /** * Add a copyright message to the map. * @param str the copyright message. The second (last?) one set * gets shown when the device starts (sometimes?). */ public void addCopyright(String str) { Label cpy = lblFile.newLabel(str); treFile.addCopyright(cpy); } /** * There is an area after the TRE header and before its data * starts that is used to save licence info. * * It seems that this must follow the code page of the LBL file. The format6 encoding is not allowed * however. * * @param msg Any string. */ public void addInfo(String msg) { int codePage = lblFile.getCodePage(); CodeFunctions functions = CodeFunctions.createEncoderForLBL(0, codePage); treFile.addInfo(functions.getEncoder().encodeText(msg)); } /** * Create a new zoom level. The level 0 is the most detailed and * level 15 is the most general. Most maps would just have 4 * different levels or less. We are just having two to start with * but will probably advance to at least 3. * * @param level The zoom level, and integer between 0 and 15. Its * like a logical zoom level. * @param bits The number of bits per coordinate, a measure of * the actual amount of detail that will be in the level. So this * is like a physical zoom level. * @return The zoom object. */ public Zoom createZoom(int level, int bits) { return treFile.createZoom(level, bits); } /** * Create the top level division. It must be empty afaik and cover * the whole area of the map. * * @param area The whole map area. * @param zoom The zoom level that you want the top level to be * at. Its going to be at least level 1. * @return The top level division. */ public Subdivision topLevelSubdivision(Area area, Zoom zoom) { zoom.setInherited(true); // May not always be necessary/desired InternalFiles ifiles = this; Subdivision sub = Subdivision.topLevelSubdivision(ifiles, area, zoom); rgnFile.startDivision(sub); return sub; } /** * Create a subdivision that is beneath the top level. We have to * pass the parent division. * <p> * Note that you cannot create these all up front. You must * create it, fill it will its map elements and then create the * next one. You must also start at the top level and work down. * * @param parent The parent subdivision. * @param area The area of the new child subdiv. * @param zoom The zoom level of the child. * @return The new division. */ public Subdivision createSubdivision(Subdivision parent, Area area, Zoom zoom) { log.debug("creating division"); return parent.createSubdivision(this, area, zoom); } public void addPointOverview(PointOverview ov) { treFile.addPointOverview(ov); } public void addPolylineOverview(PolylineOverview ov) { treFile.addPolylineOverview(ov); } public void addPolygonOverview(PolygonOverview ov) { treFile.addPolygonOverview(ov); } /** * Adds the bits to the point of interest flags. * @param flags The POI flags. */ public void addPoiDisplayFlags(int flags) { treFile.addPoiDisplayFlags((byte) flags); } public void addMapObject(MapObject item) { rgnFile.addMapObject(item); } public void setSort(Sort sort) { lblFile.setSort(sort); if (netFile != null) netFile.setSort(sort); } public void setLabelCharset(String desc, boolean forceUpper) { lblFile.setCharacterType(desc, forceUpper); } /** * Close this map by closing all the constituent files. * * Some history: */ public void close() { ImgFile[] files = { rgnFile, treFile, lblFile, netFile, nodFile }; int headerSlotsRequired = 0; FileSystemParam param = fileSystem.fsparam(); int blockSize = param.getBlockSize(); for (ImgFile f : files) { if (f == null) continue; long len = f.getSize(); log.debug("img file len=", len); // Blocks required for this file int nBlocks = (int) ((len + blockSize - 1) / blockSize); // Now we calculate how many directory blocks we need, you have // to round up as files do not share directory blocks. headerSlotsRequired += (nBlocks + DirectoryEntry.SLOTS_PER_ENTRY - 1)/DirectoryEntry.SLOTS_PER_ENTRY; } log.debug("header slots required", headerSlotsRequired); // A header slot is always 512 bytes, so we need to calculate the // number of blocks if the block-size is different. // There are 2 slots for the header itself. int blocksRequired = 2 + headerSlotsRequired * 512 / blockSize; param.setReservedDirectoryBlocks(blocksRequired); fileSystem.fsparam(param); for (ImgFile f : files) Utils.closeFile(f); fileSystem.close(); } public String getFilename() { return filename; } public RGNFile getRgnFile() { return rgnFile; } public LBLFile getLblFile() { return lblFile; } public TREFile getTreFile() { return treFile; } public NETFile getNetFile() { return netFile; } public NODFile getNodFile() { return nodFile; } }