Rev 2479 | 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.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.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.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) {
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);
}
protected void addNet() throws FileExistsException {
netFile = new NETFile(fileSystem.create(mapName + ".NET"));
}
protected 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 can be used to save any old junk it seems.
*
* @param info Any string.
*/
public void addInfo(String info) {
treFile.addInfo(info);
}
/**
* 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;
}
}