mkgmap-r3660/ 0000755 0000764 0000764 00000000000 12651103764 012163 5 ustar steve steve mkgmap-r3660/extra/ 0000755 0000764 0000764 00000000000 12651103762 013304 5 ustar steve steve mkgmap-r3660/extra/README 0000644 0000764 0000764 00000000154 11237536256 014173 0 ustar steve steve
This directory contains code that requires external libraries
to work and so is not part of the core code.
mkgmap-r3660/extra/src/ 0000755 0000764 0000764 00000000000 12651103762 014073 5 ustar steve steve mkgmap-r3660/extra/src/uk/ 0000755 0000764 0000764 00000000000 12651103762 014512 5 ustar steve steve mkgmap-r3660/extra/src/uk/me/ 0000755 0000764 0000764 00000000000 12651103762 015113 5 ustar steve steve mkgmap-r3660/extra/src/uk/me/parabola/ 0000755 0000764 0000764 00000000000 12651103762 016674 5 ustar steve steve mkgmap-r3660/extra/src/uk/me/parabola/mkgmap/ 0000755 0000764 0000764 00000000000 12651103762 020150 5 ustar steve steve mkgmap-r3660/extra/src/uk/me/parabola/mkgmap/ant/ 0000755 0000764 0000764 00000000000 12651103762 020732 5 ustar steve steve mkgmap-r3660/extra/src/uk/me/parabola/mkgmap/ant/MKGMapTask.java 0000644 0000764 0000764 00000004113 11713757443 023504 0 ustar steve steve /*
* Copyright (C) 2009 Christian Gawron
*
* 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: Christian Gawron
* Create date: 19-Jun-2009
*/
package uk.me.parabola.mkgmap.ant;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.mkgmap.CommandArgsReader;
import uk.me.parabola.mkgmap.main.Main;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
/**
* This class provides an ant task for mkgmap.
* Used like this:
*
*
*/
@SuppressWarnings({"UnusedDeclaration"})
public class MKGMapTask extends Task {
private final ArrayList paths = new ArrayList();
private String configFile;
public void addPath(Path path) {
paths.add(path);
}
public void setOptions(String configFile) {
this.configFile = configFile;
}
public void execute() {
List args = new ArrayList();
try {
CommandArgsReader argsReader = new CommandArgsReader(new Main());
if (configFile != null)
args.add("--read-config=" + configFile);
for (Path path : paths) {
String[] includedFiles = path.list();
for (String filename : includedFiles) {
log("processing " + filename);
args.add("--input-file=" + filename);
}
}
argsReader.readArgs(args.toArray(new String[args.size()]));
} catch (Exception ex) {
//log(ex, 1);
throw new BuildException(ex);
}
}
}
mkgmap-r3660/extra/src/uk/me/parabola/util/ 0000755 0000764 0000764 00000000000 12651103762 017651 5 ustar steve steve mkgmap-r3660/extra/src/uk/me/parabola/util/TableIcuCreator.java 0000644 0000764 0000764 00000006173 11400726372 023532 0 ustar steve steve /*
* 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.util;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import uk.me.parabola.imgfmt.Utils;
import com.ibm.icu.text.Transliterator;
/**
* Call this with a unicode row number and it will produce an empty table
* that can be modified.
*
* @author Steve Ratcliffe
*/
public class TableIcuCreator {
private static Transliterator trans;
public static void main(String[] args) {
int count = 0;
Enumeration targets = Transliterator.getAvailableIDs();
while (targets.hasMoreElements()) {
String s = targets.nextElement();
System.out.println(s);
count++;
}
System.out.println("number " + count);
//System.exit(0);
//trans = Transliterator.getInstance("Any-en_US; nfd; [\u0301\u0302\u0304\u0306\u0307\u0308\u030c\u0328] remove; nfc"); // [:nonspacing mark:] remove; nfc");
trans = Transliterator.getInstance("Any-Latin"); // [:nonspacing mark:] remove; nfc");
for (int row = 0; row < 256; row++) {
String name = String.format("row%02x.trans", row);
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter(name));
printRow(out, row);
} catch (IOException e) {
System.out.println("Could not open " + name + " for write");
} catch (UselessException e) {
//System.out.println("Deleting " + name);
File f = new File(name);
f.delete();
} finally {
Utils.closeFile(out);
}
}
}
private static void printRow(PrintWriter out, int row) throws UselessException {
out.println("#");
out.println("# This is a table for transliterating characters.");
out.println("# It was created using icu4j");
out.println("#");
out.println("# All resulting strings that contained characters outside the");
out.println("# range of iso 8859-1 are commented out");
out.println("#");
out.println();
int count = 0;
for (int i = 0; i < 256; i++) {
char c = (char) ((row << 8) + i);
String single = "" + c;
String result = trans.transliterate(single);
if (result.length() == 1 && result.charAt(0) == c)
result = "?";
else
count++;
boolean inRange = true;
for (char rc : result.toCharArray()) {
if (rc > 0xff) {
//System.out.printf("out of range result %c for row %d\n", rc, row);
inRange = false;
break;
}
}
if (!inRange) {
count--;
out.print("#");
}
out.format("U+%02x%02x %-12.12s # Character %s", row, i, result, single);
out.println();
}
if (count == 0)
throw new UselessException("count of 0");
}
private static class UselessException extends Exception {
private UselessException(String msg) {
super(msg);
}
}
} mkgmap-r3660/extra/src/uk/me/parabola/util/CollationRules.java 0000644 0000764 0000764 00000024571 12346663602 023471 0 ustar steve steve /*
* Copyright (C) 2014.
*
* 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.util;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.ibm.icu.text.CollationElementIterator;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RuleBasedCollator;
//import java.text.CollationElementIterator;
//import java.text.Collator;
//import java.text.RuleBasedCollator;
/**
* Create a set of rules for a given code page.
*
* Should be usable, perhaps with a few tweaks.
* Works with unicode too, need to choose which blocks to take for unicode.
*
* @author Steve Ratcliffe
*/
public class CollationRules {
private CharsetDecoder decoder;
private final NavigableSet positionMap = new TreeSet<>();
private final NavigableSet basePositionMap = new TreeSet<>();
private final Map charMap = new HashMap<>();
private boolean isUnicode;
private Charset charset;
public static void main(String[] args) {
String charsetName = args[0];
CollationRules main = new CollationRules();
main.go(charsetName);
}
private void go(String charsetName) {
RuleBasedCollator col = (RuleBasedCollator) Collator.getInstance();
charset = Charset.forName(charsetName);
if (charsetName.equalsIgnoreCase("utf-8"))
isUnicode = true;
decoder = charset.newDecoder();
if (isUnicode)
addUnicode();
else
addBlock(col, 0);
printCharMap();
printExpansions();
}
private void addBlock(RuleBasedCollator col, int block) {
for (int i = 0; i < 0x100; i++) {
int ch = (block << 8) + i;
String testString = getString(ch);
char conv = testString.charAt(0);
if (Character.getType(conv) == Character.UNASSIGNED || conv == 0xfffd)
continue;
CollationElementIterator it = col.getCollationElementIterator(testString);
System.out.printf("# %s ", fmtChar(testString.charAt(0)));
int next;
int index = 0;
CharPosition cp = new CharPosition(0);
while ((next = it.next()) != CollationElementIterator.NULLORDER) {
if (index == 0) {
cp = new CharPosition(ch);
cp.setOrder(next);
} else {
assert index < 3;
if ((next & 0xffff0000) == 0) {
cp.addOrder(next, index);
} else {
cp.addChar(new CharPosition(ch));
cp.setOrder(next);
}
}
index++;
}
System.out.printf(" %s %d", cp, Character.getType(cp.getUnicode()));
System.out.println();
tweak(cp);
if (ch > 0)
positionMap.add(cp);
if (cp.nextChar == null) {
basePositionMap.add(cp);
charMap.put(conv, cp);
}
}
}
private void addUnicode() {
Pattern pat = Pattern.compile("([0-9A-F]{4,5}) ? ; \\[[.*](.*)\\] #.*");
try (FileReader r = new FileReader("allkeys.txt")) {
try (BufferedReader br = new BufferedReader(r)) {
String line;
while ((line = br.readLine()) != null) {
Matcher matcher = pat.matcher(line);
if (matcher.matches()) {
String weights = matcher.group(2);
int ch = Integer.parseInt(matcher.group(1), 16);
if (ch > 0xffff)
continue;
System.out.printf("# %04x %s ", ch, fmtChar(ch));
String[] split = weights.split("]\\[[.*]");
int index = 0;
CharPosition cp = new CharPosition(0);
for (String s : split) {
String[] ws = s.split("\\.");
int next = Integer.parseInt(ws[0], 16) << 16
| ((Integer.parseInt(ws[1], 16) << 8) & 0xff00)
| ((Integer.parseInt(ws[2], 16)) & 0xff);
if (index == 0) {
cp = new CharPosition(ch);
cp.setOrder(next);
} else {
if ((next & 0xffff0000) == 0) {
cp.addOrder(next, index);
} else {
cp.addChar(new CharPosition(ch));
cp.setOrder(next);
}
}
index++;
}
System.out.printf(" %s %d\n", cp, Character.getType(cp.getUnicode()));
tweak(cp);
if (ch > 0)
positionMap.add(cp);
if (cp.nextChar == null) {
basePositionMap.add(cp);
charMap.put((char) ch, cp);
}
} else {
System.out.println("# NOMATCH: " + line);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Fix up a few characters that we always want to be in well known places.
*
* @param cp The position to change.
*/
private void tweak(CharPosition cp) {
if (cp.val < 8)
cp.third = cp.val + 7;
if (!isUnicode) {
switch (cp.getUnicode()) {
case '¼':
cp.nextChar = charMap.get('/').copy();
cp.nextChar.nextChar = charMap.get('4');
break;
case '½':
cp.nextChar = charMap.get('/').copy();
cp.nextChar.nextChar = charMap.get('2');
break;
case '¾':
cp.nextChar = charMap.get('/').copy();
cp.nextChar.nextChar = charMap.get('4');
break;
}
}
switch (cp.getUnicode()) {
case '˜':
CharPosition tilde = charMap.get('~');
cp.first = tilde.first;
cp.second = tilde.second + 1;
cp.third = tilde.third + 1;
cp.nextChar = null;
break;
}
}
private String getString(int i) {
if (isUnicode)
return new String(new char[]{(char) i});
else {
byte[] b = {(byte) i};
return new String(b, 0, 1, charset);
}
}
private void printCharMap() {
Formatter chars = new Formatter();
chars.format("\n");
CharPosition last = new CharPosition(0);
last.first = 0;
for (CharPosition cp : positionMap) {
if (cp.isExpansion())
continue;
if (cp.first != last.first) {
chars.format("\n < ");
} else if (cp.second != last.second) {
chars.format(" ; ");
} else if (cp.third != last.third) {
chars.format(",");
} else {
chars.format("=");
}
last = cp;
int uni = toUnicode(cp.val);
chars.format("%s", fmtChar(uni));
}
System.out.println(chars);
}
private void printExpansions() {
for (CharPosition cp : positionMap) {
if (!cp.isExpansion())
continue;
Formatter fmt = new Formatter();
//noinspection MalformedFormatString
fmt.format("expand %c to", cp.getUnicode());
boolean ok = true;
for (CharPosition cp2 = cp; cp2 != null; cp2 = cp2.nextChar) {
cp2.second = 0x50000;
int top = (cp2.third >> 16) & 0xff;
cp2.third = (top == 0x9e || top == 0xa2 || top == 0x2b) ? 0x9b0000 : 0;
CharPosition floor = basePositionMap.ceiling(cp2);
if (floor == null || floor.getUnicode() == 0xfffd) {
fmt.format(" NF");
ok = false;
} else {
fmt.format(" %s", fmtChar(floor.getUnicode()));
}
}
System.out.println((ok ? "" : "# ") + fmt.toString());
// Print comments to help find problems.
for (CharPosition cp2 = cp; cp2 != null; cp2 = cp2.nextChar) {
CharPosition floor = basePositionMap.ceiling(cp2);
if (floor == null) {
System.out.println("#FIX: NF ref=" + cp2);
} else {
System.out.println("#floor is " + fmtChar(toUnicode(floor.val)) + ", " + floor + ", ref is " + cp2);
}
}
}
}
private String fmtChar(int val) {
boolean asChar = true;
switch (val) {
case '<':
case ';':
case ',':
case '=':
case '#':
asChar = false;
break;
default:
switch (Character.getType(val)) {
case Character.UNASSIGNED:
case Character.NON_SPACING_MARK:
case Character.FORMAT:
case Character.CONTROL:
case Character.SPACE_SEPARATOR:
case Character.LINE_SEPARATOR:
case Character.PARAGRAPH_SEPARATOR:
asChar = false;
}
}
if (asChar) {
//noinspection MalformedFormatString
return String.format("%c", val);
} else {
return String.format("%04x", val);
}
}
private int toUnicode(int c) {
if (isUnicode)
return c;
ByteBuffer b = ByteBuffer.allocate(1);
b.put((byte) c);
b.flip();
try {
CharBuffer chars = decoder.decode(b);
return chars.charAt(0);
} catch (CharacterCodingException e) {
return '?';
}
}
class CharPosition implements Comparable {
private final int val;
private int first;
private int second;
private int third;
private CharPosition nextChar;
public CharPosition(int charValue) {
this.val = charValue;
}
public int compareTo(CharPosition other) {
if (other.first == first)
return compareSecond(other);
else if (first < other.first)
return -1;
else
return 1;
}
private int compareSecond(CharPosition c2) {
if (c2.second == second)
return compareThird(c2);
else if (second < c2.second)
return -1;
else
return 1;
}
private int compareThird(CharPosition c2) {
if (third == c2.third)
return new Integer(val).compareTo(c2.val);
else if (third < c2.third)
return -1;
else
return 1;
}
public String toString() {
Formatter fmt = new Formatter();
toString(fmt);
return fmt.toString();
}
private void toString(Formatter fmt) {
fmt.format("[%04x %02x %02x]", first, second, third);
if (nextChar != null)
nextChar.toString(fmt);
}
public void setOrder(int next) {
if (nextChar != null) {
nextChar.setOrder(next);
return;
}
first = (next >> 16) & 0xffff;
second = (next << 8) & 0xff0000;
third = (next << 16) & 0xff0000;
}
public void addOrder(int next, int count) {
assert ((next >>> 16) & 0xffff) == 0;
if (this.nextChar != null) {
this.nextChar.addOrder(next, count);
return;
}
second += ((next >> 8) & 0xff) << (2-count)*8;
third += ((next) & 0xff) << (2-count)*8;
}
public boolean isExpansion() {
return nextChar != null;
}
public void addChar(CharPosition pos) {
if (nextChar != null) {
nextChar.addChar(pos);
return;
}
nextChar = pos;
}
public int getUnicode() {
return toUnicode(val);
}
public CharPosition copy() {
CharPosition cp = new CharPosition(this.val);
cp.first = this.first;
cp.second = this.second;
cp.third = this.third;
return cp;
}
}
}
mkgmap-r3660/README 0000644 0000764 0000764 00000005217 12101051755 013040 0 ustar steve steve
Making maps for Garmin GPS units
================================
The goal of the project is to take the map data from
OpenStreetMap
and to generate a map in the Garmin file format so
that it can be loaded onto Garmin GPS units.
The original motivation was to help plan mapping sessions,
but now the maps are becoming useful in their own right.
Using
=====
This program requires Java 1.6 or above to run.
Producing a map is simple. Save OpenStreetMap data from JOSM
or by any other method to a file and copy it to the mkgmap
directory. In the following examples this file is called data.osm.
Run the command:
java -jar mkgmap.jar data.osm
This will produce a file called 63240001.img.
You can copy the map to your Garmin GPS unit in any way you know how.
It is best to use a SD card, since then if anything goes wrong you
can remove it from the unit and all should be well again.
Copy it to the file "Garmin/gmapsupp.img" on the card.
On many modern Garmin devices, you can use a different
name so that you can have more than one set of maps.
*NOTE* this will overwrite any other map you have on
there, make sure that you are not overwriting a valuable map.
Another way would be to use a USB memory card writer and
for a large map this is quicker as many GPS's have a slow USB
connection.
There are also various programs that can send a map to the
device.
You should (depending on the particular Garmin model) see a OSM
copyright message on starting up, and the map name 'OSM Street map'
should appear in the map setup section.
For more help see: http://www.mkgmap.org.uk/doc/index.html
Invoking mkgmap
--------------
Most of the default names mentioned in the previous section can be altered
by suitable options. Run
java -jar mkgmap.jar --help=options
to obtain an up to date and complete listing of options.
Processing more than one file at a time
---------------------------------------
The Garmin map format was designed so that a map is made of a number
of tiles, and if your map has more data than can fit into a single
tile, you will have to split the map.
See: http://www.mkgmap.org.uk/doc/splitter.html for a program that
can do this.
You can compile all of the map tiles that are created by splitter
all at once, by simply listing them all on the command line.
Acknowledgements
================
This project is almost entirely based on the file format specification
document written by John Mechalas at the SourceForge project at
http://sourceforge.net/projects/garmin-img. The 'imgdecode' program
from the same source was also very important in checking that I was
on the right lines to producing a good file. Thanks.
Steve
mkgmap-r3660/src/ 0000755 0000764 0000764 00000000000 12651103762 012750 5 ustar steve steve mkgmap-r3660/src/uk/ 0000755 0000764 0000764 00000000000 12651103762 013367 5 ustar steve steve mkgmap-r3660/src/uk/me/ 0000755 0000764 0000764 00000000000 12651103762 013770 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/ 0000755 0000764 0000764 00000000000 12651103764 015553 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/tdbfmt/ 0000755 0000764 0000764 00000000000 12651103762 017031 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/tdbfmt/Block.java 0000644 0000764 0000764 00000006132 10763340724 020733 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import uk.me.parabola.log.Logger;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.io.StructuredOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* A block within the tdb file. Really just a type and the contents.
*
* @author Steve Ratcliffe
*/
class Block {
private static final Logger log = Logger.getLogger(Block.class);
private final int blockId;
private int blockLength;
private byte[] body;
private StructuredInputStream istream;
private ByteArrayOutputStream arrayBody;
private StructuredOutputStream ostream;
/**
* Create a block that is going to be written to.
* @param blockId The id for this block.
*/
Block(int blockId) {
this.blockId = blockId;
}
/**
* Create a block from data that is read in from a file.
* @param type The block type.
* @param body The raw bytes in the block.
*/
Block(int type, byte[] body) {
blockId = type;
this.body = body;
this.blockLength = body.length;
ByteArrayInputStream stream = new ByteArrayInputStream(body);
this.istream = new StructuredInputStream(stream);
}
public int getBlockId() {
return blockId;
}
/**
* Get the raw bytes for this block. The source depends on if this block
* was constructed from file data, or is being created from program calls
* so that it can be written.
*
* @return A byte array of the raw bytes representing this block.
*/
byte[] getBody() {
if (body == null && arrayBody != null) {
byte[] bytes = arrayBody.toByteArray();
blockLength = bytes.length - 3;
// Fill in the length in the space that we left earlier.
bytes[1] = (byte) (blockLength & 0xff);
bytes[2] = (byte) ((blockLength >> 8) & 0xff);
return bytes;
}
return body;
}
/**
* Get a stream for the body of this block.
*
* @return A structured stream that can be used to read the body of this
* block.
*/
public StructuredInputStream getInputStream() {
arrayBody = null;
return this.istream;
}
public StructuredOutputStream getOutputStream() {
if (ostream == null) {
arrayBody = new ByteArrayOutputStream();
body = null;
ostream = new StructuredOutputStream(arrayBody);
try {
ostream.write(blockId);
ostream.write2(0); // This will be filled in later.
} catch (IOException e) {
log.warn("failed writing to array");
}
}
return ostream;
}
public void write(OutputStream stream) throws IOException {
byte[] b = getBody();
if (b != null)
stream.write(b);
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/TdbFile.java 0000644 0000764 0000764 00000015114 12175724544 021217 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.io.EndOfFileException;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.log.Logger;
/**
* The TDB file. See the package documentation for more details.
*
* @author Steve Ratcliffe
*/
public class TdbFile {
private static final Logger log = Logger.getLogger(TdbFile.class);
public static final int TDB_V407 = 407;
private static final int BLOCK_OVERVIEW = 0x42;
private static final int BLOCK_HEADER = 0x50;
private static final int BLOCK_COPYRIGHT = 0x44;
private static final int BLOCK_DETAIL = 0x4c;
private static final int BLOCK_R = 0x52;
private static final int BLOCK_T = 0x54;
// The version number of the TDB format
private int tdbVersion;
// The blocks that go to make up the file.
private HeaderBlock headerBlock;
private CopyrightBlock copyrightBlock = new CopyrightBlock();
private OverviewMapBlock overviewMapBlock;
private final List detailBlocks = new ArrayList();
private final RBlock rblock = new RBlock();
private final TBlock tblock = new TBlock();
private String overviewDescription;
public TdbFile() {
}
public TdbFile(int tdbVersion) {
this.tdbVersion = tdbVersion;
}
/**
* Read in a TDB file from the disk.
*
* @param name The file name to load.
* @return A TdbFile instance.
* @throws IOException For problems reading the file.
*/
public static TdbFile read(String name) throws IOException {
TdbFile tdb = new TdbFile();
InputStream is = new BufferedInputStream(new FileInputStream(name));
try {
StructuredInputStream ds = new StructuredInputStream(is);
tdb.load(ds);
} finally {
is.close();
}
return tdb;
}
public void setProductInfo(int familyId, int productId,
short productVersion, String seriesName, String familyName, String overviewDescription,
byte enableProfile)
{
headerBlock = new HeaderBlock(tdbVersion);
headerBlock.setFamilyId((short) familyId);
headerBlock.setProductId((short) productId);
headerBlock.setProductVersion(productVersion);
headerBlock.setSeriesName(seriesName);
headerBlock.setFamilyName(familyName);
headerBlock.setEnableProfile(enableProfile);
this.overviewDescription = overviewDescription;
}
public void setCodePage(int codePage) {
headerBlock.setCodePage(codePage);
}
/**
* Add a copyright segment to the file.
* @param msg The message to add.
*/
public void addCopyright(String msg) {
CopyrightSegment seg = new CopyrightSegment(CopyrightSegment.CODE_COPYRIGHT_TEXT_STRING, 3, msg);
copyrightBlock.addSegment(seg);
}
/**
* Set the overview information. Basically the overall size of the map
* set.
* @param bounds The bounds for the map.
*/
public void setOverview(Area bounds, String number) {
overviewMapBlock = new OverviewMapBlock();
overviewMapBlock.setArea(bounds);
overviewMapBlock.setMapName(number);
overviewMapBlock.setDescription(overviewDescription);
}
/**
* Add a detail block. This describes and names one of the maps in the
* map set.
* @param detail The detail to add.
*/
public void addDetail(DetailMapBlock detail) {
detailBlocks.add(detail);
}
public void write(String name) throws IOException {
CheckedOutputStream stream = new CheckedOutputStream(
new BufferedOutputStream(new FileOutputStream(name)),
new CRC32());
if (headerBlock == null || overviewMapBlock == null)
throw new IOException("Attempting to write file without being fully set up");
try {
Block block = new Block(BLOCK_HEADER);
headerBlock.write(block);
block.write(stream);
block = new Block(BLOCK_COPYRIGHT);
copyrightBlock.write(block);
block.write(stream);
if (tdbVersion >= TDB_V407) {
block = new Block(BLOCK_R);
rblock.write(block);
block.write(stream);
}
block = new Block(BLOCK_OVERVIEW);
overviewMapBlock.write(block);
block.write(stream);
for (DetailMapBlock detail : detailBlocks) {
block = new Block(BLOCK_DETAIL);
detail.write(block);
block.write(stream);
}
if (tdbVersion >= TDB_V407) {
tblock.setSum(stream.getChecksum().getValue());
block = new Block(BLOCK_T);
tblock.write(block);
block.write(stream);
}
} finally {
stream.close();
}
}
/**
* Load from the given file name.
*
* @param ds The stream to read from.
* @throws IOException For problems reading the file.
*/
private void load(StructuredInputStream ds) throws IOException {
while (!ds.testEof()) {
Block block = readBlock(ds);
switch (block.getBlockId()) {
case BLOCK_HEADER:
headerBlock = new HeaderBlock(block);
log.info("header block seen", headerBlock);
break;
case BLOCK_COPYRIGHT:
log.info("copyright block");
copyrightBlock = new CopyrightBlock(block);
break;
case BLOCK_OVERVIEW:
overviewMapBlock = new OverviewMapBlock(block);
log.info("overview block", overviewMapBlock);
break;
case BLOCK_DETAIL:
DetailMapBlock db = new DetailMapBlock(block);
log.info("detail block", db);
detailBlocks.add(db);
break;
default:
log.warn("Unknown block in tdb file");
break;
}
}
}
/**
* The file is divided into blocks. This reads a single block.
*
* @param is The input stream.
* @return A block from the file.
* @throws IOException For problems reading the file.
*/
private Block readBlock(StructuredInputStream is) throws IOException {
int blockType = is.read();
if (blockType == -1)
throw new EndOfFileException();
int blockLength = is.read2();
byte[] body = new byte[blockLength];
int n = is.read(body);
if (n < 0)
throw new IOException("failed to read block");
return new Block(blockType, body);
}
public int getTdbVersion() {
return headerBlock.getTdbVersion();
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/RBlock.java 0000644 0000764 0000764 00000001676 11110054726 021054 0 ustar steve steve /*
* Copyright (C) 2008 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: 23-Jun-2008
*/
package uk.me.parabola.tdbfmt;
import java.io.IOException;
import uk.me.parabola.io.StructuredOutputStream;
/**
* @author Steve Ratcliffe
*/
public class RBlock {
private final String previewDescription = "Test preview map";
public void write(Block block) throws IOException {
StructuredOutputStream os = block.getOutputStream();
os.write(0xc3);
os.writeString(previewDescription);
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/TBlock.java 0000644 0000764 0000764 00000002563 11033117613 021051 0 ustar steve steve /*
* Copyright (C) 2008 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: 23-Jun-2008
*/
package uk.me.parabola.tdbfmt;
import java.io.IOException;
import uk.me.parabola.io.StructuredOutputStream;
/**
* @author Steve Ratcliffe
*/
public class TBlock {
private long sum;
public void write(Block block) throws IOException {
StructuredOutputStream os = block.getOutputStream();
// If you change A,B,C or D the maps
// will not load, you can change the rest without easily visible
// problems although I suppose they must do something.
//
// A,B,C,D is a standard crc32 sum of the rest of the file.
// (Andrzej Popowsk)
os.write2(0);
os.write((int) (sum >> 24)); // A
os.write3(0);
os.write3(0);
os.write((int) (sum >> 16)); // B
os.write2(0);
os.write((int) (sum >> 8)); // C
os.write4(0);
os.write((int) sum); // D
os.write2(0);
}
public void setSum(long sum) {
this.sum = sum;
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/HeaderBlock.java 0000644 0000764 0000764 00000007255 12175724544 022060 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import java.io.IOException;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.io.StructuredOutputStream;
/**
* The header block. Identifies the particular map set.
*
* @author Steve Ratcliffe
*/
class HeaderBlock {
/** The map family. */
private short familyId;
/** A unique number associated with the map product */
private short productId;
/** The version of TDB */
private final int tdbVersion;
/** The series name is an overall name eg 'US Topo' */
private String seriesName;
/** The version number of the map product */
private short productVersion;
/**
* Identifies a map within the series
* @see #seriesName
*/
private String familyName;
private byte enableProfile;
private int codePage;
HeaderBlock(int tdbVersion) {
this.tdbVersion = tdbVersion;
}
HeaderBlock(Block block) throws IOException {
StructuredInputStream ds = block.getInputStream();
productId = (short) ds.read2();
familyId = (short) ds.read2();
tdbVersion = ds.read2();
seriesName = ds.readString();
productVersion = (short) ds.read2();
familyName = ds.readString();
}
public void write(Block block) throws IOException {
StructuredOutputStream os = block.getOutputStream();
os.write2(productId);
os.write2(familyId);
os.write2(tdbVersion);
os.writeString(seriesName);
os.write2(productVersion);
os.writeString(familyName);
if (tdbVersion >= TdbFile.TDB_V407) {
// Unknown purpose
os.write(0);
os.write(0x12); // lowest map level
os.write(1);
os.write(1);
os.write(1);
os.write4(0);
os.write(0);
os.write(0x18); // highest routable? 19 no, 21 ok
os.write4(0);
os.write4(0);
os.write4(0);
os.write4(0);
os.write3(0);
os.write4(codePage);
os.write4(10000);
os.write(1); // map is routable
if (enableProfile == 1)
os.write(1); // map has profile information
else
os.write(0);
os.write(0); // map has DEM sub files
}
}
// good
//os.write(0);
//os.write(0x12);
//os.write(1);
//os.write(1);
//os.write(1);
//os.write4(0);
//os.write(0);
//os.write(0x15);
//os.write4(0);
//os.write4(0);
//os.write4(0);
//os.write4(0);
//os.write3(0);
//os.write4(1252);
//os.write4(10000);
//os.write(1);
//os.write(0);
//os.write(0);
public String toString() {
return "TDB header: "
+ productId
+ " version="
+ tdbVersion
+ ", series:"
+ seriesName
+ ", family:"
+ familyName
+ ", ver="
+ productVersion
;
}
public void setProductId(short productId) {
this.productId = productId;
}
public void setSeriesName(String seriesName) {
this.seriesName = seriesName;
}
public void setFamilyName(String familyName) {
this.familyName = familyName;
}
public void setProductVersion(short productVersion) {
this.productVersion = productVersion;
}
public void setFamilyId(short familyId) {
this.familyId = familyId;
}
void setCodePage(int codePage) {
this.codePage = codePage;
}
public int getTdbVersion() {
return tdbVersion;
}
public void setEnableProfile(byte enableProfile) {
this.enableProfile = enableProfile;
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/package.html 0000644 0000764 0000764 00000000562 11154317465 021321 0 ustar steve steve
The TDB file format
The TDB file contains summary information about individual IMG files that make
up a map set. It is usual for a map to be broken down into a set of tiles and
the TDB file gives an overview of how they all fit together.
Acknowledgments
Based on the document Garmin TDB File Format by John Mechalas
mkgmap-r3660/src/uk/me/parabola/tdbfmt/CopyrightBlock.java 0000644 0000764 0000764 00000003365 10763340724 022631 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import uk.me.parabola.log.Logger;
import uk.me.parabola.io.StructuredInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
/**
* A copyright block consists of a number of copyright segments.
*
* @author Steve Ratcliffe
*/
class CopyrightBlock {
private static final Logger log = Logger.getLogger(CopyrightBlock.class);
private final List segments = new ArrayList();
private final Set copySet = new HashSet();
CopyrightBlock() {
}
CopyrightBlock(Block block) throws IOException {
StructuredInputStream ds = block.getInputStream();
while (!ds.testEof()) {
CopyrightSegment segment = new CopyrightSegment(ds);
log.info("segment: " + segment);
segments.add(segment);
}
}
public void write(Block block) throws IOException {
for (CopyrightSegment seg : segments) {
seg.write(block);
}
}
/**
* Add a copyright segment. We only add unique ones.
* @param seg The copyright segment to add.
*/
public void addSegment(CopyrightSegment seg) {
if (copySet.add(seg))
segments.add(seg);
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/DetailMapBlock.java 0000644 0000764 0000764 00000007155 11532723256 022522 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import java.io.IOException;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.io.StructuredOutputStream;
/**
* Details of a single .img file that is part of the map set. There will be
* one of these for each .img file.
*
* @author Steve Ratcliffe
*/
public class DetailMapBlock extends OverviewMapBlock {
private int tdbVersion;
private String innername;
// Sizes of the regions. It is possible that rgn and tre are reversed?
private int rgnDataSize;
private int treDataSize;
private int lblDataSize;
private int netDataSize;
private int nodDataSize;
public DetailMapBlock(int tdbVersion) {
assert tdbVersion > 0;
this.tdbVersion = tdbVersion;
}
/**
* Initialise this block from the raw block given.
* @param block The raw block read from the file.
* @throws IOException For io problems.
*/
public DetailMapBlock(Block block) throws IOException {
super(block);
StructuredInputStream ds = block.getInputStream();
// First there are a couple of fields that we ignore.
int junk = ds.read2();
assert junk == 4;
junk = ds.read2();
assert junk == 3;
// Sizes of the data
rgnDataSize = ds.read4();
treDataSize = ds.read4();
lblDataSize = ds.read4();
// Another ignored field
junk = ds.read();
assert junk == 1;
}
/**
* Write into the given block.
*
* @param block The block that will have been initialised to be a detail
* block.
* @throws IOException Problems writing, probably can't really happen as
* we use an array backed stream.
*/
public void write(Block block) throws IOException {
super.write(block);
StructuredOutputStream os = block.getOutputStream();
int n = 3;
if (tdbVersion >= TdbFile.TDB_V407) {
if (netDataSize > 0)
n++;
if (nodDataSize > 0)
n++;
}
os.write2(n+1);
os.write2(n);
os.write4(treDataSize);
os.write4(rgnDataSize);
os.write4(lblDataSize);
if (tdbVersion >= TdbFile.TDB_V407) {
if (n > 3) os.write4(netDataSize);
if (n > 4) os.write4(nodDataSize);
//01 c3 00 ff
os.write4(0xff00c301);
os.write(0);
os.write(0);
os.write(0);
String mn = getInnername();
os.writeString(mn + ".TRE");
os.writeString(mn + ".RGN");
os.writeString(mn + ".LBL");
if (n > 3) os.writeString(mn + ".NET");
if (n > 4) os.writeString(mn + ".NOD");
} else {
os.write(1);
}
}
public String getInnername() {
return innername;
}
public void setInnername(String innername) {
this.innername = innername;
}
public void setRgnDataSize(int rgnDataSize) {
this.rgnDataSize = rgnDataSize;
}
public void setTreDataSize(int treDataSize) {
this.treDataSize = treDataSize;
}
public void setLblDataSize(int lblDataSize) {
this.lblDataSize = lblDataSize;
}
public void setNetDataSize(int netDataSize) {
this.netDataSize = netDataSize;
}
public void setNodDataSize(int nodDataSize) {
this.nodDataSize = nodDataSize;
}
public String toString() {
return super.toString()
+ ", rgn size="
+ rgnDataSize
+ ", tre size="
+ treDataSize
+ ", lbl size"
+ lblDataSize
;
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/OverviewMapBlock.java 0000644 0000764 0000764 00000006206 11532723256 023122 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import java.io.IOException;
import uk.me.parabola.imgfmt.app.Area;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.io.StructuredOutputStream;
/**
* The overview map provides a low-detail image for the detailed maps. It
* allows you to see what areas the detail maps cover so they can be selected
* in programs such as QLandkarte and Garmin's MapSource.
*
* In addition to a low detail map, the overview map contains a number of type
* 0x4a polygons. These definition areas a labeled after and correspond to
* the detail map img files.
*
* The detail maps contain a background polygon (type 0x4b) that matches the
* definition area in the overview map.
*
* @author Steve Ratcliffe
*/
public class OverviewMapBlock {
private int mapNumber;
private String mapName;
private int parentMapNumber;
private String description;
private int maxLat;
private int maxLong;
private int minLat;
private int minLong;
public OverviewMapBlock() {
description = "overview map";
}
public OverviewMapBlock(Block block) throws IOException {
StructuredInputStream ds = block.getInputStream();
mapNumber = ds.read4();
parentMapNumber = ds.read4();
maxLat = ds.read4();
maxLong = ds.read4();
minLat = ds.read4();
minLong = ds.read4();
description = ds.readString();
}
public void write(Block block) throws IOException {
StructuredOutputStream os = block.getOutputStream();
os.write4(mapNumber);
os.write4(parentMapNumber);
os.write4(maxLat);
os.write4(maxLong);
os.write4(minLat);
os.write4(minLong);
os.writeString(description);
}
public String toString() {
return "Overview: "
+ mapNumber
+ ", parent="
+ parentMapNumber
+ " covers "
+ '(' + toDegrees(minLat) + ',' + toDegrees(minLong) + ')'
+ '(' + toDegrees(maxLat) + ',' + toDegrees(maxLong) + ')'
+ " : "
+ description
;
}
private double toDegrees(int tdbunits) {
return (double) tdbunits * 360 / Math.pow(2, 32);
}
public void setArea(Area bounds) {
minLat = bounds.getMinLat() << 8;
minLong = bounds.getMinLong() << 8;
maxLat = bounds.getMaxLat() << 8;
maxLong = bounds.getMaxLong() << 8;
}
public void setDescription(String description) {
this.description = description;
}
public void setMapName(String mapName) {
this.mapName = mapName;
try {
this.mapNumber = Integer.parseInt(mapName);
} catch (NumberFormatException e) {
this.mapNumber = 0;
}
}
protected String getMapName() {
return mapName;
}
public void setParentMapNumber(int parentMapNumber) {
this.parentMapNumber = parentMapNumber;
}
}
mkgmap-r3660/src/uk/me/parabola/tdbfmt/CopyrightSegment.java 0000644 0000764 0000764 00000005720 11503736442 023175 0 ustar steve steve /*
* Copyright (C) 2007 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: 23-Sep-2007
*/
package uk.me.parabola.tdbfmt;
import uk.me.parabola.io.StructuredInputStream;
import uk.me.parabola.io.StructuredOutputStream;
import java.io.IOException;
/**
* One copyright that is within the copyright block.
*
* @author Steve Ratcliffe
*/
class CopyrightSegment {
/**
* Source information text string. Describes what data sources were used
* in generating the map.
*/
public static final int CODE_SOURCE_INFORMATION = 0x00;
/** Copyright information from the map manufacturer. */
public static final int CODE_COPYRIGHT_TEXT_STRING = 0x06;
/**
* A filename that contains a BMP image to be printed along with
* the map.
*/
public static final int CODE_COPYRIGHT_BITMAP_REFERENCE = 0x07;
/**
* A code that shows what kind of copyright information is
* contained in this segment.
* The field {@link #extraProperties} can be used too as extra information.
*/
private final byte copyrightCode;
private final byte whereCode;
private final short extraProperties;
private final String copyright;
CopyrightSegment(StructuredInputStream ds) throws IOException {
copyrightCode = (byte) ds.read();
whereCode = (byte) ds.read();
extraProperties = (short) ds.read2();
copyright = ds.readString();
}
CopyrightSegment(int code, int where, String msg) {
this.copyrightCode = (byte) code;
this.whereCode = (byte) where;
this.copyright = msg;
this.extraProperties = 0;
}
public void write(Block block) throws IOException {
StructuredOutputStream os = block.getOutputStream();
os.write(copyrightCode);
os.write(whereCode);
os.write2(extraProperties);
os.writeString(copyright);
}
public String toString() {
return "Copyright: "
+ copyrightCode
+ ", where="
+ whereCode
+ ", extra="
+ extraProperties
+ ": "
+ copyright
;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof CopyrightSegment)) return false;
CopyrightSegment that = (CopyrightSegment) o;
if (copyrightCode != that.copyrightCode) return false;
if (extraProperties != that.extraProperties) return false;
if (whereCode != that.whereCode) return false;
if (!copyright.equals(that.copyright)) return false;
return true;
}
public int hashCode() {
int result = (int) copyrightCode;
result = 31 * result + (int) whereCode;
result = 31 * result + (int) extraProperties;
result = 31 * result + copyright.hashCode();
return result;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/ 0000755 0000764 0000764 00000000000 12651103763 017035 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/fs/ 0000755 0000764 0000764 00000000000 12651103763 017445 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/fs/FileSystem.java 0000644 0000764 0000764 00000005660 11266100545 022377 0 ustar steve steve /*
* 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: 26-Nov-2006
*/
package uk.me.parabola.imgfmt.fs;
import java.io.Closeable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.FileSystemParam;
/**
* File system operations.
*
* @author Steve Ratcliffe
*/
public interface FileSystem extends Closeable {
/**
* Create a new file it must not already exist.
* @param name The file name.
* @return A directory entry for the new file.
* @throws FileExistsException If the file already exists.
*/
public ImgChannel create(String name) throws FileExistsException;
/**
* Open a file. The returned file object can be used to read and write the
* underlying file.
*
* @param name The file name to open.
* @param mode Either "r" for read access, "w" for write access or "rw"
* for both read and write.
* @return A file descriptor.
* @throws FileNotFoundException When the file does not exist.
*/
public ImgChannel open(String name, String mode)
throws FileNotFoundException;
/**
* Lookup the file and return a directory entry for it.
*
* @param name The filename to look up.
* @return A directory entry.
* @throws IOException If an error occurs reading the directory.
*/
public DirectoryEntry lookup(String name) throws IOException;
/**
* List all the files in the directory.
* @return A List of directory entries.
*/
public List list() ;
/**
* Get the filesystem / archive parameters. Things that are stored in
* the header.
*
* @return The filesystem parameters.
*/
public FileSystemParam fsparam();
/**
* Reconfigure the filesystem with the given parameters.
* Only some parameters can be changed and the may only be changeable
* at certain points in the construction of a file system for example.
* @param param The new parameters.
* @throws IllegalStateException If the changes cannot be made (for example
* if the file system is already written).
*/
public void fsparam(FileSystemParam param);
/**
* Sync with the underlying file. All unwritten data is written out to
* the underlying file.
* @throws IOException If an error occurs during the write.
*/
public void sync() throws IOException;
/**
* Close the filesystem. Any saved data is flushed out. It is better
* to explicitly sync the data out first, to be sure that it has worked.
*/
void close();
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/fs/package.html 0000644 0000764 0000764 00000000571 10637204532 021727 0 ustar steve steve
Interfaces for accessing the filesystem
These are the way that application code should access the
filesystem parts of the code.
The main interface is {@link uk.me.parabola.imgfmt.fs.FileSystem} which contains
all the operations that can be performed on the system.
See also
The Garmin img project
mkgmap-r3660/src/uk/me/parabola/imgfmt/fs/ImgChannel.java 0000644 0000764 0000764 00000002431 11503736442 022316 0 ustar steve steve /*
* 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: 02-Dec-2006
*/
package uk.me.parabola.imgfmt.fs;
import java.nio.channels.ByteChannel;
/**
* An extension of ByteChannel that allows us to know the position. It may
* evolve to have several of the methods of the FileChannel class and plays
* a similar role.
*
* @author Steve Ratcliffe
*/
public interface ImgChannel extends ByteChannel {
/**
* Get the file position. Note that this is a logical position relative
* to the beginning of the file (the file within the .img file, not the
* beginning of the .img file itself).
*
* @return The offset in bytes from the beginning of the file.
*/
public long position();
/**
* Set the position within the file.
*
* @param pos The position to set.
*/
public void position(long pos);
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/fs/DirectoryEntry.java 0000644 0000764 0000764 00000002751 11226342005 023272 0 ustar steve steve /*
* 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: 02-Dec-2006
*/
package uk.me.parabola.imgfmt.fs;
/**
* Interface used for directory entries used to represent files.
* A directory entry has the file name, its extension (its a 8+3 filename)
* and the size of the file.
*
* @author Steve Ratcliffe
*/
public interface DirectoryEntry {
public static final int ENTRY_SIZE = 512;
public static final int SLOTS_PER_ENTRY = 240;
/**
* Get the file name.
* @return The file name.
*/
public String getName();
/**
* Get the file extension.
* @return The file extension.
*/
public String getExt();
/**
* Get the full name. That is name + extension.
*
* @return The full name as NAME.EXT
*/
public String getFullName();
/**
* Get the file size.
* @return The size of the file in bytes.
*/
public int getSize();
/**
* If this is a special 'hidden' file. True for the all-spaces 'file'.
*
* @return True if this is not a regular file.
*/
public boolean isSpecial();
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/Utils.java 0000644 0000764 0000764 00000025265 12371067413 021012 0 ustar steve steve /*
* 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;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Calendar;
import java.util.Date;
import java.util.zip.GZIPInputStream;
import uk.me.parabola.imgfmt.app.Coord;
/**
* Some miscellaneous functions that are used within the .img code.
*
* @author Steve Ratcliffe
*/
public class Utils {
/**
* Routine to convert a string to bytes and pad with a character up
* to a given length.
* Only to be used for strings that are expressible in latin1.
*
* @param s The original string.
* @param len The length to pad to.
* @param pad The byte used to pad.
* @return An array created from the string.
*/
public static byte[] toBytes(String s, int len, byte pad) {
if (s == null)
throw new IllegalArgumentException("null string provided");
byte[] out = new byte[len];
for (int i = 0; i < len; i++) {
if (i > s.length()) {
out[i] = pad;
} else {
out[i] = (byte) s.charAt(i);
}
}
return out;
}
public static byte[] toBytes(String s) {
return toBytes(s, s.length(), (byte) 0);
}
/**
* Convert from bytes to a string. Only to be used when the character set
* is ascii or latin1.
*
* @param buf A byte buffer to get the bytes from. Should be ascii or latin1.
* @param off The offset into buf.
* @param len The length to get.
* @return A string.
*/
public static String bytesToString(ByteBuffer buf, int off, int len) {
if (buf == null)
throw new IllegalArgumentException("null byte buffer provided");
byte[] bbuf = new byte[len];
buf.position(off);
buf.get(bbuf);
char[] cbuf = new char[len];
for (int i = 0; i < bbuf.length; i++) {
cbuf[i] = (char) bbuf[i];
}
return new String(cbuf);
}
/**
* Set the creation date. Note that the year is encoded specially.
*
* @param buf The buffer to write into. It must have been properly positioned
* beforehand.
* @param date The date to set.
*/
public static void setCreationTime(ByteBuffer buf, Date date) {
Calendar cal = Calendar.getInstance();
if (date != null)
cal.setTime(date);
fillBufFromTime(buf, cal);
}
/**
* A map unit is an integer value that is 1/(2^24) degrees of latitude or
* longitude.
*
* @param l The lat or long as decimal degrees.
* @return An integer value in map units.
*/
public static int toMapUnit(double l) {
double DELTA = 360.0D / (1 << 24) / 2; //Correct rounding
if (l > 0)
return (int) ((l + DELTA) * (1 << 24)/360);
else
return (int) ((l - DELTA) * (1 << 24)/360);
}
/**
* Convert a date into the in-file representation of a date.
*
* @param date The date.
* @return A byte stream in .img format.
*/
public static byte[] makeCreationTime(Date date) {
Calendar cal = Calendar.getInstance();
if (date != null)
cal.setTime(date);
byte[] ret = new byte[7];
ByteBuffer buf = ByteBuffer.wrap(ret);
buf.order(ByteOrder.LITTLE_ENDIAN);
fillBufFromTime(buf, cal);
return ret;
}
private static void fillBufFromTime(ByteBuffer buf, Calendar cal) {
buf.putChar((char) cal.get(Calendar.YEAR));
buf.put((byte) (cal.get(Calendar.MONTH)+1));
buf.put((byte) cal.get(Calendar.DAY_OF_MONTH));
buf.put((byte) cal.get(Calendar.HOUR_OF_DAY));
buf.put((byte) cal.get(Calendar.MINUTE));
buf.put((byte) cal.get(Calendar.SECOND));
}
/**
* Make a date from the garmin representation.
* @param date The bytes representing the date.
* @return A java date.
*/
public static Date makeCreationTime(byte[] date) {
Calendar cal = Calendar.getInstance();
int y = ((date[1] & 0xff) << 8) + (date[0] & 0xff);
cal.set(y, date[2]-1, date[3], date[4], date[5], date[6]);
return cal.getTime();
}
/**
* Convert an angle in map units to degrees.
*/
public static double toDegrees(int val) {
return (double) val * (360.0 / (1 << 24));
}
/**
* Convert an angle in map units to radians.
*/
public static double toRadians(int mapunits) {
return toDegrees(mapunits) * (Math.PI / 180);
}
public static void closeFile(Closeable f) {
if (f != null) {
try {
f.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Open a file and apply filters necessary for reading it such as
* decompression.
*
* @param name The file to open.
* @return A stream that will read the file, positioned at the beginning.
* @throws FileNotFoundException If the file cannot be opened for any reason.
*/
public static InputStream openFile(String name) throws FileNotFoundException {
InputStream is = new FileInputStream(name);
if (name.endsWith(".gz")) {
try {
is = new GZIPInputStream(is);
} catch (IOException e) {
throw new FileNotFoundException( "Could not read as compressed file");
}
}
return is;
}
public static String joinPath(String dir, String basename, String ext) {
return joinPath(dir, basename + "." + ext);
}
public static String joinPath(String dir, String basename) {
File file = new File(dir, basename);
return file.getAbsolutePath();
}
/**
* Rounds an integer up to the nearest multiple of {@code 2^shift}.
* Works with both positive and negative integers.
* @param val the integer to round up.
* @param shift the power of two to round up to.
* @return the rounded integer.
*/
public static int roundUp(int val, int shift) {
return (val + (1 << shift) - 1) >>> shift << shift;
}
/**
* Calculates the angle between the two segments (c1,c2),(c2,c3).
* It is assumed that the segments are rhumb lines, not great circle paths.
* @param c1 first point
* @param c2 second point
* @param c3 third point
* @return angle between the two segments in degree [-180;180]
*/
public static double getAngle(Coord c1, Coord c2, Coord c3) {
double a = c2.bearingTo(c1);
double b = c2.bearingTo(c3);
double angle = b - (a - 180);
while(angle > 180)
angle -= 360;
while(angle < -180)
angle += 360;
return angle;
}
/**
* Calculates the angle between the two segments (c1,c2),(c2,c3)
* using the coords in map units.
* @param c1 first point
* @param c2 second point
* @param c3 third point
* @return angle between the two segments in degree [-180;180]
*/
public static double getDisplayedAngle(Coord c1, Coord c2, Coord c3) {
return getAngle(c1.getDisplayedCoord(), c2.getDisplayedCoord(), c3.getDisplayedCoord());
}
public final static int NOT_STRAIGHT = 0;
public final static int STRAIGHT_SPIKE = 1;
public final static int STRICTLY_STRAIGHT = 2;
/**
* Checks if the two segments (c1,c2),(c2,c3) form a straight line.
* @param c1 first point
* @param c2 second point
* @param c3 third point
* @return NOT_STRAIGHT, STRAIGHT_SPIKE or STRICTLY_STRAIGHT
*/
public static int isStraight(Coord c1, Coord c2, Coord c3) {
if (c1.equals(c3))
return STRAIGHT_SPIKE;
long area;
// calculate the area that is enclosed by the three points
// (as if a closing line is drawn from c3 back to c1)
area = ((long)c1.getLongitude() * c2.getLatitude() -
(long)c2.getLongitude() * c1.getLatitude());
area += ((long)c2.getLongitude() * c3.getLatitude() -
(long)c3.getLongitude() * c2.getLatitude());
area += ((long)c3.getLongitude() * c1.getLatitude() -
(long)c1.getLongitude() * c3.getLatitude());
if (area == 0){
// area is empty-> points lie on a straight line
int delta1 = c1.getLatitude() - c2.getLatitude();
int delta2 = c2.getLatitude() - c3.getLatitude();
if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0)
return STRAIGHT_SPIKE;
delta1 = c1.getLongitude() - c2.getLongitude();
delta2 = c2.getLongitude() - c3.getLongitude();
if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0)
return STRAIGHT_SPIKE;
return STRICTLY_STRAIGHT;
}
// line is not straight
return NOT_STRAIGHT;
}
/**
* Checks if the two segments (c1,c2),(c2,c3) form a straight line
* using high precision coordinates.
* @param c1 first point
* @param c2 second point
* @param c3 third point
* @return NOT_STRAIGHT, STRAIGHT_SPIKE or STRICTLY_STRAIGHT
*/
public static int isHighPrecStraight(Coord c1, Coord c2, Coord c3) {
if (c1.highPrecEquals(c3))
return STRAIGHT_SPIKE;
long area;
long c1Lat = c1.getHighPrecLat();
long c2Lat = c2.getHighPrecLat();
long c3Lat = c3.getHighPrecLat();
long c1Lon = c1.getHighPrecLon();
long c2Lon = c2.getHighPrecLon();
long c3Lon = c3.getHighPrecLon();
// calculate the area that is enclosed by the three points
// (as if a closing line is drawn from c3 back to c1)
area = c1Lon * c2Lat - c2Lon * c1Lat;
area += c2Lon * c3Lat - c3Lon * c2Lat;
area += c3Lon * c1Lat - c1Lon * c3Lat;
if (area == 0){
// area is empty-> points lie on a straight line
long delta1 = c1Lat - c2Lat;
long delta2 = c2Lat - c3Lat;
if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0)
return STRAIGHT_SPIKE;
delta1 = c1Lon - c2Lon;
delta2 = c2Lon - c3Lon;
if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0)
return STRAIGHT_SPIKE;
return STRICTLY_STRAIGHT;
}
// line is not straight
return NOT_STRAIGHT;
}
/**
* approximate atan2, much faster than Math.atan2()
* Based on a 50-year old arctan approximation due to Hastings
*/
private final static double PI_BY_2 = Math.PI / 2;
// |error| < 0.005
public static double atan2_approximation( double y, double x )
{
if ( x == 0.0f )
{
if ( y > 0.0f ) return PI_BY_2 ;
if ( y == 0.0f ) return 0.0f;
return -PI_BY_2 ;
}
double atan;
double z = y/x;
if ( Math.abs( z ) < 1.0f )
{
atan = z/(1.0f + 0.28f*z*z);
if ( x < 0.0f )
{
if ( y < 0.0f ) return atan - Math.PI;
return atan + Math.PI;
}
}
else
{
atan = PI_BY_2 - z/(z*z + 0.28f);
if ( y < 0.0f ) return atan - Math.PI;
}
return atan;
}
/**
* calculate a long value for the latitude and longitude of a coord
* in high precision.
* @param co
* @return a long that can be used as a key in HashMaps
*/
public static long coord2Long(Coord co){
int lat30 = co.getHighPrecLat();
int lon30 = co.getHighPrecLon();
return (long)(lat30 & 0xffffffffL) << 32 | (lon30 & 0xffffffffL);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/MapFailedException.java 0000644 0000764 0000764 00000005316 12333157050 023401 0 ustar steve steve /*
* Copyright (C) 2010.
*
* 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;
import uk.me.parabola.log.Logger;
/**
* Used for cases where the current map has failed to compile, but the error
* is expected to be specific to the map (eg it is too big etc). When this
* error is thrown it may be possible for other maps on the command line to
* succeed.
*
* If the error is such that processing further maps is not likely to be
* successful then use {@link ExitException} instead.
*
* @author Steve Ratcliffe
*/
public class MapFailedException extends RuntimeException {
private static final Logger log = Logger.getLogger(MapFailedException.class);
/**
* Constructs a new runtime exception with the specified detail message.
* The cause is not initialized, and may subsequently be initialized by a
* call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public MapFailedException(String message) {
super(message);
log(message);
}
/**
* Constructs a new runtime exception with the specified detail message and
* cause.
Note that the detail message associated with
* cause is not automatically incorporated in
* this runtime exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public MapFailedException(String message, Throwable cause) {
super(message, cause);
log(message);
}
private static void log(String message){
String thrownBy = "";
try{
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
int callerPosInStack = 3;
String[] caller = stackTraceElements[callerPosInStack].getClassName().split("\\.");
thrownBy = "(thrown in " + caller[caller.length-1]+ "." +stackTraceElements[callerPosInStack].getMethodName() + "()) ";
} catch(Exception e){
log.info(e);
}
log.error(thrownBy + message);
}
} mkgmap-r3660/src/uk/me/parabola/imgfmt/FormatException.java 0000644 0000764 0000764 00000003015 11503736442 023007 0 ustar steve steve /*
* 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: 18-Dec-2006
*/
package uk.me.parabola.imgfmt;
/**
* Thrown for any kind of malformed input to the mapping program.
*
* @author Steve Ratcliffe
*/
public class FormatException extends RuntimeException {
/**
* Constructs a new exception with the specified detail message and
* cause.
Note that the detail message associated with
* cause is not automatically incorporated in
* this exception's detail message.
*
* @param message the detail message (which is saved for later retrieval
* by the {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the
* {@link #getCause()} method). (A null value is
* permitted, and indicates that the cause is nonexistent or
* unknown.)
* @since 1.4
*/
public FormatException(String message, Throwable cause) {
super(message, cause);
}
public FormatException(String message) {
super(message);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mdxfmt/ 0000755 0000764 0000764 00000000000 12651103763 020334 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/mdxfmt/MdxFileReader.java 0000644 0000764 0000764 00000003001 11532723256 023646 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.mdxfmt;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* For reading the MDX file.
*
* @author Steve Ratcliffe
*/
public class MdxFileReader {
private final ImgFileReader reader;
private int numberOfMaps;
private final List maps = new ArrayList();
public MdxFileReader(ImgChannel chan) {
this.reader = new BufferedImgFileReader(chan);
readHeader();
readMaps();
}
private void readMaps() {
for (int i = 0; i < numberOfMaps; i++) {
MapInfo info = new MapInfo();
info.setHexMapname(reader.getInt());
info.setProductId(reader.getChar());
info.setFamilyId(reader.getChar());
info.setMapname(reader.getInt());
maps.add(info);
}
}
private void readHeader() {
reader.getInt();
reader.getChar();
reader.getInt();
numberOfMaps = reader.getInt();
}
public List getMaps() {
return maps;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mdxfmt/MapInfo.java 0000644 0000764 0000764 00000003303 11532723256 022531 0 ustar steve steve /*
* 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.mdxfmt;
import java.nio.ByteBuffer;
/**
* Represents an individual file in the MDX file.
*
* I don't really understand the difference between what I call hex mapname
* and mapname. We shall always make them equal.
*
* There is no good reason to call it 'hexMapname' its just a name that stuck
* I still don't know what the difference is. We always make them the same
* but they can differ.
*/
public class MapInfo {
private int hexMapname;
private int mapname;
private char familyId;
private char productId;
private String filename;
void write(ByteBuffer os) {
os.putInt(hexMapname);
os.putChar(productId);
os.putChar(familyId);
os.putInt(mapname);
}
public int getHexMapname() {
return hexMapname;
}
public void setHexMapname(int hexMapname) {
this.hexMapname = hexMapname;
}
public void setMapname(int mapname) {
this.mapname = mapname;
}
public void setFamilyId(char familyId) {
this.familyId = familyId;
}
public void setProductId(char productId) {
this.productId = productId;
}
public int getMapname() {
return mapname;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mdxfmt/MdxFile.java 0000644 0000764 0000764 00000007335 11532723256 022541 0 ustar steve steve /*
* 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.mdxfmt;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* The MDX index file. Used with the global index. This is located
* at the family level in the windows registry and can perhaps index
* across different products (and maybe families), although such a thing
* hasn't been seen.
*
* @author Steve Ratcliffe
*/
public class MdxFile {
private final char familyId;
private final char productId;
private final List maps = new ArrayList();
/**
* Create with default family and product ids.
* @param familyId The default family id that will be used if no other one
* is supplied.
* @param productId The default product id for the maps indexed by this
* file.
*/
public MdxFile(int familyId, int productId) {
this.familyId = (char) familyId;
this.productId = (char) productId;
}
/**
* Add a map with the default family and product id's and with equal
* name and hex name.
* @param name The map name (from the filename of the map) as an integer.
* @param hexname The map id that is inside the TRE header
* @param filename The file name of the map being added. Mainly for diagnostics,
* it is not needed for the file.
*/
public void addMap(int name, int hexname, String filename) {
MapInfo info = new MapInfo();
info.setHexMapname(hexname);
info.setMapname(name);
info.setFamilyId(familyId);
info.setProductId(productId);
info.setFilename(filename);
maps.add(info);
}
/**
* Write the file out to the given filename.
*/
public void write(String filename) throws IOException {
FileOutputStream stream = new FileOutputStream(filename);
FileChannel chan = stream.getChannel();
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.order(ByteOrder.LITTLE_ENDIAN);
try {
writeHeader(chan, buf);
writeBody(chan, buf);
} finally {
chan.close();
}
}
private void writeHeader(WritableByteChannel chan, ByteBuffer buf) throws IOException {
try {
buf.put("Midx".getBytes("ascii"));
} catch (UnsupportedEncodingException e) {
throw new IOException("Could not write header");
}
buf.putChar((char) 100);
buf.putInt(12);
buf.putInt(maps.size());
buf.flip();
chan.write(buf);
}
private void writeBody(WritableByteChannel chan, ByteBuffer buf) throws IOException {
// Sort the maps by the hex number.
Collections.sort(maps, new Comparator() {
public int compare(MapInfo o1, MapInfo o2) {
if (o1.getHexMapname() == o2.getHexMapname())
return 0;
else if (o1.getHexMapname() < o2.getHexMapname())
return -1;
else
return 1;
}
});
for (MapInfo info : maps) {
// Although its not necessarily wrong for them to be zero, it probably
// sign that something is wrong.
if (info.getHexMapname() == 0 || info.getMapname() == 0)
System.err.println("Invalid mapname for " + info.getFilename() + ", perhaps it is not a .img file");
buf.compact();
info.write(buf);
buf.flip();
chan.write(buf);
}
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/ 0000755 0000764 0000764 00000000000 12651103763 017653 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/ImgFS.java 0000644 0000764 0000764 00000024641 11763457463 021506 0 ustar steve steve /*
* 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: 26-Nov-2006
*/
package uk.me.parabola.imgfmt.sys;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.List;
import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.FileNotWritableException;
import uk.me.parabola.imgfmt.FileSystemParam;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.FileSystem;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* The img file is really a filesystem containing several files.
* It is made up of a header, a directory area and a data area which
* occur in the filesystem in that order.
*
* @author steve
*/
public class ImgFS implements FileSystem {
private static final Logger log = Logger.getLogger(ImgFS.class);
// The directory is just like any other file, but with a name of 8+3 spaces
static final String DIRECTORY_FILE_NAME = " . ";
// This is the read or write channel to the real file system.
private final FileChannel file;
private boolean readOnly = true;
// The header contains general information.
private ImgHeader header;
// There is only one directory that holds all filename and block allocation
// information.
private Directory directory;
// The filesystem is responsible for allocating blocks
private BlockManager fileBlockManager;
// The header entries are written in 512 blocks, regardless of the block size of the file itself.
private static final long ENTRY_BLOCK_SIZE = 512L;
private BlockManager headerBlockManager;
private byte xorByte; // if non-zero, all bytes are XORed with this
/**
* Private constructor, use the static {@link #createFs} and {@link #openFs}
* routines to make a filesystem.
*
* @param chan The open file.
*/
private ImgFS(FileChannel chan) {
file = chan;
}
/**
* Create an IMG file from its external filesystem name and optionally some
* parameters.
*
* @param filename The name of the file to be created.
* @param params File system parameters. Can not be null.
* @throws FileNotWritableException If the file can not be written to.
*/
public static FileSystem createFs(String filename, FileSystemParam params) throws FileNotWritableException {
params.setFilename(filename);
try {
RandomAccessFile rafile = new RandomAccessFile(filename, "rw");
return createFs(rafile.getChannel(), params);
} catch (FileNotFoundException e) {
throw new FileNotWritableException("Could not create file: " + params.getFilename(), e);
}
}
private static FileSystem createFs(FileChannel chan, FileSystemParam params)
throws FileNotWritableException
{
assert params != null;
// Truncate the file, because extra bytes beyond the end make for a
// map that doesn't work on the GPS (although its likely to work in
// other software viewers).
try {
chan.truncate(0);
} catch (IOException e) {
throw new FileNotWritableException("Failed to truncate file", e);
}
ImgFS fs = new ImgFS(chan);
fs.createInitFS(chan, params);
return fs;
}
/**
* Open an existing IMG file system.
* @param name The file name to open.
* @return A File system that can be used lookup the internal files.
* @throws FileNotFoundException When the file doesn't exist or can't be
* read.
*/
public static FileSystem openFs(String name) throws FileNotFoundException {
RandomAccessFile rafile = new RandomAccessFile(name, "r");
return openFs(name, rafile.getChannel());
}
private static FileSystem openFs(String name, FileChannel chan) throws FileNotFoundException {
ImgFS fs = new ImgFS(chan);
try {
fs.readInitFS(chan);
} catch (IOException e) {
throw new FileNotFoundException(name + ": " + e.getMessage());
}
return fs;
}
/**
* Create a new file, it must not already exist.
*
* @param name The file name.
* @return A directory entry for the new file.
*/
public ImgChannel create(String name) throws FileExistsException {
Dirent dir = directory.create(name, fileBlockManager);
return new FileNode(file, dir, "w");
}
/**
* Open a file. The returned file object can be used to read and write the
* underlying file.
*
* @param name The file name to open.
* @param mode Either "r" for read access, "w" for write access or "rw"
* for both read and write.
* @return A file descriptor.
* @throws FileNotFoundException When the file does not exist.
*/
public ImgChannel open(String name, String mode) throws FileNotFoundException {
if (name == null || mode == null)
throw new IllegalArgumentException("null argument");
if (mode.indexOf('r') >= 0) {
Dirent ent = internalLookup(name);
FileNode fn = new FileNode(file, ent, "r");
if(xorByte != 0)
fn.setXorByte(xorByte);
return fn;
} else if (mode.indexOf('w') >= 0) {
Dirent ent;
try {
ent = internalLookup(name);
} catch (FileNotFoundException e) {
try {
ent = directory.create(name, fileBlockManager);
} catch (FileExistsException e1) {
// This shouldn't happen as we have just checked.
throw new FileNotFoundException("Attempt to duplicate a file name");
}
}
return new FileNode(file, ent, "w");
} else {
throw new IllegalArgumentException("Invalid mode given");
}
}
/**
* Lookup the file and return a directory entry for it.
*
* @param name The filename to look up.
* @return A directory entry.
* @throws FileNotFoundException If an error occurs looking for the file,
* including it not existing.
*/
public DirectoryEntry lookup(String name) throws FileNotFoundException {
return internalLookup(name);
}
/**
* List all the files in the directory.
*
* @return A List of directory entries.
*/
public List list() {
return directory.getEntries();
}
public FileSystemParam fsparam() {
return header.getParams();
}
public void fsparam(FileSystemParam param) {
int reserved = param.getReservedDirectoryBlocks() + 2;
fileBlockManager.setCurrentBlock(reserved);
headerBlockManager.setMaxBlock(reserved);
}
/**
* Sync with the underlying file. All unwritten data is written out to
* the underlying file.
*
* @throws IOException If an error occurs during the write.
*/
public void sync() throws IOException {
if (readOnly)
return;
header.setNumBlocks(fileBlockManager.getMaxBlockAllocated());
header.sync();
directory.sync();
}
/**
* Close the filesystem. Any saved data is flushed out. It is better
* to explicitly sync the data out first, to be sure that it has worked.
*/
public void close() {
try {
sync();
} catch (IOException e) {
log.debug("could not sync filesystem");
} finally {
try {
file.close();
} catch (IOException e) {
log.warn("Could not close file");
}
}
}
/**
* Set up and ImgFS that has just been created.
*
* @param chan The real underlying file to write to.
* @param params The file system parameters.
* @throws FileNotWritableException If the file cannot be written for any
* reason.
*/
private void createInitFS(FileChannel chan, FileSystemParam params) throws FileNotWritableException {
readOnly = false;
// The block manager allocates blocks for files.
headerBlockManager = new BlockManager(params.getBlockSize(), 0);
headerBlockManager.setMaxBlock(params.getReservedDirectoryBlocks());
// This bit is tricky. We want to use a regular ImgChannel to write
// to the header and directory, but to create one normally would involve
// it already existing, so it is created by hand.
try {
directory = new Directory(headerBlockManager, params.getDirectoryStartEntry());
Dirent ent = directory.create(DIRECTORY_FILE_NAME, headerBlockManager);
ent.setSpecial(true);
ent.setInitialized(true);
FileNode f = new FileNode(chan, ent, "w");
directory.setFile(f);
header = new ImgHeader(f);
header.createHeader(params);
} catch (FileExistsException e) {
throw new FileNotWritableException("Could not create img file directory", e);
}
fileBlockManager = new BlockManager(params.getBlockSize(), params.getReservedDirectoryBlocks());
assert header != null;
}
/**
* Initialise a filesystem that is going to be read from. We need to read
* in the header including directory.
*
* @param chan The file channel to read from.
* @throws IOException If the file cannot be read.
*/
private void readInitFS(FileChannel chan) throws IOException {
ByteBuffer headerBuf = ByteBuffer.allocate(512);
headerBuf.order(ByteOrder.LITTLE_ENDIAN);
chan.read(headerBuf);
xorByte = headerBuf.get(0);
if(xorByte != 0) {
byte[] headerBytes = headerBuf.array();
for(int i = 0; i < headerBytes.length; ++i)
headerBytes[i] ^= xorByte;
}
if (headerBuf.position() < 512)
throw new IOException("File too short or corrupted");
header = new ImgHeader(null);
header.setHeader(headerBuf);
FileSystemParam params = header.getParams();
BlockManager headerBlockManager = new BlockManager(params.getBlockSize(), 0);
headerBlockManager.setMaxBlock(params.getReservedDirectoryBlocks());
directory = new Directory(headerBlockManager, params.getDirectoryStartEntry());
directory.setStartPos(params.getDirectoryStartEntry() * ENTRY_BLOCK_SIZE);
Dirent ent = directory.create(DIRECTORY_FILE_NAME, headerBlockManager);
FileNode f = new FileNode(chan, ent, "r");
header.setFile(f);
directory.setFile(f);
directory.readInit(xorByte);
}
/**
* Lookup the file and return a directory entry for it.
*
* @param name The filename to look up.
* @return A directory entry.
* @throws FileNotFoundException If an error occurs reading the directory.
*/
private Dirent internalLookup(String name) throws FileNotFoundException {
if (name == null)
throw new IllegalArgumentException("null name argument");
Dirent ent = (Dirent) directory.lookup(name);
if (ent == null)
throw new FileNotFoundException(name + " not found");
return ent;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/FileNode.java 0000644 0000764 0000764 00000021566 11256647151 022221 0 ustar steve steve /*
* 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.sys;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ClosedByInterruptException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.FileChannel;
import java.nio.channels.NonReadableChannelException;
import java.nio.channels.NonWritableChannelException;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* The internal representation of a file in the file system. In use it
* should only be referred to by the {@link ImgChannel} interface.
*
* @author Steve Ratcliffe
*/
public class FileNode implements ImgChannel {
private static final Logger log = Logger.getLogger(FileNode.class);
private boolean open;
private boolean writeable;
private boolean readable;
private final FileChannel file;
private final BlockManager blockManager;
private final Dirent dirent;
// The position in this file
private long position;
private byte xorByte;
/**
* Creates a new file in the file system. You can treat this just like
* a regular file and write or read from it.
* Operations to two different files may not be interleaved although
* it may be possible to implement this.
*
* @param file The handle to the underlying file.
* @param dir The directory entry associated with this file.
* @param mode The mode "rw" for read and write etc.
*/
public FileNode(FileChannel file, Dirent dir, String mode)
{
this.file = file;
this.dirent = dir;
if (mode.indexOf('r') >= 0)
readable = true;
if (mode.indexOf('w') >= 0)
writeable = true;
if (!(readable || writeable))
throw new IllegalArgumentException("File must be readable or writeable");
blockManager = dir.getBlockManager();
if (blockManager == null)
throw new IllegalArgumentException("no file system supplied");
open = true;
}
/**
* Closes this channel.
*
*
After a channel is closed, any further attempt to invoke I/O
* operations upon it will cause a {@link ClosedChannelException} to be
* thrown.
*
*
If this channel is already closed then invoking this method has no
* effect.
*
*
This method may be invoked at any time. If some other thread has
* already invoked it, however, then another invocation will block until
* the first invocation is complete, after which it will return without
* effect.
*
* @throws IOException If an I/O error occurs
*/
public void close() throws IOException {
if (!open)
return;
sync();
open = false;
readable = false;
writeable = false;
}
/**
* Tells whether or not this channel is open.
*
* @return true if, and only if, this channel is open
*/
public boolean isOpen() {
return open;
}
/**
* Reads a sequence of bytes from this channel into the given buffer.
*
* @param dst The buffer into which bytes are to be transferred
*
* @return The number of bytes read, possibly zero, or -1 if the
* channel has reached end-of-stream
*
* @throws NonReadableChannelException If this channel was not opened for reading
* @throws ClosedChannelException If this channel is closed
* @throws AsynchronousCloseException If another thread closes this channel
* while the read operation is in progress
* @throws ClosedByInterruptException If another thread interrupts the
* current thread while the read operation is in progress, thereby closing
* the channel and setting the current thread's interrupt status
* @throws IOException If some other I/O error occurs
*/
public int read(ByteBuffer dst) throws IOException {
if (!open)
throw new ClosedChannelException();
if (!readable)
throw new NonReadableChannelException();
int blockSize = blockManager.getBlockSize();
long size = dst.remaining();
long fileSize = dirent.getSize();
if (position >= fileSize)
return -1;
size = Math.min(size, fileSize - position);
int totalRead = 0;
while (size > 0) {
// Tet the logical block number, as we see it in our file.
int lblock = (int) (position / blockSize);
// Get the physical block number, the actual block number in
// the underlying file.
int pblock = dirent.getPhysicalBlock(lblock);
if (pblock == 0xffff) {
// We are at the end of the file.
log.debug("at eof");
break;
}
// Position the underlying file
int off = (int) (position - lblock*blockSize);
file.position((long) pblock * blockSize + off);
int n = (int) size;
if (n > blockSize)
n = blockSize;
if (off != 0)
n = Math.min(n, blockSize - off);
dst.limit(dst.position() + n);
int pos = dst.position();
int nr = file.read(dst);
if (nr == -1)
return -1;
if (nr == 0)
throw new IOException("Read nothing");
if(xorByte != 0) {
byte[] bufBytes = dst.array();
for(int i = pos + n - 1; i >= pos; --i)
bufBytes[i] ^= xorByte;
}
// Update the file positions
size -= nr;
position += nr;
totalRead += nr;
}
log.debug("read ret", totalRead);
return totalRead;
}
/**
* Writes a sequence of bytes to this channel from the given buffer.
*
*
An attempt is made to write up to r bytes to the channel,
* where r is the number of bytes remaining in the buffer, that is,
* dst.remaining(), at the moment this method is invoked.
*
The logical block has to be converted to a physical block in the
* underlying file.
*
* @param src The buffer from which bytes are to be retrieved
* @return The number of bytes written, possibly zero
* @throws NonWritableChannelException
* If this channel was not opened for writing
* @throws ClosedChannelException
* If this channel is closed
* @throws IOException If some other I/O error occurs
*/
public int write(ByteBuffer src) throws IOException {
if (!open)
throw new ClosedChannelException();
int blockSize = blockManager.getBlockSize();
// Get the size of this write
int size = src.remaining();
// Loop over each block, this is to support the case (which we may
// not implement) of non-contiguous blocks.
int totalWritten = 0;
while (size > 0) {
// Get the logical block, ie the block as we see it in our file.
int lblock = (int) (position/blockSize);
// First need to allocate enough blocks for this write. First check
// if the block exists already
int pblock = dirent.getPhysicalBlock(lblock);
log.debug("lblock / pblock", lblock, '/', pblock);
if (pblock == 0xffff) {
log.debug("allocating new block");
pblock = blockManager.allocate();
dirent.addBlock(pblock);
}
// Position the underlying file, so that it is in the correct place.
int off = (int) (position - lblock*blockSize);
file.position((long) pblock * blockSize + off);
int n = size;
if (n > blockSize)
n = blockSize;
if (off != 0)
n = Math.min(n, blockSize - off);
src.limit(src.position() + n);
// Write to the underlying file.
int nw = file.write(src);
if (nw == 0)
throw new IOException("Wrote nothing");
// Update the file positions
size -= nw;
position += nw;
totalWritten += nw;
// Update file size.
if (position > dirent.getSize())
dirent.setSize((int) position);
}
return totalWritten;
}
public long position() {
return position;
}
public void position(long pos) {
int blockSize = blockManager.getBlockSize();
while (pos > position) {
long lblock = position / blockSize;
int pblock = dirent.getPhysicalBlock((int) lblock);
if (pblock == 0xffff) {
if (writeable) {
log.debug("setting position allocating new block", lblock);
pblock = blockManager.allocate();
dirent.addBlock(pblock);
}
}
position = (lblock+1) * blockSize;
}
this.position = pos;
}
/**
* Write out any unsaved data to disk.
*
* @throws IOException If there is an error writing to disk.
*/
private void sync() throws IOException {
if (!writeable)
return;
// Ensure that a complete block is written out.
int bs = blockManager.getBlockSize();
long rem = bs - (file.position() % bs);
ByteBuffer buf = ByteBuffer.allocate(blockManager.getBlockSize());
// Complete any partial block.
for (int i = 0; i < rem; i++)
buf.put((byte) 0);
buf.flip();
file.write(buf);
}
public void setXorByte(byte xorByte) {
this.xorByte = xorByte;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/BlockManager.java 0000644 0000764 0000764 00000004475 11562201364 023051 0 ustar steve steve /*
* Copyright (C) 2007 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: 25-Oct-2007
*/
package uk.me.parabola.imgfmt.sys;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.log.Logger;
/**
* This is used to allocate blocks for files in the filesystem/archive.
*
* @author Steve Ratcliffe
*/
class BlockManager {
private static final Logger log = Logger.getLogger(BlockManager.class);
private final int blockSize;
private int currentBlock;
private int maxBlock = 0xfffe;
private int maxBlockAllocated;
private final int initialBlock;
BlockManager(int blockSize, int initialBlock) {
this.blockSize = blockSize;
this.currentBlock = initialBlock;
this.initialBlock = initialBlock;
this.maxBlockAllocated = initialBlock;
}
/**
* Well the algorithm is pretty simple - you just get the next unused block
* number.
*
* @return A block number that is free to be used.
*/
public int allocate() {
int n = currentBlock++;
if (maxBlock > 0 && n > maxBlock) {
log.error("overflowed directory with max block " + maxBlock + ", current=" + n);
// This problem is fixable so give some useful advice on what
// to do about it
String message = String.format("Too many blocks." +
" Use a larger block size with an option such as" +
" --block-size=%d or --block-size=%d",
blockSize * 2, blockSize * 4);
throw new MapFailedException(message);
}
maxBlockAllocated++;
return n;
}
public int getBlockSize() {
return blockSize;
}
public int getMaxBlock() {
return maxBlock;
}
public void setMaxBlock(int maxBlock) {
this.maxBlock = maxBlock;
}
public void setCurrentBlock(int n) {
if (maxBlockAllocated != initialBlock)
throw new IllegalStateException("Blocks already allocated");
currentBlock = n;
maxBlockAllocated = n;
}
public int getMaxBlockAllocated() {
return maxBlockAllocated;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/Dirent.java 0000644 0000764 0000764 00000015430 11503736442 021747 0 ustar steve steve /*
* 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: 30-Nov-2006
*/
package uk.me.parabola.imgfmt.sys;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* An entry within a directory. This holds its name and a list
* of blocks that go to make up this file.
*
* A directory entry may take more than block in the file system.
*
*
All documentation seems to point to the block numbers having to be
* contiguous, but seems strange so I shall experiment.
*
*
Entries are in blocks of 512 bytes, regardless of the block size.
*
* @author Steve Ratcliffe
*/
class Dirent implements DirectoryEntry {
protected static final Logger log = Logger.getLogger(Dirent.class);
// Constants.
static final int MAX_FILE_LEN = 8;
static final int MAX_EXT_LEN = 3;
// Offsets
static final int OFF_FILE_USED = 0x00;
static final int OFF_NAME = 0x01;
static final int OFF_EXT = 0x09;
static final int OFF_FLAG = 0x10;
static final int OFF_FILE_PART = 0x11;
private static final int OFF_SIZE = 0x0c;
// File names are a base+extension
private String name;
private String ext;
// The file size.
private int size;
private final BlockManager blockManager;
// The block table holds all the blocks that belong to this file. The
// documentation suggests that block numbers are always contiguous.
private final BlockTable blockTable;
private boolean special;
private static final int OFF_USED_FLAG = 0;
private boolean initialized;
Dirent(String name, BlockManager blockManager) {
this.blockManager = blockManager;
int dot = name.lastIndexOf('.');
if (dot >= 0) {
setName(name.substring(0, dot));
setExt(name.substring(dot+1));
} else
throw new IllegalArgumentException("Filename did not have dot");
blockTable = new BlockTable();
}
/**
* Write this entry out to disk. Note that these are 512 bytes, regardless
* of the block size.
*
* @param file The file to write to.
* @throws IOException If writing fails for any reason.
*/
void sync(ImgChannel file) throws IOException {
int ntables = blockTable.getNBlockTables();
ByteBuffer buf = ByteBuffer.allocate(ENTRY_SIZE * ntables);
buf.order(ByteOrder.LITTLE_ENDIAN);
for (int part = 0; part < ntables; part++) {
log.debug("position at part", part, "is", buf.position());
buf.put((byte) 1);
buf.put(Utils.toBytes(name, MAX_FILE_LEN, (byte) ' '));
buf.put(Utils.toBytes(ext, MAX_EXT_LEN, (byte) ' '));
// Size is only present in the first part
if (part == 0) {
log.debug("dirent", name, '.', ext, "size is going to", size);
buf.putInt(size);
} else {
buf.putInt(0);
}
buf.put((byte) (special? 0x3: 0));
buf.putChar((char) part);
// Write out the allocation of blocks for this entry.
buf.position(ENTRY_SIZE * part + 0x20);
blockTable.writeTable(buf, part);
}
buf.flip();
file.write(buf);
}
/**
* Get the file name.
*
* @return The file name.
*/
public String getName() {
return name;
}
/**
* Get the file extension.
*
* @return The file extension.
*/
public String getExt() {
return ext;
}
/**
* The full name is of the form 8+3 with a dot in between the name and
* extension. The full name is used as the index in the directory.
*
* @return The full name.
*/
public String getFullName() {
return name + '.' + ext;
}
/**
* Read in the block numbers from the given buffer. If this is the first
* directory block for this file, then the size is set too.
*
* @param buf The data as read from the file.
*/
void initBlocks(ByteBuffer buf) {
byte used = buf.get(OFF_USED_FLAG);
if (used != 1)
return;
int part = buf.get(OFF_FILE_PART) & 0xff;
if (part == 0 || (isSpecial() && part == 3))
size = buf.getInt(OFF_SIZE);
blockTable.readTable(buf);
initialized = true;
}
/**
* Get the file size.
*
* @return The size of the file in bytes.
*/
public int getSize() {
return size;
}
/**
* Set the file name. The name should be exactly eight characters long
* and it is truncated or left padded with zeros to make this true.
*
* @param name The file name.
*/
private void setName(String name) {
int len = name.length();
if (len > MAX_FILE_LEN) {
this.name = name.substring(0, 8);
} else if (len < MAX_FILE_LEN) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < MAX_FILE_LEN - len; i++) {
sb.append('0');
}
sb.append(name);
this.name = sb.toString();
} else
this.name = name;
}
/**
* Set the file extension. Can't be longer than three characters.
* @param ext The file extension.
*/
private void setExt(String ext) {
log.debug("ext len", ext.length());
if (ext.length() != MAX_EXT_LEN)
throw new IllegalArgumentException("File extension is wrong size");
this.ext = ext;
}
/**
* The number of blocks that the header covers. The header includes
* the directory for the purposes of this routine.
*
* @return The total number of header basic blocks (blocks of 512 bytes).
*/
int numberHeaderBlocks() {
return blockTable.getNBlockTables();
}
void setSize(int size) {
if (log.isDebugEnabled())
log.debug("setting size", getName(), getExt(), "to", size);
this.size = size;
}
/**
* Add a block without increasing the size of the file.
*
* @param n The block number.
*/
void addBlock(int n) {
blockTable.addBlock(n);
}
/**
* Set for the first directory entry that covers the header and directory
* itself.
*
* @param special Set to true to mark as the special first entry.
*/
public void setSpecial(boolean special) {
this.special = special;
}
public boolean isSpecial() {
return special;
}
/**
* Converts from a logical block to a physical block. If the block does
* not exist then 0xffff will be returned.
*
* @param lblock The logical block in the file.
* @return The corresponding physical block in the filesystem.
*/
public int getPhysicalBlock(int lblock) {
return blockTable.physFromLogical(lblock);
}
public BlockManager getBlockManager() {
return blockManager;
}
protected void setInitialized(boolean initialized) {
this.initialized = initialized;
}
protected boolean isInitialized() {
return initialized;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/FileImgChannel.java 0000644 0000764 0000764 00000004651 11532723256 023333 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 14, 2007
*/
package uk.me.parabola.imgfmt.sys;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import uk.me.parabola.imgfmt.ReadFailedException;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* This is an implementation of ImgChannel that delegates to a regular channel.
* It can therefore be used to read or write regular files on the file system.
*
* @author Steve Ratcliffe
*/
public class FileImgChannel implements ImgChannel {
private final FileChannel channel;
private long position;
public FileImgChannel(String filename, String mode) {
RandomAccessFile raf;
try {
raf = new RandomAccessFile(filename, mode);
} catch (FileNotFoundException e) {
throw new ReadFailedException("Could not open " + filename, e);
}
this.channel = raf.getChannel();
}
public FileImgChannel(FileChannel channel) {
this.channel = channel;
}
public int read(ByteBuffer dst) throws IOException {
int n = channel.read(dst);
if (n > 0)
position += n;
return n;
}
public boolean isOpen() {
return channel.isOpen();
}
public void close() throws IOException {
channel.close();
}
public int write(ByteBuffer src) throws IOException {
int n = channel.write(src);
position += n;
return n;
}
/**
* Get the file position. Note that this is a logical position relative to the
* beginning of the file (the file within the .img file, not the beginning of the
* .img file itself).
*
* @return The offset in bytes from the beginning of the file.
*/
public long position() {
return position;
}
/**
* Set the position within the file.
*
* @param pos The position to set.
*/
public void position(long pos) {
try {
channel.position(pos);
position = pos;
} catch (IOException e) {
throw new ReadFailedException("Could not seek", e);
}
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/package.html 0000644 0000764 0000764 00000000566 11154317465 022146 0 ustar steve steve
Filesystem implementation
This holds the filesystem implementation for the .img
format. I refer through out to a file system containing
files, rather than using the term sub-files as in John Mechalas'
document.
Another way to look at it (and probably a better one) is that it a kind
of archive format just like a .zip file.
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/BlockTable.java 0000644 0000764 0000764 00000007131 11503736442 022523 0 ustar steve steve /*
* Copyright (C) 2007 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-Feb-2007
*/
package uk.me.parabola.imgfmt.sys;
import uk.me.parabola.log.Logger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Holds block numbers for a file. It is part of the directory. For a file
* that needs more than one block several directory entries exist. Each of
* these has the header with the file name etc. in it, but the first one has
* extra flags and info.
*
*
What is important here is that only part of a full block is used to
* hold block numbers.
*
*
The entries are 512 bytes regardless of the block size.
*
* @author Steve Ratcliffe
*/
class BlockTable {
private static final Logger log = Logger.getLogger(BlockTable.class);
// Offset of the block table in the directory entry block.
private static final int BLOCKS_TABLE_START = 0x20;
private static final int ENTRY_SIZE = 512;
private static final int TABLE_SIZE = (ENTRY_SIZE - BLOCKS_TABLE_START)/2;
//private final int tableSize;
private int curroff;
private final List blocks;
private char[] currTable;
BlockTable() {
blocks = new ArrayList(200);
}
/**
* Write out the specified table to the given buffer.
*
* @param buf The buffer to write to.
* @param n The number of the block table to write out.
*/
public void writeTable(ByteBuffer buf, int n) {
char[] cbuf = blocks.get(n);
log.debug("block with length", cbuf.length);
for (char c : cbuf) {
buf.putChar(c);
}
}
/**
* Read a block table from the given buffer. The table is added to the
* list.
* @param buf The buffer to read from.
*/
public void readTable(ByteBuffer buf) {
buf.position(BLOCKS_TABLE_START);
buf.limit(ENTRY_SIZE);
char[] cbuf = newTable();
for (int i = 0; i < cbuf.length; i++) {
char c = buf.getChar();
cbuf[i] = c;
}
}
/**
* Add the given block number to this directory.
*
* @param n The block number to add.
*/
public void addBlock(int n) {
char[] thisTable = currTable;
if (curroff >= TABLE_SIZE || currTable == null)
thisTable = newTable();
thisTable[curroff++] = (char) n;
}
/**
* Given a logical block number, return the physical block number.
*
* @param lblock The logical block number, ie with respect to the file.
* @return The physical block number in the file system.
*/
public int physFromLogical(int lblock) {
int blockNum = lblock / TABLE_SIZE;
int offset = lblock - blockNum * TABLE_SIZE;
if (blockNum >= blocks.size())
return 0xffff;
char[] cbuf = blocks.get(blockNum);
return cbuf[offset];
}
/**
* Get the number of block tables. This is the number of blocks that
* will be used in the on disk directory structure.
*
* @return The number of blocks tables.
*/
public int getNBlockTables() {
return blocks.size();
}
/**
* Allocate a new block to hold more directory block numbers.
*
* @return Array for more numbers.
*/
private char[] newTable() {
char[] table = new char[TABLE_SIZE];
Arrays.fill(table, (char) 0xffff);
curroff = 0;
blocks.add(table);
currTable = table;
return table;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/Directory.java 0000644 0000764 0000764 00000014657 12323435234 022474 0 ustar steve steve /*
* 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: 26-Nov-2006
*/
package uk.me.parabola.imgfmt.sys;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* The directory. There is only one directory and it contains the
* file names and block information. On disk each entry is a
* multiple of the block size.
*
* @author Steve Ratcliffe
*/
class Directory {
private static final Logger log = Logger.getLogger(Directory.class);
//private final FileChannel file;
private ImgChannel chan;
private final BlockManager headerBlockManager;
private final int startEntry;
private long startPos;
// The list of files themselves.
private final Map entries = new LinkedHashMap();
Directory(BlockManager headerBlockManager, int startEntry) {
this.headerBlockManager = headerBlockManager;
this.startEntry = startEntry;
}
/**
* Create a new file in the directory.
*
* @param name The file name. Must be 8+3 characters.
* @param blockManager To allocate blocks for the created file entry.
* @return The new directory entity.
* @throws FileExistsException If the entry already
* exists.
*/
Dirent create(String name, BlockManager blockManager) throws FileExistsException {
// Check to see if it is already there.
if (entries.get(name) != null)
throw new FileExistsException("File " + name + " already exists");
Dirent ent;
if (name.equals(ImgFS.DIRECTORY_FILE_NAME)) {
ent = new HeaderDirent(name, blockManager);
} else {
ent = new Dirent(name, blockManager);
}
addEntry(ent);
return ent;
}
/**
* Initialise the directory for reading the file. The whole directory
* is read in.
*
* @throws IOException If it cannot be read.
*/
void readInit(byte xorByte) throws IOException {
assert chan != null;
ByteBuffer buf = ByteBuffer.allocate(512);
buf.order(ByteOrder.LITTLE_ENDIAN);
chan.position(startPos);
Dirent current = null;
while ((chan.read(buf)) > 0) {
buf.flip();
if(xorByte != 0) {
byte[] bufBytes = buf.array();
for(int i = 0; i < bufBytes.length; ++i)
bufBytes[i] ^= xorByte;
}
int used = buf.get(Dirent.OFF_FILE_USED);
if (used != 1)
continue;
String name = Utils.bytesToString(buf, Dirent.OFF_NAME, Dirent.MAX_FILE_LEN);
String ext = Utils.bytesToString(buf, Dirent.OFF_EXT, Dirent.MAX_EXT_LEN);
log.debug("readinit name", name, ext);
int flag = buf.get(Dirent.OFF_FLAG);
int part = buf.get(Dirent.OFF_FILE_PART) & 0xff;
if (flag == 3 && current == null) {
current = (Dirent) entries.get(ImgFS.DIRECTORY_FILE_NAME);
current.initBlocks(buf);
} else if (part == 0) {
current = create(name + '.' + ext, headerBlockManager);
current.initBlocks(buf);
} else {
assert current != null;
current.initBlocks(buf);
}
buf.clear();
}
}
/**
* Write out the directory to the file. The file should be correctly
* positioned by the caller.
*
* @throws IOException If there is a problem writing out any
* of the directory entries.
*/
public void sync() throws IOException {
// The first entry can't really be written until the rest of the directory is
// so we have to step through once to calculate the size and then again
// to write it out.
int headerEntries = 0;
for (DirectoryEntry dir : entries.values()) {
Dirent ent = (Dirent) dir;
log.debug("ent size", ent.getSize());
int n = ent.numberHeaderBlocks();
headerEntries += n;
}
// Save the current position
long dirPosition = chan.position();
int blockSize = headerBlockManager.getBlockSize();
// Get the number of blocks required for the directory entry representing the header.
// First calculate the number of blocks required for the directory entries.
int headerBlocks = (int) Math.ceil((startEntry + 1.0 + headerEntries) * DirectoryEntry.ENTRY_SIZE / blockSize);
int forHeader = (headerBlocks + DirectoryEntry.ENTRY_SIZE - 1)/DirectoryEntry.ENTRY_SIZE;
log.debug("header blocks needed", forHeader);
// There is nothing really wrong with larger values (perhaps, I don't
// know for sure!) but the code is written to make it 1, so make sure that it is.
assert forHeader == 1;
// Write the blocks that will will contain the header blocks.
chan.position(dirPosition + (long) forHeader * DirectoryEntry.ENTRY_SIZE);
for (DirectoryEntry dir : entries.values()) {
Dirent ent = (Dirent) dir;
if (!ent.isSpecial()) {
log.debug("wrting ", dir.getFullName(), " at ", chan.position());
log.debug("ent size", ent.getSize());
ent.sync(chan);
}
}
long end = (long) blockSize * headerBlockManager.getMaxBlock();
ByteBuffer buf = ByteBuffer.allocate((int) (end - chan.position()));
for (int i = 0; i < buf.capacity(); i++)
buf.put((byte) 0);
buf.flip();
chan.write(buf);
// Now go back and write in the directory entry for the header.
chan.position(dirPosition);
Dirent ent = (Dirent) entries.values().iterator().next();
log.debug("ent header size", ent.getSize());
ent.sync(chan);
}
/**
* Get the entries. Used for listing the directory.
*
* @return A list of the directory entries. They will be in the same
* order as in the file.
*/
public List getEntries() {
return new ArrayList(entries.values());
}
/**
* Add an entry to the directory.
*
* @param ent The entry to add.
*/
private void addEntry(DirectoryEntry ent) {
entries.put(ent.getFullName(), ent);
}
public void setFile(ImgChannel chan) {
this.chan = chan;
}
public void setStartPos(long startPos) {
this.startPos = startPos;
}
public DirectoryEntry lookup(String name) {
return entries.get(name);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/ImgHeader.java 0000644 0000764 0000764 00000030565 11771656621 022364 0 ustar steve steve /*
* 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: 26-Nov-2006
*/
package uk.me.parabola.imgfmt.sys;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Calendar;
import java.util.Date;
import uk.me.parabola.imgfmt.FileSystemParam;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
import static java.util.Arrays.asList;
/**
* The header at the very beginning of the .img filesystem. It has the
* same signature as a DOS partition table, although I don't know
* exactly how much the partition concepts are used.
*
* @author Steve Ratcliffe
*/
class ImgHeader {
private static final Logger log = Logger.getLogger(ImgHeader.class);
// Offsets into the header.
private static final int OFF_XOR = 0x0;
private static final int OFF_UPDATE_MONTH = 0xa;
private static final int OFF_UPDATE_YEAR = 0xb; // +1900 for val >= 0x63, +2000 for less
private static final int OFF_SUPP = 0xe; // Appears to be set for gmapsupp files
private static final int OFF_CHECKSUM = 0xf;
private static final int OFF_SIGNATURE = 0x10;
private static final int OFF_UNK_1 = 0x17;
// If this was a real boot sector these would be the meanings
private static final int OFF_SECTORS = 0x18;
private static final int OFF_HEADS = 0x1a;
private static final int OFF_CYLINDERS = 0x1c;
private static final int OFF_CREATION_DATE = 0x39;
// The block number where the directory starts.
private static final int OFF_DIRECTORY_START_BLOCK = 0x40;
private static final int OFF_MAP_FILE_INTENTIFIER = 0x41;
private static final int OFF_MAP_DESCRIPTION = 0x49; // 0x20 padded
private static final int OFF_HEADS2 = 0x5d;
private static final int OFF_SECTORS2 = 0x5f;
private static final int OFF_BLOCK_SIZE_EXPONENT1 = 0x61;
private static final int OFF_BLOCK_SIZE_EXPONENT2 = 0x62;
private static final int OFF_BLOCK_SIZE = 0x63;
// private static final int OFF_UKN_3 = 0x63;
private static final int OFF_MAP_NAME_CONT = 0x65;
// 'Partition table' offsets.
private static final int OFF_START_HEAD = 0x1bf;
private static final int OFF_START_SECTOR = 0x1c0;
private static final int OFF_START_CYLINDER = 0x1c1;
private static final int OFF_SYSTEM_TYPE = 0x1c2;
private static final int OFF_END_HEAD = 0x1c3;
private static final int OFF_END_SECTOR = 0x1c4;
private static final int OFF_END_CYLINDER = 0x1c5;
private static final int OFF_REL_SECTORS = 0x1c6;
private static final int OFF_NUMBER_OF_SECTORS = 0x1ca;
private static final int OFF_PARTITION_SIG = 0x1fe;
// Lengths of some of the fields
private static final int LEN_MAP_NAME_CONT = 30;
private static final int LEN_MAP_DESCRIPTION = 20;
private FileSystemParam fsParams;
private final ByteBuffer header = ByteBuffer.allocate(512);
private ImgChannel file;
private Date creationTime;
private int sectorsPerTrack;
private int headsPerCylinder;
// Signatures.
private static final byte[] FILE_ID = {
'G', 'A', 'R', 'M', 'I', 'N', '\0'};
private static final byte[] SIGNATURE = {
'D', 'S', 'K', 'I', 'M', 'G', '\0'};
private int numBlocks;
ImgHeader(ImgChannel chan) {
this.file = chan;
header.order(ByteOrder.LITTLE_ENDIAN);
}
/**
* Create a header from scratch.
* @param params File system parameters.
*/
void createHeader(FileSystemParam params) {
this.fsParams = params;
header.put(OFF_XOR, (byte) 0);
// Set the block size. 2^(E1+E2) where E1 is always 9.
int exp = 9;
int bs = params.getBlockSize();
for (int i = 0; i < 32; i++) {
bs >>>= 1;
if (bs == 0) {
exp = i;
break;
}
}
if (exp < 9)
throw new IllegalArgumentException("block size too small");
header.put(OFF_BLOCK_SIZE_EXPONENT1, (byte) 0x9);
header.put(OFF_BLOCK_SIZE_EXPONENT2, (byte) (exp - 9));
header.position(OFF_SIGNATURE);
header.put(SIGNATURE);
header.position(OFF_MAP_FILE_INTENTIFIER);
header.put(FILE_ID);
header.put(OFF_UNK_1, (byte) 0x2);
// Actually this may not be the directory start block, I am guessing -
// always assume it is 2 anyway.
header.put(OFF_DIRECTORY_START_BLOCK, (byte) fsParams.getDirectoryStartEntry());
header.position(OFF_CREATION_DATE);
Utils.setCreationTime(header, creationTime);
setDirectoryStartEntry(params.getDirectoryStartEntry());
// Set the times.
Date date = new Date();
setCreationTime(date);
setUpdateTime(date);
setDescription(params.getMapDescription());
// Checksum is not checked.
header.put(OFF_CHECKSUM, (byte) 0);
}
/**
* Write out the values associated with the partition sizes.
*
* @param blockSize Block size.
*/
private void writeSizeValues(int blockSize) {
int endSector = (int) (((numBlocks+1L) * blockSize + 511) / 512);
//System.out.printf("end sector %d %x\n", endSector, endSector);
// We have three maximum values for sectors, heads and cylinders. We attempt to find values
// for them that are larger than the
sectorsPerTrack = 32; // 6 bit value
headsPerCylinder = 128;
int cyls = 0x400;
// Try out various values of h, s and c until we find a combination that is large enough.
// I'm not entirely sure about the valid values, but it seems that only certain values work
// which is why we use values from a list.
// See: http://www.win.tue.nl/~aeb/partitions/partition_types-2.html for justification for the h list
out:
for (int h : asList(16, 32, 64, 128, 256)) {
for (int s : asList(4, 8, 16, 32)) {
for (int c : asList(0x20, 0x40, 0x80, 0x100, 0x200, 0x3ff)) {
log.info("shc=", s + "," + h + "," + c, "end=", endSector);
//System.out.println("shc=" + s + "," + h + "," + c + "end=" + endSector);
if (s * h * c > endSector) {
headsPerCylinder = h;
sectorsPerTrack = s;
cyls = c;
break out;
}
}
}
}
// This sectors, head, cylinders stuff appears to be used by mapsource
// and they have to be larger than the actual size of the map. It
// doesn't appear to have any effect on a garmin device or other software.
header.putShort(OFF_SECTORS, (short) sectorsPerTrack);
header.putShort(OFF_SECTORS2, (short) sectorsPerTrack);
header.putShort(OFF_HEADS, (short) headsPerCylinder);
header.putShort(OFF_HEADS2, (short) headsPerCylinder);
header.putShort(OFF_CYLINDERS, (short) cyls);
// Since there are only 2 bytes here it can overflow, if it
// does we replace it with 0xffff.
int blocks = (int) (endSector * 512L / blockSize);
char shortBlocks = blocks > 0xffff ? 0xffff : (char) blocks;
header.putChar(OFF_BLOCK_SIZE, shortBlocks);
header.put(OFF_PARTITION_SIG, (byte) 0x55);
header.put(OFF_PARTITION_SIG + 1, (byte) 0xaa);
// Partition starts at zero. This is 0,0,1 in CHS terms.
header.put(OFF_START_HEAD, (byte) 0);
header.put(OFF_START_SECTOR, (byte) 1);
header.put(OFF_START_CYLINDER, (byte) 0);
header.put(OFF_SYSTEM_TYPE, (byte) 0);
// Now calculate the CHS address of the last sector of the partition.
CHS chs = new CHS(endSector - 1);
header.put(OFF_END_HEAD, (byte) (chs.h));
header.put(OFF_END_SECTOR, (byte) ((chs.s) | ((chs.c >> 2) & 0xc0)));
header.put(OFF_END_CYLINDER, (byte) (chs.c & 0xff));
// Write the LBA block address of the beginning and end of the partition.
header.putInt(OFF_REL_SECTORS, 0);
header.putInt(OFF_NUMBER_OF_SECTORS, endSector);
log.info("number of blocks", endSector - 1);
}
void setHeader(ByteBuffer buf) {
buf.flip();
header.put(buf);
byte exp1 = header.get(OFF_BLOCK_SIZE_EXPONENT1);
byte exp2 = header.get(OFF_BLOCK_SIZE_EXPONENT2);
log.debug("header exponent", exp1, exp2);
fsParams = new FileSystemParam();
fsParams.setBlockSize(1 << (exp1 + exp2));
fsParams.setDirectoryStartEntry(header.get(OFF_DIRECTORY_START_BLOCK));
StringBuffer sb = new StringBuffer();
sb.append(Utils.bytesToString(buf, OFF_MAP_DESCRIPTION, LEN_MAP_DESCRIPTION));
sb.append(Utils.bytesToString(buf, OFF_MAP_NAME_CONT, LEN_MAP_NAME_CONT));
fsParams.setMapDescription(sb.toString().trim());
byte h = header.get(OFF_END_HEAD);
byte sc1 = header.get(OFF_END_SECTOR);
byte sc2 = header.get(OFF_END_CYLINDER);
CHS chs = new CHS();
chs.setFromPartition(h, sc1, sc2);
int lba = chs.toLba();
log.info("partition sectors", lba);
// ... more to do
}
void setFile(ImgChannel file) {
this.file = file;
}
FileSystemParam getParams() {
return fsParams;
}
/**
* Sync the header to disk.
* @throws IOException If an error occurs during writing.
*/
public void sync() throws IOException {
setUpdateTime(new Date());
writeSizeValues(fsParams.getBlockSize());
header.rewind();
file.position(0);
file.write(header);
file.position(fsParams.getDirectoryStartEntry() * 512L);
}
/**
* Set the update time.
* @param date The date to use.
*/
protected void setUpdateTime(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
header.put(OFF_UPDATE_YEAR, toYearCode(cal.get(Calendar.YEAR)));
header.put(OFF_UPDATE_MONTH, (byte) (cal.get(Calendar.MONTH)+1));
}
/**
* Set the description. It is spread across two areas in the header.
* @param desc The description.
*/
protected void setDescription(String desc) {
int len = desc.length();
if (len > 50)
throw new IllegalArgumentException("Description is too long (max 50)");
String part1, part2;
if (len > LEN_MAP_DESCRIPTION) {
part1 = desc.substring(0, LEN_MAP_DESCRIPTION);
part2 = desc.substring(LEN_MAP_DESCRIPTION, len);
} else {
part1 = desc.substring(0, len);
part2 = "";
}
header.position(OFF_MAP_DESCRIPTION);
header.put(toByte(part1));
for (int i = len; i < LEN_MAP_DESCRIPTION; i++)
header.put((byte) ' ');
header.position(OFF_MAP_NAME_CONT);
header.put(toByte(part2));
for (int i = Math.max(len - LEN_MAP_DESCRIPTION, 0); i < LEN_MAP_NAME_CONT; i++)
header.put((byte) ' ');
header.put((byte) 0); // really?
}
/**
* Convert a string to a byte array.
* @param s The string
* @return A byte array.
*/
private byte[] toByte(String s) {
// NB: what character set should be used?
return s.getBytes();
}
/**
* Convert to the one byte code that is used for the year.
* If the year is in the 1900, then subtract 1900 and add the result to 0x63,
* else subtract 2000.
* Actually looks simpler, just subtract 1900..
* @param y The year in real-world format eg 2006.
* @return A one byte code representing the year.
*/
private byte toYearCode(int y) {
return (byte) (y - 1900);
}
protected void setDirectoryStartEntry(int directoryStartEntry) {
header.put(OFF_DIRECTORY_START_BLOCK, (byte) directoryStartEntry);
fsParams.setDirectoryStartEntry(directoryStartEntry);
}
protected void setCreationTime(Date date) {
this.creationTime = date;
}
public void setNumBlocks(int numBlocks) {
this.numBlocks = numBlocks;
}
/**
* Represent a block number in the chs format.
*
* Note that this class uses the headsPerCylinder and sectorsPerTrack values
* from the enclosing class.
*
* @see Logical block addressing
*/
private class CHS {
private int h;
private int s;
private int c;
private CHS() {
}
public CHS(int lba) {
toChs(lba);
}
/**
* Calculate the CHS values from the the given logical block address.
* @param lba Input logical block address.
*/
private void toChs(int lba) {
h = (lba / sectorsPerTrack) % headsPerCylinder;
s = (lba % sectorsPerTrack) + 1;
c = lba / (sectorsPerTrack * headsPerCylinder);
}
/**
* Set from a partition table entry.
*
* The cylinder is 10 bits and is split between the top 2 bit of the sector
* value and its own byte.
*
* @param h The h value.
* @param sc1 The s value (6 bits) and top 2 bits of c.
* @param sc2 The bottom 8 bits of c.
*/
public void setFromPartition(byte h, byte sc1, byte sc2) {
this.h = h;
this.s = (sc1 & 0x3f) + ((sc2 >> 2) & 0xc0);
this.c = sc2 & 0xff;
}
public int toLba() {
return (c * headsPerCylinder + h) * sectorsPerTrack + (s - 1);
}
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/sys/HeaderDirent.java 0000644 0000764 0000764 00000004052 11511612275 023052 0 ustar steve steve /*
* Copyright (C) 2007 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: 27-Oct-2007
*/
package uk.me.parabola.imgfmt.sys;
/**
* This is a special class for the header. It makes it easier to bootstrap
* the directory by having a special implementation that starts up by knowing
* that the blocks in the header start from 0.
*
* @author Steve Ratcliffe
*/
class HeaderDirent extends Dirent {
HeaderDirent(String name, BlockManager blockManager) {
super(name, blockManager);
}
/**
* Converts from a logical block to a physical block. This is a special
* version that returns the logical block number when the {@link Dirent} is not
* set up. This allows us to bootstrap the reading of the header blocks.
* The header blocks always logical and physical blocks the same.
*
* @param lblock The logical block in the file.
* @return The corresponding physical block in the filesystem.
*/
public int getPhysicalBlock(int lblock) {
if (isInitialized()) {
log.debug("gpb (ok)");
return super.getPhysicalBlock(lblock);
} else {
log.debug("gpb (not setup)");
return lblock;
}
}
/**
* Get the file size. The file appears large until the first blocks are
* read in and then it will take on its actual size.
*
* @return The size of the file in bytes.
*/
public int getSize() {
if (isInitialized())
return super.getSize();
else
return getBlockManager().getBlockSize() * 32;
}
/**
* Always returns true as this is only used for the special header
* directory entry.
*
* @return Always returns true.
*/
public boolean isSpecial() {
return true;
}
} mkgmap-r3660/src/uk/me/parabola/imgfmt/FileExistsException.java 0000644 0000764 0000764 00000002227 10666506051 023642 0 ustar steve steve /*
* 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: 02-Dec-2006
*/
package uk.me.parabola.imgfmt;
import java.io.IOException;
/**
* Thrown when a file already exists and would be overwritten.
*
* @author Steve Ratcliffe
*/
public class FileExistsException extends IOException {
/**
* Constructs a new exception with the specified detail message. The
* cause is not initialized, and may subsequently be initialized by
* a call to {@link #initCause}.
*
* @param message the detail message. The detail message is saved for
* later retrieval by the {@link #getMessage()} method.
*/
public FileExistsException(String message) {
super(message);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/ReadFailedException.java 0000644 0000764 0000764 00000002536 10766754612 023557 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 14, 2007
*/
package uk.me.parabola.imgfmt;
/**
* @author Steve Ratcliffe
*/
public class ReadFailedException extends RuntimeException {
/**
* Constructs a new runtime exception with the specified detail message and
* cause.
Note that the detail message associated with cause
* is not automatically incorporated in this runtime exception's detail
* message.
*
* @param message the detail message (which is saved for later retrieval by the
* {@link #getMessage()} method).
* @param cause the cause (which is saved for later retrieval by the {@link
* #getCause()} method). (A null value is permitted, and indicates
* that the cause is nonexistent or unknown.)
* @since 1.4
*/
public ReadFailedException(String message, Throwable cause) {
super(message, cause);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/ 0000755 0000764 0000764 00000000000 12651103763 017634 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/Block.java 0000644 0000764 0000764 00000004006 11110050256 021514 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 19, 2007
*/
package uk.me.parabola.imgfmt.mps;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.io.StructuredOutputStream;
/**
* All the blocks in the file have a type and a length.
*
* @author Steve Ratcliffe
*/
public abstract class Block {
private final int type;
private final ByteArrayOutputStream output = new ByteArrayOutputStream();
protected Block(int type) {
this.type = type;
}
public void write(ImgChannel chan) throws IOException {
// First write the body to the byte buffer so that we know its length.
writeBody(new StructuredOutputStream(output));
ByteBuffer buf = ByteBuffer.allocate(16);
buf.order(ByteOrder.LITTLE_ENDIAN);
buf.put((byte) type);
char len = getLength();
buf.putChar(len);
// write the header.
buf.flip();
chan.write(buf);
// write the body.
buf = ByteBuffer.allocate(len);
buf.put(output.toByteArray());
buf.flip();
chan.write(buf);
}
/**
* Writes the body to the output stream given.
*
* @param out The stream to write to.
*/
protected abstract void writeBody(StructuredOutputStream out) throws IOException;
/**
* This is only valid after everything is written to the block.
*
* @return The length of the block (or the amount written already).
*/
private char getLength() {
int len = output.toByteArray().length;
assert len <= 0xffff;
return (char) len;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/MapsetBlock.java 0000644 0000764 0000764 00000002155 10763340724 022710 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 19, 2007
*/
package uk.me.parabola.imgfmt.mps;
import uk.me.parabola.io.StructuredOutputStream;
import java.io.IOException;
/**
* Block describing the map set.
*
* @author Steve Ratcliffe
*/
public class MapsetBlock extends Block {
private static final int BLOCK_TYPE = 0x56;
private String name = "OSM map set";
public MapsetBlock() {
super(BLOCK_TYPE);
}
protected void writeBody(StructuredOutputStream out) throws IOException {
out.writeString(name);
out.write(0); // unknown
}
public void setName(String name) {
if (name != null)
this.name = name;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/MpsFile.java 0000644 0000764 0000764 00000003575 11315746733 022056 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 19, 2007
*/
package uk.me.parabola.imgfmt.mps;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* This file is a description of the map set that is loaded into the
* gmapsupp.img file and an index of the maps that it contains.
*
* It is different than all the other files that fit inside the gmapsupp file
* in that it doesn't contain the common header. So it does not extend ImgFile.
*
* @author Steve Ratcliffe
*/
public class MpsFile {
private String mapsetName = "OSM map set";
private final Set products = new HashSet();
private final List maps = new ArrayList();
private final ImgChannel chan;
public MpsFile(ImgChannel chan) {
this.chan = chan;
}
public void sync() throws IOException {
for (MapBlock map : maps)
map.write(chan);
for (ProductBlock block : products)
block.write(chan);
MapsetBlock mapset = new MapsetBlock();
mapset.setName(mapsetName);
mapset.write(chan);
}
public void addMap(MapBlock map) {
maps.add(map);
}
public void addProduct(ProductBlock pb) {
products.add(pb);
}
public void setMapsetName(String mapsetName) {
this.mapsetName = mapsetName;
}
public void close() throws IOException {
chan.close();
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/MpsFileReader.java 0000644 0000764 0000764 00000005232 12075561467 023174 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 19, 2007
*/
package uk.me.parabola.imgfmt.mps;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* This file is a description of the map set that is loaded into the
* gmapsupp.img file and an index of the maps that it contains.
*
* It is different than all the other files that fit inside the gmapsupp file
* in that it doesn't contain the common header. So it does not extend ImgFile.
*
* @author Steve Ratcliffe
*/
public class MpsFileReader implements Closeable {
private final List maps = new ArrayList();
private final List products = new ArrayList();
private final ImgChannel chan;
private final ImgFileReader reader;
public MpsFileReader(ImgChannel chan) {
this.chan = chan;
this.reader = new BufferedImgFileReader(chan);
readBlocks();
}
private void readBlocks() {
byte type;
while ((type = reader.get()) > 0) {
int len = reader.getChar();
switch (type) {
case 0x4c:
readMapBlock();
break;
case 0x46:
readProductBlock();
break;
default:
// We always know the length, so just read over it
reader.get(len);
break;
}
}
}
private void readMapBlock() {
MapBlock block = new MapBlock();
int val = reader.getInt();
block.setIds(val >>> 16, val & 0xffff);
block.setMapNumber(reader.getInt());
block.setSeriesName(reader.getZString());
block.setMapDescription(reader.getZString());
block.setAreaName(reader.getZString());
block.setHexNumber(reader.getInt());
reader.getInt();
maps.add(block);
}
private void readProductBlock() {
ProductBlock block = new ProductBlock();
block.setProductId(reader.getChar());
block.setFamilyId(reader.getChar());
block.setDescription(reader.getZString());
products.add(block);
}
public List getMaps() {
return maps;
}
public List getProducts() {
return products;
}
public void close() throws IOException {
chan.close();
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/MapBlock.java 0000644 0000764 0000764 00000004373 11532723256 022200 0 ustar steve steve /*
* Copyright (C) 2007,2011.
*
* 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.mps;
import java.io.IOException;
import uk.me.parabola.io.StructuredOutputStream;
/**
* A block describing an individual map.
*
* The family id, product id, series name, area name and map description can
* be set per map.
*
* @author Steve Ratcliffe
*/
public class MapBlock extends Block {
private static final int BLOCK_TYPE = 0x4c;
private int familyId;
private int productId;
private int mapNumber;
private int hexNumber;
private String seriesName;
private String mapDescription;
private String areaName;
public MapBlock() {
super(BLOCK_TYPE);
}
protected void writeBody(StructuredOutputStream out) throws IOException {
out.write2(productId);
out.write2(familyId);
out.write4(mapNumber);
out.writeString(seriesName);
out.writeString(mapDescription);
out.writeString(areaName);
out.write4(hexNumber);
out.write4(0);
}
public void setIds(int familyId, int productId) {
this.familyId = familyId;
this.productId = productId;
}
public void setSeriesName(String seriesName) {
this.seriesName = seriesName;
}
public void setMapNumber(int mapNumber) {
this.mapNumber = mapNumber;
}
public void setHexNumber(int hexNumber) {
this.hexNumber = hexNumber;
}
public void setMapDescription(String mapDescription) {
this.mapDescription = mapDescription;
}
public void setAreaName(String areaName) {
this.areaName = areaName;
}
public int getFamilyId() {
return familyId;
}
public int getProductId() {
return productId;
}
public int getMapNumber() {
return mapNumber;
}
public int getHexNumber() {
return hexNumber;
}
public String getSeriesName() {
return seriesName;
}
public String getMapDescription() {
return mapDescription;
}
public String getAreaName() {
return areaName;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/ProductBlock.java 0000644 0000764 0000764 00000003676 11315746733 023114 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 19, 2007
*/
package uk.me.parabola.imgfmt.mps;
import java.io.IOException;
import uk.me.parabola.io.StructuredOutputStream;
/**
* A block describing a particular product. Not sure how this relates
* to the map set.
*
* @author Steve Ratcliffe
*/
public class ProductBlock extends Block {
private static final int BLOCK_TYPE = 0x46;
private int familyId;
private int productId;
private String description = "OSM maps";
public ProductBlock() {
super(BLOCK_TYPE);
}
protected void writeBody(StructuredOutputStream out) throws IOException {
out.write2(productId);
out.write2(familyId);
out.writeString(description);
}
public void setFamilyId(int familyId) {
this.familyId = familyId;
}
public int getFamilyId() {
return familyId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public int getProductId() {
return productId;
}
public void setDescription(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProductBlock that = (ProductBlock) o;
if (familyId != that.familyId) return false;
if (productId != that.productId) return false;
return true;
}
public int hashCode() {
int result = familyId;
result = 31 * result + productId;
return result;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/mps/package.html 0000644 0000764 0000764 00000001563 12152046274 022121 0 ustar steve steve
The MPS file
This file is similar to the TDB file in that it is a list of maps along with
other information describing the set. It is inserted into the gmapsupp.img
file.
A map will work without this file, but it is essential when you have maps from different
families loaded together as it allows you to see the names of the map
families and turn them on and off separately.
It isn't like the other files in the app package, in that it doesn't
begin with the common header. It should perhaps live with the TDB format or
in a similar top level place. It is here because it is included inside the
gmapsupp.img file and doesn't normally have a separate existance.
The names and lengths of the fields were obtained from the program gpsexplorer
by the author identified as 'garminmaploader@yahoo.com' and released under
GPL2+.
mkgmap-r3660/src/uk/me/parabola/imgfmt/FileNotWritableException.java 0000644 0000764 0000764 00000001677 11000461742 024613 0 ustar steve steve /*
* Copyright (C) 2007 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: 02-Sep-2007
*/
package uk.me.parabola.imgfmt;
import java.io.IOException;
/**
* If a file cannot be created, or written to, then this exception is thrown.
* @author Steve Ratcliffe
*/
public class FileNotWritableException extends IOException {
public FileNotWritableException(String s, Exception e) {
// super class does not have the constructor that takes an exception
super(s + ' ' + e.getMessage());
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/ 0000755 0000764 0000764 00000000000 12651103763 017615 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/app/ImgReader.java 0000644 0000764 0000764 00000002432 11400726372 022316 0 ustar steve steve /*
* 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;
import java.io.Closeable;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.util.Configurable;
/**
* Base class for all the img sub file reading classes.
*
* @author Steve Ratcliffe
*/
public abstract class ImgReader implements Closeable, Configurable {
private CommonHeader header;
private ImgFileReader reader;
public void close() {
Utils.closeFile(reader);
}
protected long position() {
return reader.position();
}
protected void position(long pos) {
reader.position(pos);
}
public CommonHeader getHeader() {
return header;
}
protected final void setHeader(CommonHeader header) {
this.header = header;
}
protected ImgFileReader getReader() {
return reader;
}
protected void setReader(ImgFileReader reader) {
this.reader = reader;
}
} mkgmap-r3660/src/uk/me/parabola/imgfmt/app/ImgFileReader.java 0000644 0000764 0000764 00000005754 11532723256 023134 0 ustar steve steve /*
* 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: 07-Dec-2006
*/
package uk.me.parabola.imgfmt.app;
import java.io.Closeable;
import uk.me.parabola.imgfmt.ReadFailedException;
/**
* For reading subfiles from the img. The focus of mkgmap is on writing,
* but some limited reading is needed for several operations.
*
* @author Steve Ratcliffe
*/
public interface ImgFileReader extends Closeable {
/**
* Get the position. Needed because may not be reflected in the underlying
* file if being buffered.
*
* @return The logical position within the file.
*/
public long position();
/**
* Set the position of the file.
* @param pos The new position in the file.
*/
void position(long pos);
/**
* Read in a single byte.
* @return The byte that was read.
*/
public byte get() throws ReadFailedException;
/**
* Read in two bytes. Done in the correct byte order.
* @return The 2 byte integer that was read.
*/
public char getChar() throws ReadFailedException;
/**
* Get a 3byte signed quantity.
*
* @return The value read.
* @throws ReadFailedException When the file cannot be read.
*/
public int get3() throws ReadFailedException;
/**
* Get a 3byte unsigned quantity.
*
* @return The value read.
* @throws ReadFailedException When the file cannot be read.
*/
public int getu3() throws ReadFailedException;
/**
* Read in a 4 byte value.
* @return A 4 byte integer.
*/
public int getInt() throws ReadFailedException;
/**
* Read a variable sized integer. The size is given.
* @param n The size of the integer to read. Must be 1 to 4.
* @return The integer which will not be sign extended if it is less
* than 4 bytes long.
*/
public int getUint(int n) throws ReadFailedException;
/**
* Read in an arbitrary length sequence of bytes.
*
* @param len The number of bytes to read.
*/
public byte[] get(int len) throws ReadFailedException;
/**
* Read a zero terminated string from the file.
* @return A string
* @throws ReadFailedException For failures.
*/
public String getZString() throws ReadFailedException;
/**
* Read in a string of digits in the compressed base 11 format that is used
* for phone numbers in the POI section.
* @param delimiter This will replace all digit 11 characters. Usually a
* '-' to separate numbers in a telephone. No doubt there is a different
* standard in each country.
* @return A phone number possibly containing the delimiter character.
*/
public String getBase11str(byte firstChar, char delimiter);
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/FileBackedImgFileWriter.java 0000644 0000764 0000764 00000012651 11642611367 025073 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* Write img file data to a temporary file. On a call to sync() the data
* is copied to the output channel.
*
* @author Steve Ratcliffe
*/
public class FileBackedImgFileWriter implements ImgFileWriter{
private final ImgChannel outputChan;
private final File tmpFile;
private final BufferedOutputStream file;
private final FileChannel tmpChannel;
public FileBackedImgFileWriter(ImgChannel chan, File outputDir) {
this.outputChan = chan;
try {
tmpFile = File.createTempFile("img", null, outputDir);
tmpFile.deleteOnExit();
FileOutputStream out = new FileOutputStream(tmpFile);
tmpChannel = out.getChannel();
file = new BufferedOutputStream(out, 16*1024);
} catch (IOException e) {
throw new MapFailedException("Could not create mdr temporary file");
}
}
/**
* Maps the temporary file and copies to the output channel.
*
* @throws IOException If there is an error writing.
*/
public void sync() throws IOException {
file.close();
FileInputStream is = null;
try {
is = new FileInputStream(tmpFile);
FileChannel channel = is.getChannel();
channel.transferTo(0, channel.size(), outputChan);
channel.close();
} finally {
Utils.closeFile(is);
if (!tmpFile.delete())
System.err.println("Could not delete mdr img temporary file");
}
}
/**
* Get the position. Have to flush the buffer before getting the position.
*
* @return The logical position within the file.
*/
public int position() {
try {
file.flush();
return (int) tmpChannel.position();
} catch (IOException e) {
return 0;
}
}
/**
* Set the position of the file.
* The buffer has to be flushed first.
*
* @param pos The new position in the file.
*/
public void position(long pos) {
try {
file.flush();
tmpChannel.position(pos);
} catch (IOException e) {
throw new MapFailedException("Could not set position in mdr tmp file");
}
}
/**
* Write out a single byte.
*
* @param b The byte to write.
*/
public void put(byte b) {
try {
file.write(b);
} catch (IOException e) {
throw new MapFailedException("could not write byte to mdr tmp file");
}
}
/**
* Write out two bytes. Can't use writeChar() since need to reverse the byte
* order.
*
* @param c The value to write.
*/
public void putChar(char c) {
try {
file.write(c);
file.write(c >> 8);
} catch (IOException e) {
throw new MapFailedException("could not write char to mdr tmp file");
}
}
/**
* Write out three bytes. Done in the little endian byte order.
*
* @param val The value to write, only the bottom three bytes will be written.
*/
public void put3(int val) {
try {
file.write(val);
file.write(val >> 8);
file.write(val >> 16);
} catch (IOException e) {
throw new MapFailedException("could not write3 to mdr tmp file");
}
}
/**
* Write out 4 byte value.
*
* @param val The value to write.
*/
public void putInt(int val) {
try {
file.write(val);
file.write(val >> 8);
file.write(val >> 16);
file.write(val >> 24);
} catch (IOException e) {
throw new MapFailedException("could not write int to mdr tmp file");
}
}
/**
* Write out an arbitrary length sequence of bytes.
*
* @param val The values to write.
*/
public void put(byte[] val) {
try {
file.write(val);
} catch (IOException e) {
throw new MapFailedException("could not write bytes to mdr tmp file");
}
}
/**
* Write out part of a byte array.
*
* @param src The array to take bytes from.
* @param start The start position.
* @param length The number of bytes to write.
*/
public void put(byte[] src, int start, int length) {
try {
file.write(src, start, length);
} catch (IOException e) {
throw new MapFailedException("could not write bytes to mdr tmp file");
}
}
/**
* Write out a complete byte buffer.
*
* @param src The buffer to write.
*/
public void put(ByteBuffer src) {
try {
file.flush();
tmpChannel.write(src);
} catch (IOException e) {
throw new MapFailedException("could not write buffer to mdr tmp file");
}
}
/**
* Returns the size of the file.
*
* @return The file size in bytes.
*/
public long getSize() {
try {
file.flush();
return tmpChannel.size();
} catch (IOException e) {
throw new MapFailedException("could not get size of mdr tmp file");
}
}
/**
* Closes this stream and releases any system resources associated with it. If the stream is already closed then
* invoking this method has no effect.
*
* @throws IOException if an I/O error occurs
*/
public void close() throws IOException {
outputChan.close();
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/ 0000755 0000764 0000764 00000000000 12651103763 020431 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypPoint.java 0000644 0000764 0000764 00000003712 11665675646 023107 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.nio.charset.CharsetEncoder;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Represents a POI in the typ file.
*
* @author Steve Ratcliffe
*/
public class TypPoint extends TypElement {
private Xpm nightXpm;
private static final byte F_BITMAP = 0x1;
private static final byte F_NIGHT_XPM = 0x2;
private static final byte F_LABEL = 0x4;
private static final byte F_EXTENDED_FONT = 0x8;
public void write(ImgFileWriter writer, CharsetEncoder encoder) {
offset = writer.position();
byte flags = F_BITMAP;
if (nightXpm != null)
flags |= F_NIGHT_XPM;
if (!labels.isEmpty())
flags |= F_LABEL;
if (fontStyle != 0 || dayFontColour != null || nightFontColour != null)
flags |= F_EXTENDED_FONT;
writer.put(flags);
// Width and height is the same for day and night images, so it is written once only.
ColourInfo colourInfo = xpm.getColourInfo();
writer.put((byte) colourInfo.getWidth());
writer.put((byte) colourInfo.getHeight());
// Day or only image
writeImage(writer, xpm);
if ((flags & F_NIGHT_XPM) != 0)
writeImage(writer, nightXpm);
if ((flags & F_LABEL) != 0)
writeLabelBlock(writer, encoder);
if ((flags & F_EXTENDED_FONT) != 0)
writeExtendedFontInfo(writer);
}
public void setNightXpm(Xpm nightXpm) {
this.nightXpm = nightXpm;
}
/**
* Points have full pixmaps with multiple colours, including 24 full colour images.
*/
public boolean simpleBitmap() {
return false;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/ShapeStacking.java 0000644 0000764 0000764 00000003137 11670361040 024016 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 15, 2007
*/
package uk.me.parabola.imgfmt.app.typ;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Holds the shape stacking section.
*
* Deals with sorting everything correctly, so no need to sort in the input file.
*
* @author Steve Ratcliffe
*/
public class ShapeStacking {
private final SortedMap bar = new TreeMap();
public void addPolygon(int level, int type, int subtype) {
int levelType = (level << 16) + type;
DrawOrder order = bar.get(levelType);
if (order == null) {
order = new DrawOrder(type);
bar.put(levelType, order);
}
order.addSubtype(subtype);
}
public void write(ImgFileWriter writer) {
int lastLevel = 1;
DrawOrder empty = new DrawOrder(0);
for (Map.Entry ent : bar.entrySet()) {
int level = (ent.getKey() >> 16) & 0xffff;
DrawOrder order = ent.getValue();
if (level != lastLevel) {
empty.write(writer);
lastLevel = level;
}
order.write(writer);
}
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TrueImage.java 0000644 0000764 0000764 00000006754 11711515370 023167 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import uk.me.parabola.imgfmt.app.BitWriter;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* A true colour image.
*
* The image is represented by an array of int, with each int in RGBA format.
*
* @author Steve Ratcliffe
*/
public class TrueImage implements Image {
private final ColourInfo colourInfo;
private final int[] image;
// If this is mode 16, then the transparent colour is set.
private int transparentPixel;
public TrueImage(ColourInfo colourInfo, int[] image) {
analyzeColours(image, colourInfo);
this.colourInfo = colourInfo;
this.image = image;
}
/**
* Write out the image. It is a set of pixel values that are full RGB values, rather than
* table driven as in the other image type. If the colour mode is 32 the colours have an
* extra 4 bit opacity value following.
*
* If the colour mode is 16, then the transparent pixel is written just before the image
* itself.
*/
public void write(ImgFileWriter writer) {
int width = colourInfo.getWidth();
int height = colourInfo.getHeight();
int mode = colourInfo.getColourMode();
// For mode 16, the transparent pixel precedes the pixmap data.
if (mode == 16) {
writer.put((byte) (transparentPixel>>8));
writer.put((byte) (transparentPixel>>16));
writer.put((byte) (transparentPixel>>24));
}
boolean hasAlpha = mode == 32;
// Unlike the xpm based images, the true-colour image format does not appear to
// have any padding so write as a continuous block.
BitWriter bitWriter = new BitWriter();
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
int col = image[h * width + w];
bitWriter.putn(col>>8 & 0xff, 8);
bitWriter.putn(col>>16 & 0xff, 8);
bitWriter.putn(col>>24 & 0xff, 8);
if (hasAlpha) {
int alpha = 0xff - (col & 0xff);
alpha = ColourInfo.alphaRound4(alpha);
bitWriter.putn(alpha, 4);
}
}
}
writer.put(bitWriter.getBytes(), 0, bitWriter.getLength());
}
/**
* Analyze the colours and determine if this should be a mode 16 or 32 image.
*
* By default it will be a mode 0 image. If there is any transparency the appropriate
* colour mode will be selected.
*
* @param image An images as an array of integers. Each integer is a colour in RGBA format.
* @param colourInfo The colour mode will be set in this.
*/
private void analyzeColours(int[] image, ColourInfo colourInfo) {
boolean hasTransparent = false;
boolean hasAlpha = false;
int nPixels = colourInfo.getWidth() * colourInfo.getHeight();
for (int i = 0; i < nPixels; i++) {
int col = image[i];
int a = col & 0xff;
if (a == 0) {
// Completely transparent, change all transparent pixels to the same value
if (hasTransparent)
image[i] = transparentPixel;
else
transparentPixel = image[i];
hasTransparent = true;
} else if (a < 255) {
// Partially transparent
hasAlpha = true;
}
}
if (hasAlpha)
colourInfo.setColourMode(32);
else if (hasTransparent)
colourInfo.setColourMode(16);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TYPHeader.java 0000644 0000764 0000764 00000013111 11665675646 023100 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 14, 2007
*/
package uk.me.parabola.imgfmt.app.typ;
import uk.me.parabola.imgfmt.app.CommonHeader;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Section;
/**
* The header for the TYP file.
*
* @author Thomas Lußnig
*/
public class TYPHeader extends CommonHeader {
public static final int HEADER_LEN = 0x9c; // 0x6e;
private char familyId;
private char productId;
private char codePage;
private final Section pointData = new Section();
private final Section lineData = new Section(pointData);
private final Section polygonData = new Section(lineData);
private final Section pointIndex = new Section(polygonData, (char) 2);
private final Section lineIndex = new Section(pointIndex, (char) 2);
private final Section polygonIndex = new Section(lineIndex, (char) 2);
private final Section shapeStacking = new Section(polygonIndex, (char) 5);
private final Section iconData = new Section(polygonIndex);
private final Section iconIndex = new Section(iconData, (char) 3);
private final Section labels = new Section(iconIndex);
private final Section stringIndex = new Section(labels);
private final Section typeIndex = new Section(stringIndex);
public TYPHeader() {
super(HEADER_LEN, "GARMIN TYP");
}
/**
* Read the rest of the header. Specific to the given file. It is guaranteed
* that the file position will be set to the correct place before this is
* called.
*
* @param reader The header is read from here.
*/
protected void readFileHeader(ImgFileReader reader) {
// Reset position for the real header reading code.
reader.position(COMMON_HEADER_LEN);
codePage = reader.getChar(); // 1252
pointData.setPosition(reader.getInt());
pointData.setSize(reader.getInt());
lineData.setPosition(reader.getInt());
lineData.setSize(reader.getInt());
polygonData.setPosition(reader.getInt());
polygonData.setSize(reader.getInt());
familyId = reader.getChar();
productId = reader.getChar();
pointIndex.setPosition(reader.getInt());
pointIndex.setItemSize(reader.getChar());
pointIndex.setSize(reader.getInt());
lineIndex.setPosition(reader.getInt());
lineIndex.setItemSize(reader.getChar());
lineIndex.setSize(reader.getInt());
polygonIndex.setPosition(reader.getInt());
polygonIndex.setItemSize(reader.getChar());
polygonIndex.setSize(reader.getInt());
shapeStacking.setPosition(reader.getInt());
shapeStacking.setItemSize(reader.getChar());
shapeStacking.setSize(reader.getInt());
}
/**
* Write the rest of the header. It is guaranteed that the writer will be set
* to the correct position before calling.
*
* This header appears to have a different layout to most other headers.
*
* @param writer The header is written here.
*/
protected void writeFileHeader(ImgFileWriter writer) {
writer.putChar(codePage);
pointData.writeSectionInfo(writer);
lineData.writeSectionInfo(writer);
polygonData.writeSectionInfo(writer);
writer.putChar(familyId);
writer.putChar(productId);
// Can't use Section.writeSectionInfo here as there is an unusual layout.
writeSectionInfo(writer, pointIndex);
writeSectionInfo(writer, lineIndex);
writeSectionInfo(writer, polygonIndex);
writeSectionInfo(writer, shapeStacking);
if (getHeaderLength() > 0x5b) {
writeSectionInfo(writer, iconIndex);
writer.put((byte) 0x13);
iconData.writeSectionInfo(writer);
writer.putInt(0);
}
if (getHeaderLength() > 0x6e) {
labels.writeSectionInfo(writer);
// not known, guessing. Different layout to other files.
writer.putInt(stringIndex.getItemSize());
writer.putInt(0x1b);
writer.putInt(stringIndex.getPosition());
writer.putInt(stringIndex.getSize());
writer.putInt(typeIndex.getItemSize());
writer.putInt(0x1b);
writer.putInt(typeIndex.getPosition());
writer.putInt(typeIndex.getSize());
writer.putChar((char) 0);
}
}
/**
* There is an unusual layout of the section pointers in the TYP file for the sections
* that have an item size.
*/
private void writeSectionInfo(ImgFileWriter writer, Section section) {
writer.putInt(section.getPosition());
writer.putChar(section.getItemSize());
writer.putInt(section.getSize());
}
void setCodePage(char codePage) {
this.codePage = codePage;
}
Section getPointData() {
return pointData;
}
void setFamilyId(char familyId) {
this.familyId = familyId;
}
void setProductId(char productId) {
this.productId = productId;
}
Section getPointIndex() {
return pointIndex;
}
Section getShapeStacking() {
return shapeStacking;
}
public Section getPolygonData() {
return polygonData;
}
public Section getPolygonIndex() {
return polygonIndex;
}
public Section getLineData() {
return lineData;
}
public Section getLineIndex() {
return lineIndex;
}
public Section getIconData() {
return iconData;
}
public Section getIconIndex() {
return iconIndex;
}
public Section getLabels() {
return labels;
}
public Section getStringIndex() {
return stringIndex;
}
public Section getTypeIndex() {
return typeIndex;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypLabel.java 0000644 0000764 0000764 00000002042 11665675646 023030 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
/**
* @author Steve Ratcliffe
*/
public class TypLabel {
private final int lang;
private final String text;
public TypLabel(String in) {
String[] split = in.split(",", 2);
int l;
String s;
try {
l = Integer.decode(split[0]);
s = split[1];
} catch (NumberFormatException e) {
l = 0;
s = in;
}
this.lang = l;
this.text = s;
}
public TypLabel(int lang, String text) {
this.lang = lang;
this.text = text;
}
public int getLang() {
return lang;
}
public String getText() {
return text;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypLabelException.java 0000644 0000764 0000764 00000001633 11665675646 024714 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
/**
* Used when a label cannot be converted. An error is thrown indicating a charset to try
* instead of the default system one.
*
* @author Steve Ratcliffe
*/
public class TypLabelException extends RuntimeException {
private String charsetName;
public TypLabelException(String charsetName) {
this.charsetName = charsetName;
}
public String getCharsetName() {
return charsetName;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypElement.java 0000644 0000764 0000764 00000012155 12072370014 023356 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Base routines and data used by points, lines and polygons.
*
* If fact they are all very similar, so there is very little extra in the
* subclasses apart from the write routine.
*
* @author Steve Ratcliffe
*/
public abstract class TypElement implements Comparable {
private int type;
private int subType;
protected final List labels = new ArrayList();
protected Xpm xpm;
protected int fontStyle;
protected Rgb dayFontColour;
protected Rgb nightFontColour;
protected int offset;
public void setType(int type) {
this.type = type;
}
public void setSubType(int subType) {
this.subType = subType;
}
public int getType() {
return type;
}
/**
* We sort these by type.
* Only the index needs to be sorted (probably) but we don't create the index separately.
*
* @param o The other object to compare against.
* @return The usual -1, 0, 1 for the other object being less than, equal, greater than than this.
*/
public int compareTo(TypElement o) {
int t1 = getTypeForFile();
int t2 = o.getTypeForFile();
if (t1 == t2)
return 0;
else if (t1 < t2)
return -1;
else
return 1;
}
/**
* Get the type in the format required for writing in the typ file sections.
*/
public int getTypeForFile() {
return (type << 5) | (subType & 0x1f);
}
public void addLabel(String text) {
labels.add(new TypLabel(text));
}
public void setXpm(Xpm xpm) {
this.xpm = xpm;
}
public void setFontStyle(int font) {
this.fontStyle = font;
}
public void setDayFontColor(String value) {
dayFontColour = new Rgb(value);
}
public void setNightCustomColor(String value) {
nightFontColour = new Rgb(value);
}
public abstract void write(ImgFileWriter writer, CharsetEncoder encoder);
public int getOffset() {
return offset;
}
/**
* Does this element have two colour bitmaps, with possible automatic night colours. For lines and polygons.
*
* Overridden for points and icons.
*/
public boolean simpleBitmap() {
return true;
}
/**
* Make the label block separately as we need its length before we write it out properly.
*
* @param encoder For encoding the strings as bytes.
* @return A byte buffer with position set to the length of the block.
*/
protected ByteBuffer makeLabelBlock(CharsetEncoder encoder) {
ByteBuffer out = ByteBuffer.allocate(256 * labels.size());
for (TypLabel tl : labels) {
out.put((byte) tl.getLang());
CharBuffer cb = CharBuffer.wrap(tl.getText());
try {
ByteBuffer buffer = encoder.encode(cb);
out.put(buffer);
} catch (CharacterCodingException ignore) {
String name = encoder.charset().name();
//System.out.println("cs " + name);
throw new TypLabelException(name);
}
out.put((byte) 0);
}
return out;
}
/**
* Write the label block, this is the same for all element types.
* @param encoder To properly encode the labels.
*/
protected void writeLabelBlock(ImgFileWriter writer, CharsetEncoder encoder) {
ByteBuffer out = makeLabelBlock(encoder);
int len = out.position();
// The length is encoded as a variable length integer with the length indicated by a suffix.
len = (len << 1) + 1;
int mask = ~0xff;
while ((len & mask) != 0) {
mask <<= 8;
len <<= 1;
}
// write out the length, I'm assuming that it will be 1 or 2 bytes
if (len > 0xff)
writer.putChar((char) len);
else
writer.put((byte) len);
// Prepare and write buffer
out.flip();
writer.put(out);
}
/**
* Write out extended font information, colour and size.
*
* This is the same for each element type.
*/
protected void writeExtendedFontInfo(ImgFileWriter writer) {
byte fontExt = (byte) fontStyle;
if (dayFontColour != null)
fontExt |= 0x8;
if (nightFontColour != null)
fontExt |= 0x10;
writer.put(fontExt);
if (dayFontColour != null)
dayFontColour.write(writer, (byte) 0x10);
if (nightFontColour != null)
nightFontColour.write(writer, (byte) 0x10);
}
/**
* Write out an image. The width and height are written separately, because they are not
* repeated for the night image.
*
* @param xpm Either the day or night XPM.
*/
protected void writeImage(ImgFileWriter writer, Xpm xpm) {
ColourInfo colourInfo = xpm.getColourInfo();
writer.put((byte) colourInfo.getNumberOfSColoursForCM());
writer.put((byte) colourInfo.getColourMode());
colourInfo.write(writer);
xpm.writeImage(writer);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/DrawOrder.java 0000644 0000764 0000764 00000001350 11670361040 023156 0 ustar steve steve package uk.me.parabola.imgfmt.app.typ;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Writeable;
/**
* The drawing order for a type and a set of subtypes.
*
* The drawing order is specified by the order of these within the file, rather than anything
* actually in the item.
*/
public class DrawOrder implements Writeable {
private final byte type;
private int subTypes;
private boolean hasSubtypes;
public DrawOrder(int type) {
this.type = (byte) (type & 0xff);
if (type >= 0x100)
hasSubtypes = true;
}
public void write(ImgFileWriter writer) {
writer.put(type);
writer.putInt(subTypes);
}
public void addSubtype(int subtype) {
if (hasSubtypes)
subTypes |= 1 << subtype;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypLine.java 0000644 0000764 0000764 00000005432 11665675646 022706 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.nio.charset.CharsetEncoder;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* A line as read from a typ.txt file.
*
* @author Steve Ratcliffe
*/
public class TypLine extends TypElement {
private static final int F_LABEL = 0x1;
private static final int F_USE_ROTATION = 0x2;
private static final int F_EXTENDED = 0x4;
private boolean useOrientation;
private byte lineWidth;
private byte borderWidth;
/**
* This is slightly different to the polygon case, but not much.
*
* The line width is held in the first byte along with the type.
* The colour scheme does not have a bit to say if a bitmap is used,
* as you can always have one.
*
* There is a border width that can be specified.
*
* @param encoder For the labels.
*/
public void write(ImgFileWriter writer, CharsetEncoder encoder) {
offset = writer.position();
byte flags = 0;
if (!labels.isEmpty())
flags |= F_LABEL;
if (fontStyle != 0 || dayFontColour != null)
flags |= F_EXTENDED;
if (!useOrientation)
flags |= F_USE_ROTATION;
int height = 0;
if (xpm.hasImage())
height = xpm.getColourInfo().getHeight();
ColourInfo colourInfo = xpm.getColourInfo();
int scheme = colourInfo.getColourScheme() & 0x7;
writer.put((byte) ((scheme & 0x7) | (height << 3)));
writer.put(flags);
colourInfo.write(writer);
if (xpm.hasImage())
xpm.writeImage(writer);
if (height == 0) {
writer.put(lineWidth);
if ((scheme&~1) != 6)
writer.put((byte) (lineWidth + 2*borderWidth));
}
// The labels have a length byte to show the number of bytes following. There is
// also a flag in the length. The strings have a language number proceeding them.
// The strings themselves are null terminated.
if ((flags & F_LABEL) != 0)
writeLabelBlock(writer, encoder);
// The extension section hold font style and colour information for the labels.
if ((flags & F_EXTENDED) != 0)
writeExtendedFontInfo(writer);
}
public void setUseOrientation(boolean useOrientation) {
this.useOrientation = useOrientation;
}
public void setLineWidth(int val) {
lineWidth = (byte) val;
}
public void setBorderWidth(int borderWidth) {
this.borderWidth = (byte) borderWidth;
}
public void finish() {
if (borderWidth != 0)
xpm.getColourInfo().setHasBorder(true);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TYPFile.java 0000644 0000764 0000764 00000015034 12072370014 022543 0 ustar steve steve /*
* 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
* Change: Thomas Lußnig
*/
package uk.me.parabola.imgfmt.app.typ;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.BufferedImgFileWriter;
import uk.me.parabola.imgfmt.app.ImgFile;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Section;
import uk.me.parabola.imgfmt.app.SectionWriter;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.srt.SortKey;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* The TYP file.
*
* @author Thomas Lußnig
* @author Steve Ratcliffe
*/
public class TYPFile extends ImgFile {
private static final Logger log = Logger.getLogger(TYPFile.class);
private final TYPHeader header = new TYPHeader();
private TypData data;
private final Map strToType = new TreeMap();
private final Map typeToStr = new TreeMap();
public TYPFile(ImgChannel chan) {
setHeader(header);
setWriter(new BufferedImgFileWriter(chan));
position(TYPHeader.HEADER_LEN);
}
public void write() {
ImgFileWriter writer = getWriter();
writer.position(TYPHeader.HEADER_LEN);
writeSection(writer, header.getPolygonData(), header.getPolygonIndex(), data.getPolygons());
writeSection(writer, header.getLineData(), header.getLineIndex(), data.getLines());
writeSection(writer, header.getPointData(), header.getPointIndex(), data.getPoints());
SectionWriter subWriter = header.getShapeStacking().makeSectionWriter(writer);
data.getStacking().write(subWriter);
Utils.closeFile(subWriter);
writeSection(writer, header.getIconData(), header.getIconIndex(), data.getIcons());
writeLabels(writer);
writeStrIndex(writer);
writerTypeIndex(writer);
zapZero(header.getShapeStacking(), header.getLabels(), header.getStringIndex(), header.getTypeIndex());
log.debug("syncing TYP file");
position(0);
getHeader().writeHeader(getWriter());
}
private void writeLabels(ImgFileWriter in) {
if (data.getIcons().isEmpty())
return;
SectionWriter writer = header.getLabels().makeSectionWriter(in);
List> keys = new ArrayList>();
Sort sort = data.getSort();
for (TypIconSet icon : data.getIcons()) {
String label = icon.getLabel();
if (label != null) {
SortKey key = sort.createSortKey(icon, label);
keys.add(key);
}
}
Collections.sort(keys);
// Offset 0 is reserved to mean no label.
writer.put((byte) 0);
for (SortKey key : keys) {
int off = writer.position();
TypIconSet icon = key.getObject();
int type = icon.getTypeForFile();
String label = icon.getLabel();
if (label != null) {
CharBuffer cb = CharBuffer.wrap(label);
CharsetEncoder encoder = data.getEncoder();
try {
ByteBuffer buffer = encoder.encode(cb);
writer.put(buffer);
// If we succeeded then note offsets for indexes
strToType.put(off, type);
typeToStr.put(type, off);
} catch (CharacterCodingException ignore) {
String name = encoder.charset().name();
throw new TypLabelException(name);
}
writer.put((byte) 0);
}
}
Utils.closeFile(writer);
}
private void writeStrIndex(ImgFileWriter in) {
SectionWriter writer = header.getStringIndex().makeSectionWriter(in);
int psize = ptrSize(header.getLabels().getSize());
header.getStringIndex().setItemSize((char) (3 + psize));
for (Map.Entry ent : strToType.entrySet()) {
putN(writer, psize, ent.getKey());
putN(writer, 3, ent.getValue());
}
Utils.closeFile(writer);
}
private void writerTypeIndex(ImgFileWriter in) {
SectionWriter writer = header.getTypeIndex().makeSectionWriter(in);
int psize = ptrSize(header.getLabels().getSize());
header.getTypeIndex().setItemSize((char) (3 + psize));
for (Map.Entry ent : typeToStr.entrySet()) {
putN(writer, 3, ent.getKey());
putN(writer, psize, ent.getValue());
}
Utils.closeFile(writer);
}
private void writeSection(ImgFileWriter writer, Section dataSection, Section indexSection,
List extends TypElement> elementData)
{
Collections.sort(elementData);
SectionWriter subWriter = dataSection.makeSectionWriter(writer);
CharsetEncoder encoder = data.getEncoder();
for (TypElement elem : elementData)
elem.write(subWriter, encoder);
Utils.closeFile(subWriter);
int size = dataSection.getSize();
int typeSize = indexSection.getItemSize();
int psize = ptrSize(size);
indexSection.setItemSize((char) (typeSize + psize));
subWriter = indexSection.makeSectionWriter(writer);
for (TypElement elem : elementData) {
int offset = elem.getOffset();
int type = elem.getTypeForFile();
putN(subWriter, typeSize, type);
putN(subWriter, psize, offset);
}
Utils.closeFile(subWriter);
zapZero(dataSection, indexSection);
}
private void zapZero(Section... sect) {
for (Section s : sect) {
if (s.getSize() == 0) {
s.setPosition(0);
s.setItemSize((char) 0);
}
}
}
private int ptrSize(int size) {
int psize = 1;
if (size > 0xffffff)
psize = 4;
else if (size > 0xffff)
psize = 3;
else if (size > 0xff)
psize = 2;
return psize;
}
protected void putN(ImgFileWriter writer, int n, int value) {
switch (n) {
case 1:
writer.put((byte) value);
break;
case 2:
writer.putChar((char) value);
break;
case 3:
writer.put3(value);
break;
case 4:
writer.putInt(value);
break;
default: // Don't write anything.
assert false;
break;
}
}
public void setData(TypData data) {
this.data = data;
TypParam param = data.getParam();
header.setCodePage((char) param.getCodePage());
header.setFamilyId((char) param.getFamilyId());
header.setProductId((char) param.getProductId());
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypData.java 0000644 0000764 0000764 00000005153 12254147734 022653 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.srt.Sort;
/**
* Holds all the data for a typ file.
*
* @author Steve Ratcliffe
*/
public class TypData {
private final ShapeStacking stacking = new ShapeStacking();
private final TypParam param = new TypParam();
private final List polygons = new ArrayList();
private final List lines = new ArrayList();
private final List points = new ArrayList();
private final List icons = new ArrayList();
private Sort sort;
private CharsetEncoder encoder;
public void addPolygonStackOrder(int level, int type, int subtype) {
stacking.addPolygon(level, type, subtype);
}
public Sort getSort() {
return sort;
}
public void setSort(Sort sort) {
if (sort == null)
return;
if (this.sort != null) {
int origCodepage = this.sort.getCodepage();
if (origCodepage != 0) {
if (origCodepage != sort.getCodepage()) {
// This is just a warning, not a definite problem
System.out.println("WARNING: SortCode in TYP txt file different from" +
" command line setting");
}
}
}
this.sort = sort;
encoder = sort.getCharset().newEncoder();
param.setCodePage(sort.getCodepage());
}
public void setFamilyId(int val) {
param.setFamilyId(val);
}
public void setProductId(int val) {
param.setProductId(val);
}
public ShapeStacking getStacking() {
return stacking;
}
public TypParam getParam() {
return param;
}
public void addPolygon(TypPolygon polygon) {
polygons.add(polygon);
}
public CharsetEncoder getEncoder() {
return encoder;
}
public List getPolygons() {
return polygons;
}
public void addLine(TypLine line) {
lines.add(line);
}
public List getLines() {
return lines;
}
public void addPoint(TypPoint point) {
points.add(point);
}
public List getPoints() {
return points;
}
public void addIcon(TypIconSet current) {
icons.add(current);
}
public List getIcons() {
return icons;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/Rgb.java 0000644 0000764 0000764 00000003402 11665675646 022027 0 ustar steve steve package uk.me.parabola.imgfmt.app.typ;
import uk.me.parabola.imgfmt.FormatException;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
public class Rgb {
private final int b;
private final int g;
private final int r;
private final int a;
public Rgb(int r, int g, int b, int a) {
this.r = r;
this.g = g;
this.b = b;
this.a = a;
}
public Rgb(int r, int g, int b) {
this(r, g, b, 0xff);
}
/**
* Initialise from a string.
*
* The format is #RRGGBB and without the '#'. You can also append
* an alpha value. FF for fully opaque, and 00 for fully transparent.
* The typ file only deals with fully transparent.
*
* @param in The string form of the color.
*/
public Rgb(String in) {
String colour = in;
if (colour.startsWith("#"))
colour = colour.substring(1);
r = Integer.parseInt(colour.substring(0, 2), 16);
g = Integer.parseInt(colour.substring(2, 4), 16);
b = Integer.parseInt(colour.substring(4, 6), 16);
if (colour.length() > 6)
a = Integer.parseInt(colour.substring(6, 8), 16);
else
a = 0xff;
}
/**
* Create a new Rgb from the given one, adding the given alpha channel value.
*/
public Rgb(Rgb rgb, int alpha) {
this(rgb.r, rgb.g, rgb.b, alpha);
}
public void write(ImgFileWriter writer, byte type) {
if (type != 0x10)
throw new FormatException("Invalid color deep");
writer.put((byte) b);
writer.put((byte) g);
writer.put((byte) r);
}
public boolean isTransparent() {
return a == 0;
}
public String toString() {
if (a == 0xff)
return String.format("#%02x%02x%02x", r, g, b);
else
return String.format("#%02x%02x%02x%02x", r, g, b, a);
}
public int getB() {
return b;
}
public int getG() {
return g;
}
public int getR() {
return r;
}
public int getA() {
return a;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/Xpm.java 0000644 0000764 0000764 00000002105 11665675646 022060 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Holds everything read from an XPM value in the typ txt file.
*
* @author Steve Ratcliffe
*/
public class Xpm {
private ColourInfo colourInfo;
private Image image;
public ColourInfo getColourInfo() {
return colourInfo;
}
public void setColourInfo(ColourInfo colourInfo) {
this.colourInfo = colourInfo;
}
public void setImage(Image image) {
this.image = image;
}
public boolean hasImage() {
return image != null;
}
public void writeImage(ImgFileWriter writer) {
image.write(writer);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/AlphaAdder.java 0000644 0000764 0000764 00000002020 11711515370 023250 0 ustar steve steve /*
* Copyright (C) 2012.
*
* 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.typ;
/**
* Interface for adding an alpha value to a previously saved colour value.
*
* The current TYP editors place the alpha value after the colour definition, eg:
*
* "#992299" alpha 5
*
* so we need to add the alpha value to a colour that has already been read
* in.
*
* @author Steve Ratcliffe
*/
public interface AlphaAdder {
/**
* Add an alpha value to the last colour that was saved.
*
* @param alpha A true alpha value ie 0 is transparent, 255 opaque.
*/
public void addAlpha(int alpha);
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/RgbWithTag.java 0000644 0000764 0000764 00000001610 11665675646 023316 0 ustar steve steve /*
* Copyright (C) 2008 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 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.typ;
/**
* @author Steve Ratcliffe
*/
public class RgbWithTag extends Rgb {
private final String tag;
public RgbWithTag(String tag, Rgb rgb) {
super(rgb.getR(), rgb.getG(), rgb.getB(), rgb.getA());
this.tag = tag;
}
public RgbWithTag(RgbWithTag rgb, int alpha) {
super(rgb, alpha);
this.tag = rgb.getTag();
}
public String getTag() {
return tag;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/package.html 0000644 0000764 0000764 00000000377 10735217321 022716 0 ustar steve steve
The TYP file for custom rendering styles
This file contains definitions of how a map element will be rendered
on the GPS which allows you to display things in a different way.
Not all devices support the TYP file however.
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypIconSet.java 0000644 0000764 0000764 00000003705 11665675646 023364 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* The new multiple icon format.
* There can be several icons at different resolutions.
*
* @author Steve Ratcliffe
*/
public class TypIconSet extends TypElement {
private final List icons = new ArrayList();
public void write(ImgFileWriter writer, CharsetEncoder encoder) {
offset = writer.position();
// Start with the number of icons
writer.put((byte) icons.size());
for (Xpm xpm : icons) {
ColourInfo colourInfo = xpm.getColourInfo();
int nbits = calcBits(colourInfo);
writer.putChar((char) (nbits/2));
writer.put((byte) 1);
writer.put((byte) colourInfo.getWidth());
writer.put((byte) colourInfo.getHeight());
writeImage(writer, xpm);
}
}
private int calcBits(ColourInfo colourInfo) {
int bits = 0;
int bpp = colourInfo.getBitsPerPixel();
bits += colourInfo.getWidth() * colourInfo.getHeight() * bpp;
bits += colourInfo.getNumberOfSColoursForCM() * 3 * 8;
if (colourInfo.getNumberOfColours() == 0 && colourInfo.getColourMode() == 0x10)
bits += 3*8;
bits += 0x2c;
return bits;
}
public void addIcon(Xpm xpm) {
icons.add(xpm);
}
public String getLabel() {
if (labels.isEmpty())
return null;
return labels.get(0).getText();
}
/**
* Icon sets can have full colour pixmaps.
*/
public boolean simpleBitmap() {
return false;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/BitmapImage.java 0000644 0000764 0000764 00000003131 11665675646 023473 0 ustar steve steve package uk.me.parabola.imgfmt.app.typ;
import uk.me.parabola.imgfmt.app.BitWriter;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Holds a bitmap image for the typ file.
*
* There are a number of different formats allowed.
*
* Based on code by Thomas Lußnig, but type and colour information separated out and
* deals with more than just points.
*/
public class BitmapImage implements Image {
private final ColourInfo colourInfo;
private final String image;
public BitmapImage(ColourInfo colourInfo, String image) {
this.colourInfo = colourInfo;
this.image = image;
}
public void write(ImgFileWriter writer) {
final int bitSize = colourInfo.getBitsPerPixel();
int cpp = colourInfo.getCharsPerPixel();
int width = colourInfo.getWidth();
int height = colourInfo.getHeight();
int i = 0;
for (int h = 0; h < height; h++) {
// Each row is padded to a byte boundary, creating a new bit writer for every
// row ensures that happens.
BitWriter bitWriter = new BitWriter();
for (int w = 0; w < width; w++) {
String idx = image.substring(i, i + cpp);
i += cpp;
int val = colourInfo.getIndex(idx);
bitWriter.putn(val, bitSize);
}
writer.put(bitWriter.getBytes(), 0, bitWriter.getLength());
}
}
public int compare(BitmapImage a, BitmapImage b) {
throw new UnsupportedOperationException();
//if (a == null)
// return 1;
//if (b == null)
// return -1;
//if (a.typ < b.typ)
// return -1;
//if (a.typ > b.typ)
// return 1;
//if (a.dayNight < b.dayNight)
// return -1;
//if (a.dayNight > b.dayNight)
// return 1;
//return 0;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypParam.java 0000644 0000764 0000764 00000002052 11657205245 023033 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
/**
* General TYP file parameters.
*
* @author Steve Ratcliffe
*/
public class TypParam {
private int familyId;
private int productId;
private int codePage;
public int getFamilyId() {
return familyId;
}
public void setFamilyId(int familyId) {
this.familyId = familyId;
}
public int getProductId() {
return productId;
}
public void setProductId(int productId) {
this.productId = productId;
}
public int getCodePage() {
return codePage;
}
public void setCodePage(int codePage) {
this.codePage = codePage;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/Image.java 0000644 0000764 0000764 00000001266 11665675646 022345 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import uk.me.parabola.imgfmt.app.Writeable;
/**
* Interface for the different image types.
*
* @author Steve Ratcliffe
*/
public interface Image extends Writeable {
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/TypPolygon.java 0000644 0000764 0000764 00000003227 11677054151 023427 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.nio.charset.CharsetEncoder;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
/**
* Holds the data for a polygon style.
*
* @author Steve Ratcliffe
*/
public class TypPolygon extends TypElement {
private static final int F_LABEL = 0x10;
private static final int F_EXTENDED = 0x20;
public void write(ImgFileWriter writer, CharsetEncoder encoder) {
offset = writer.position();
ColourInfo colourInfo = xpm.getColourInfo();
int scheme = colourInfo.getColourScheme();
if (!labels.isEmpty())
scheme |= F_LABEL;
if (fontStyle != 0 || dayFontColour != null)
scheme |= F_EXTENDED;
writer.put((byte) scheme);
colourInfo.write(writer);
if (xpm.hasImage())
xpm.writeImage(writer);
// The labels have a length byte to show the number of bytes following. There is
// also a flag in the length. The strings have a language number proceeding them.
// The strings themselves are null terminated.
if ((scheme & F_LABEL) != 0)
writeLabelBlock(writer, encoder);
// The extension section hold font style and colour information for the labels.
if ((scheme & F_EXTENDED) != 0) {
writeExtendedFontInfo(writer);
}
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/typ/ColourInfo.java 0000644 0000764 0000764 00000022152 11711515370 023352 0 ustar steve steve /*
* Copyright (C) 2011.
*
* 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.typ;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import uk.me.parabola.imgfmt.app.BitWriter;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Writeable;
/**
* Holds colour information for elements in the typ file.
*
* The Colour information can relate to a bitmap or solid shapes.
*
* @author Steve Ratcliffe
*/
public class ColourInfo implements Writeable, AlphaAdder {
private static final int S_NIGHT = 1;
private static final int S_DAY_TRANSPARENT = 0x2;
private static final int S_NIGHT_TRANSPARENT = 0x4;
private static final int S_HAS_BITMAP = 0x8;
private int numberOfColours;
private int numberOfSolidColours;
private boolean hasBitmap;
private boolean hasBorder;
private final List colours = new ArrayList();
private final Map indexMap = new HashMap();
private char width;
private char height;
private char charsPerPixel;
private boolean simple = true;
private char colourMode;
/**
* Add a colour for this element.
* @param tag The xpm tag that represents the colour.
* @param rgb The actual colour.
*/
public void addColour(String tag, Rgb rgb) {
RgbWithTag cwt = new RgbWithTag(tag, rgb);
colours.add(cwt);
}
/**
* Add a transparent colour. Convenience routine.
*/
public void addTransparent(String colourTag) {
addColour(colourTag, new Rgb(0, 0, 0, 0));
}
public void setHasBitmap(boolean hasBitmap) {
this.hasBitmap = hasBitmap;
}
/**
* The colour scheme in use. This is a bitmask that has the following bits:
* 0 - Has night colour
* 1 - day background colour is transparent
* 2 - night background colour is transparent
* 3 - has bitmap
*
* If there is no night colour, then set the night background colour bit to be the same as
* the day one.
*
* @return The colour scheme bitmask. The term colour scheme is historical, it doesn't really
* describe it.
*/
public int getColourScheme() {
if (numberOfColours == 0)
numberOfColours = colours.size();
int scheme = 0;
if (hasBitmap)
scheme |= S_HAS_BITMAP;
if (numberOfColours == 4)
scheme |= S_NIGHT;
if (!hasBitmap && !hasBorder && numberOfColours == 2)
scheme |= S_NIGHT | S_DAY_TRANSPARENT | S_NIGHT_TRANSPARENT;
if (numberOfColours < 2 || colours.get(1).isTransparent())
scheme |= S_DAY_TRANSPARENT;
if (numberOfColours == 4 && (colours.get(3).isTransparent()))
scheme |= S_NIGHT_TRANSPARENT;
if ((scheme & S_NIGHT) == 0)
if ((scheme & S_DAY_TRANSPARENT) != 0)
scheme |= S_NIGHT_TRANSPARENT;
return scheme;
}
/**
* Get the number of bits per pixel that will be used in the written bitmap.
*
* This depends on the colour mode and number of colours to be represented.
*/
public int getBitsPerPixel() {
if (simple)
return 1;
// number of colours includes the transparent pixel in colormode=0x10 so this
// works for all colour modes.
int nc = numberOfColours;
if (nc == 0)
return 24;
else if (nc < 2)
return 1;
else if (nc < 4)
return 2;
else if (nc < 16)
return 4;
else
return 8;
}
/**
* Write out the colours only.
*/
public void write(ImgFileWriter writer) {
if (colourMode == 0x20) {
writeColours20(writer);
} else {
for (Rgb rgb : colours) {
if (!rgb.isTransparent())
rgb.write(writer, (byte) 0x10);
}
}
}
/**
* Write out the colours in the colormode=x20 case.
*/
private void writeColours20(ImgFileWriter writer) {
BitWriter bw = new BitWriter();
for (Rgb rgb : colours) {
bw.putn(rgb.getB(), 8);
bw.putn(rgb.getG(), 8);
bw.putn(rgb.getR(), 8);
int alpha = 0xff - rgb.getA();
alpha = alphaRound4(alpha);
bw.putn(alpha, 4);
}
writer.put(bw.getBytes(), 0, bw.getLength());
}
/**
* Round alpha value to four bits.
* @param alpha The original alpha value eg 0xf0.
* @return Rounded alpha to four bits eg 0xe.
*/
static int alphaRound4(int alpha) {
int top = (alpha >> 4) & 0xf;
int low = alpha & 0xf;
int diff = low-top;
if (diff > 8)
top++;
else if (diff < -8)
top--;
return top;
}
public int getIndex(String tag) {
Integer ind = indexMap.get(tag);
// If this is a simple bitmap (for line or polygon), then the foreground colour is
// first and so has index 0, but we want the foreground to have index 1, so reverse.
if (simple)
ind = ~ind;
return ind;
}
public void setWidth(int width) {
this.width = (char) width;
}
public void setHeight(int height) {
this.height = (char) height;
}
public void setNumberOfColours(int numberOfColours) {
this.numberOfColours = numberOfColours;
}
public void setCharsPerPixel(int charsPerPixel) {
this.charsPerPixel = (char) (charsPerPixel == 0 ? 1 : charsPerPixel);
}
public int getNumberOfColours() {
return numberOfColours;
}
public int getNumberOfSColoursForCM() {
if (colourMode == 0x10)
return numberOfSolidColours;
else
return numberOfColours;
}
public int getCharsPerPixel() {
return charsPerPixel;
}
public int getHeight() {
return height;
}
public int getWidth() {
return width;
}
public int getColourMode() {
return colourMode;
}
public void setColourMode(int colourMode) {
this.colourMode = (char) colourMode;
}
public void setSimple(boolean simple) {
this.simple = simple;
}
public void setHasBorder(boolean hasBorder) {
this.hasBorder = hasBorder;
}
/**
* Replace the last pixel with a pixel with the same colour components and the given
* alpha.
*
* This is used when the alpha value is specified separately to the colour values in the
* input file.
* @param alpha The alpha value to be added to the pixel. This is a real alpha, not a transparency.
*/
public void addAlpha(int alpha) {
int last = colours.size();
RgbWithTag rgb = colours.get(last - 1);
rgb = new RgbWithTag(rgb, alpha);
colours.set(last - 1, rgb);
}
/**
* Analyse the colour pallet and normalise it.
*
* Try to work out what is required from the supplied colour pallet and set the colour mode
* and rearrange transparent pixels if necessary to be in the proper place.
*
* At the end we build the index from colour tag to pixel index.
*
* @param simple If this is a line or polygon.
* @return A string describing the validation failure.
*/
public String analyseColours(boolean simple) {
setSimple(simple);
if (simple) {
// There can be up to four colours, no partial transparency, and a max of one transparent pixel
// in each of the day/night sections.
if (numberOfColours > 4)
return ("Too many colours for a line or polygon");
if (numberOfColours == 0)
return "Line or polygon cannot have zero colours";
// Putting the transparent pixel first is common, so reverse if found
if (colours.get(0).isTransparent()) {
if (numberOfColours < 2)
return "Only colour cannot be transparent for line or polygon";
swapColour(0, 1);
}
if (numberOfColours > 2 && colours.get(2).isTransparent()) {
if (numberOfColours < 4)
return "Only colour cannot be transparent for line or polygon";
swapColour(2, 3);
}
// There can only be one transparent pixel per colour pair
if (numberOfColours > 1 && colours.get(0).isTransparent())
return "Both day foreground and background are transparent";
if (numberOfColours > 3 && colours.get(2).isTransparent())
return "Both night foreground and background are transparent";
} else {
int transIndex = 0; // index of last transparent pixel, only used when there is only one
int nTrans = 0; // completely transparent
int nAlpha = 0; // partially transparent
int count = 0; // total number of colours
for (RgbWithTag rgb : colours) {
if (rgb.isTransparent()) {
nTrans++;
transIndex = count;
}
if (rgb.getA() != 0xff && rgb.getA() != 0)
nAlpha++;
count++;
}
if (nAlpha > 0 || (count > 0 && count == nTrans)) {
// If there is any partial transparency we need colour mode 0x20
// Also if there is only one pixel and it is transparent, since otherwise there would be zero
// solid colours and that is a special case used to indicate a true colour pixmap.
colourMode = 0x20;
} else if (nTrans == 1) {
colourMode = 0x10;
// Ensure the transparent pixel is at the end
RgbWithTag rgb = colours.remove(transIndex);
colours.add(rgb);
}
}
int count = 0;
for (RgbWithTag rgb : colours) {
indexMap.put(rgb.getTag(), count++);
if (!rgb.isTransparent())
numberOfSolidColours++;
}
return null;
}
private void swapColour(int c1, int c2) {
RgbWithTag tmp = colours.get(c1);
colours.set(c1, colours.get(c2));
colours.set(c2, tmp);
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/ImgFile.java 0000644 0000764 0000764 00000004400 11400726372 021770 0 ustar steve steve /*
* 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;
import java.io.Closeable;
import java.io.IOException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.log.Logger;
/**
* Base class for all the img files. There is a common header that
* all the sub-files share. They also have means of reading and writing
* themselves.
*
* @author Steve Ratcliffe
*/
public abstract class ImgFile implements Closeable {
private static final Logger log = Logger.getLogger(ImgFile.class);
private CommonHeader header;
private ImgFileWriter writer;
private ImgFileReader reader;
private boolean readable;
private boolean writable;
public void close() {
try {
sync();
} catch (IOException e) {
log.debug("could not sync file");
}
Utils.closeFile(writer);
Utils.closeFile(reader);
}
public int position() {
if (readable)
return (int) reader.position();
else
return writer.position();
}
protected CommonHeader getHeader() {
return header;
}
public long getSize() {
if (writable)
return writer.getSize();
throw new UnsupportedOperationException("getSize not implemented for read");
}
protected void position(long pos) {
writer.position(pos);
}
protected final void sync() throws IOException {
if (!writable)
return;
getWriter().sync();
}
protected ImgFileWriter getWriter() {
return writer;
}
protected void setWriter(ImgFileWriter writer) {
writable = true;
this.writer = writer;
}
protected ImgFileReader getReader() {
return reader;
}
protected void setReader(ImgFileReader reader) {
readable = true;
this.reader = reader;
}
protected final void setHeader(CommonHeader header) {
this.header = header;
}
protected boolean isWritable() {
return writable;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/Exit.java 0000644 0000764 0000764 00000003115 11400726372 021367 0 ustar steve steve /*
* Copyright (C) 2007 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: Jan 1, 2008
*/
package uk.me.parabola.imgfmt.app;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.lbl.ExitFacility;
import uk.me.parabola.imgfmt.app.lbl.Highway;
/**
* Represent a motorway exit
*
* @author Mark Burton
*/
public class Exit {
public final static String TAG_ROAD_REF = "exit:road_ref";
public final static String TAG_TO = "exit:to";
public final static String TAG_FACILITY = "exit:facility";
private final Highway highway;
private Label description;
private final List facilities = new ArrayList();
public Exit(Highway highway) {
this.highway = highway;
}
public void setDescription(Label description) {
this.description = description;
}
public void addFacility(ExitFacility facility) {
facilities.add(facility);
}
public boolean getOvernightParking() {
return false; // FIXME
}
public Highway getHighway() {
return highway;
}
public List getFacilities() {
return facilities;
}
public Label getDescription() {
return description;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/Section.java 0000644 0000764 0000764 00000007117 11657205245 022075 0 ustar steve steve /*
* Copyright (C) 2007 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: Dec 15, 2007
*/
package uk.me.parabola.imgfmt.app;
import java.io.IOException;
/**
* Represents an item size the position where those items start and the
* total size of the section.
*/
public class Section {
private char itemSize;
private int size;
private int position;
private Section link;
private int extraValue;
public Section() {
}
public Section(char itemSize) {
this.itemSize = itemSize;
}
public Section(Section link, char itemSize) {
this.itemSize = itemSize;
this.link = link;
}
public Section(Section link) {
this.link = link;
}
public void inc() {
size += itemSize;
}
public char getItemSize() {
return itemSize;
}
public void setItemSize(char itemSize) {
this.itemSize = itemSize;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
/**
* Get the start position of this section. If this is linked to another
* section, then we return the end address of that section.
* @return The first offset for this section.
*/
public int getPosition() {
if (link != null)
return link.getEndPos();
return position;
}
public void setPosition(int position) {
this.position = position;
// Setting a position breaks the link
this.link = null;
}
/**
* Get the position of the end of the section.
* @return The offset of the end of the section relative to the beginning
* of the application file.
*/
public int getEndPos() {
return getPosition() + size;
}
public String toString() {
return "pos=" + getPosition() + ", size=" + size + ", itemSize=" + itemSize;
}
/**
* Get the number of items in the section. This should only be called
* if the itemSize is set.
* @return The number of items in the section, or zero if this is not
* a fixed size item kind of section.
*/
public int getNumItems() {
if (itemSize == 0)
return 0;
return size/ (int) itemSize;
}
protected int getExtraValue() {
return extraValue;
}
public void setExtraValue(int extraValue) {
this.extraValue = extraValue;
}
public void readSectionInfo(ImgFileReader reader, boolean withItemSize) {
setPosition(reader.getInt());
setSize(reader.getInt());
if (withItemSize)
setItemSize(reader.getChar());
}
public SectionWriter makeSectionWriter(ImgFileWriter writer) {
setPosition(writer.position());
return new SectionWriter(writer, this);
}
public void writeSectionInfo(ImgFileWriter writer) {
writeSectionInfo(writer, false);
}
public void writeSectionInfo(ImgFileWriter writer, boolean withItemSize) {
writeSectionInfo(writer, withItemSize, false);
}
public void writeSectionInfo(ImgFileWriter writer, boolean withItemSize, boolean withExtraValue) {
writer.putInt(getPosition());
writer.putInt(getSize());
if (withItemSize || getItemSize() > 0)
writer.putChar(getItemSize());
if (withExtraValue)
writer.putInt(getExtraValue());
}
public static void close(ImgFileWriter writer) {
assert writer instanceof SectionWriter;
try {
writer.close();
} catch (IOException e) {
// ignore as this is only for section writers.
}
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/BufferedImgFileWriter.java 0000644 0000764 0000764 00000012151 12333071201 024620 0 ustar steve steve /*
* 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: 07-Dec-2006
*/
package uk.me.parabola.imgfmt.app;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.log.Logger;
/**
* A straight forward implementation that just keeps all the data in a buffer
* until the file needs to be written to disk.
*
* @author Steve Ratcliffe
*/
public class BufferedImgFileWriter implements ImgFileWriter {
private static final Logger log = Logger.getLogger(BufferedImgFileWriter.class);
private static final int KBYTE = 1024;
private static final int INIT_SIZE = 16 * KBYTE;
private static final int GROW_SIZE = 128 * KBYTE;
private static final int GUARD_SIZE = KBYTE;
private final ImgChannel chan;
private ByteBuffer buf = ByteBuffer.allocate(INIT_SIZE);
private int bufferSize = INIT_SIZE;
// The size of the file. Note that for this to be set properly, the
// position must be set to a low value after the full file is written. This
// always happens because we go back and write the header after all is
// written.
private int maxSize;
// The maximum allowed file size.
private long maxAllowedSize = 0xffffff;
public BufferedImgFileWriter(ImgChannel chan) {
this.chan = chan;
buf.order(ByteOrder.LITTLE_ENDIAN);
}
/**
* Called to write out any saved buffers. The strategy may write
* directly to the file in which case this would have nothing or
* little to do.
*/
public void sync() throws IOException {
buf.limit(maxSize);
buf.position(0);
log.debug("syncing to pos", chan.position(), ", size", buf.limit());
chan.write(buf);
}
/**
* Get the position. Needed because may not be reflected in the underlying
* file if being buffered.
*
* @return The logical position within the file.
*/
public int position() {
return buf.position();
}
/**
* Set the position of the file.
*
* @param pos The new position in the file.
*/
public void position(long pos) {
int cur = position();
if (cur > maxSize)
maxSize = cur;
buf.position((int) pos);
}
/**
* Called when the stream is closed. Any resources can be freed.
*/
public void close() throws IOException {
chan.close();
}
/**
* Write out a single byte.
*
* @param b The byte to write.
*/
public void put(byte b) {
ensureSize(1);
buf.put(b);
}
/**
* Write out two bytes. Done in the correct byte order.
*
* @param c The value to write.
*/
public void putChar(char c) {
ensureSize(2);
buf.putChar(c);
}
/**
* Write out a 3 byte value in the correct byte order etc.
*
* @param val The value to write.
*/
public void put3(int val) {
ensureSize(3);
buf.put((byte) (val & 0xff));
buf.putChar((char) (val >> 8));
}
/**
* Write out 4 byte value.
*
* @param val The value to write.
*/
public void putInt(int val) {
ensureSize(4);
buf.putInt(val);
}
/**
* Write out an arbitrary length sequence of bytes.
*
* @param val The values to write.
*/
public void put(byte[] val) {
ensureSize(val.length);
buf.put(val);
}
/**
* Write out part of a byte array.
*
* @param src The array to take bytes from.
* @param start The start position.
* @param length The number of bytes to write.
*/
public void put(byte[] src, int start, int length) {
ensureSize(length);
buf.put(src, start, length);
}
public void put(ByteBuffer src) {
ensureSize(src.limit());
buf.put(src);
}
/**
* Get the size of the file as written.
*
* NOTE: that calling this is only valid at certain times.
*
* @return The size of the file, if it is available.
*/
public long getSize() {
return maxSize;
}
public ByteBuffer getBuffer() {
return buf;
}
/**
* Make sure there is enough room for the data we are about to write.
*
* @param length The amount of data.
*/
private void ensureSize(int length) {
int needed = buf.position() + length;
if (needed > (bufferSize - GUARD_SIZE)) {
while(needed > (bufferSize - GUARD_SIZE))
bufferSize += GROW_SIZE;
if (bufferSize > maxAllowedSize) {
// Previous message was confusing people, although it is difficult to come
// up with something that is strictly true in all situations.
throw new MapFailedException(
"There is not enough room in a single garmin map for all the input data." +
" The .osm file should be split into smaller pieces first.");
}
ByteBuffer newb = ByteBuffer.allocate(bufferSize);
newb.order(ByteOrder.LITTLE_ENDIAN);
buf.flip();
newb.put(buf);
buf = newb;
}
}
public void setMaxSize(long maxSize) {
this.maxAllowedSize = maxSize;
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/srt/ 0000755 0000764 0000764 00000000000 12651103763 020425 5 ustar steve steve mkgmap-r3660/src/uk/me/parabola/imgfmt/app/srt/SRTFile.java 0000644 0000764 0000764 00000011343 12346663602 022546 0 ustar steve steve /*
* Copyright (C) 2010.
*
* 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.srt;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import uk.me.parabola.imgfmt.app.BufferedImgFileWriter;
import uk.me.parabola.imgfmt.app.ImgFile;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.SectionWriter;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* The SRT file. This contains a table showing the sort order of
* the characters that is being used.
*
* @author Steve Ratcliffe
*/
public class SRTFile extends ImgFile {
private final SRTHeader header;
private Sort sort;
private boolean isMulti;
private String description;
private final List srt8Starts = new ArrayList<>();
public SRTFile(ImgChannel chan) {
header = new SRTHeader();
setHeader(header);
BufferedImgFileWriter fileWriter = new BufferedImgFileWriter(chan);
fileWriter.setMaxSize(Long.MAX_VALUE);
setWriter(fileWriter);
// Position at the start of the writable area.
position(header.getHeaderLength());
}
/**
* Write out the file.
* This file has an unusual layout. There are several header like structures within
* the main body of the file, with the real header being very small.
*/
public void write() {
ImgFileWriter writer = getWriter();
writeDescription(writer);
SectionWriter subWriter = header.makeSectionWriter(writer);
subWriter.position(sort.isMulti()? SRTHeader.HEADER3_MULTI_LEN: SRTHeader.HEADER3_LEN);
writeSrt4Chars(subWriter);
writeSrt5Expansions(subWriter);
if (sort.isMulti()) {
for (int i = 0; i <= sort.getMaxPage(); i++)
srt8Starts.add(-1);
writeSrt8(subWriter);
writeSrt7(subWriter);
}
subWriter.close();
// Header 2 is just after the real header
writer.position(header.getHeaderLength());
header.writeHeader2(writer);
// Header 3 is after the description
writer.position(header.getHeaderLength() + description.length() + 1 + SRTHeader.HEADER2_LEN);
header.writeHeader3(writer);
header.writeHeader(writer);
}
private void writeDescription(ImgFileWriter writer) {
writer.position(header.getHeaderLength() + SRTHeader.HEADER2_LEN);
writer.put(description.getBytes(Charset.forName("ascii")));
writer.put((byte) 0);
header.endDescription(writer.position());
}
/**
* Write SRT4 the character table.
*
* @param writer The img file writer.
*/
private void writeSrt4Chars(ImgFileWriter writer) {
for (int i = 1; i < 256; i++) {
writer.put(sort.getFlags(i));
writeWeights(writer, i);
}
header.endCharTable(writer.position());
}
private void writeWeights(ImgFileWriter writer, int i) {
if (isMulti) {
writer.putChar((char) sort.getPrimary(i));
writer.put((byte) sort.getSecondary(i));
writer.put((byte) sort.getTertiary(i));
} else {
writer.put((byte) sort.getPrimary(i));
writer.put((byte) ((sort.getTertiary(i) << 4) | (sort.getSecondary(i) & 0xf)));
}
}
/**
* Write SRT5, the expansion table.
*
* Write out the expansion table. This is referenced from the character table, when
* the top nibble of the type is set via the primary position value.
*/
private void writeSrt5Expansions(ImgFileWriter writer) {
int size = sort.getExpansionSize();
for (int j = 1; j <= size; j++) {
CodePosition b = sort.getExpansion(j);
if (isMulti) {
writer.putChar(b.getPrimary());
writer.put(b.getSecondary());
writer.put(b.getTertiary());
} else {
writer.put((byte) b.getPrimary());
writer.put((byte) ((b.getTertiary() << 4) | (b.getSecondary() & 0xf)));
}
}
header.endTab2(writer.position());
}
private void writeSrt7(SectionWriter writer) {
assert sort.isMulti();
for (int i = 1; i <= sort.getMaxPage(); i++) {
writer.putInt(srt8Starts.get(i));
}
header.endSrt7(writer.position());
}
private void writeSrt8(SectionWriter writer) {
assert sort.isMulti();
int offset = 0;
for (int p = 1; p <= sort.getMaxPage(); p++) {
if (sort.hasPage(p)) {
srt8Starts.set(p, offset);
for (int j = 0; j < 256; j++) {
int ch = p * 256 + j;
writer.put(sort.getFlags(ch));
writeWeights(writer, ch);
offset += 5;
}
}
}
header.endSrt8(writer.position());
}
public void setSort(Sort sort) {
this.sort = sort;
header.setSort(sort);
description = sort.getDescription();
isMulti = sort.isMulti();
}
}
mkgmap-r3660/src/uk/me/parabola/imgfmt/app/srt/Sort.java 0000644 0000764 0000764 00000053127 12354242000 022213 0 ustar steve steve /*
* Copyright (C) 2010, 2011.
*
* 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.srt;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import java.text.CollationKey;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.app.Label;
/**
* Represents the sorting positions for all the characters in a codepage.
*
* A map contains a file that determines how the characters are to be sorted. So we
* have to have to be able to create such a file and sort with exactly the same rules
* as is contained in it.
*
* What about the java {@link java.text.RuleBasedCollator}? It turns out that it is possible to
* make it work in the way we need it to, although it doesn't help with creating the srt file.
* Also it is significantly slower than this implementation, so this one is staying. I also
* found that sorting with the sort keys and the collator gave different results in some
* cases. This implementation does not.
*
* Be careful when benchmarking. With small lists (< 10000 entries) repeated runs cause some
* pretty aggressive optimisation to kick in. This tends to favour this implementation which has
* much tighter loops that the java7 or ICU implementations, but this may not be realised with
* real workloads.
*
* @author Steve Ratcliffe
*/
public class Sort {
private static final byte[] ZERO_KEY = new byte[4];
private static final Integer NO_ORDER = 0;
private int codepage;
private int id1; // Unknown - identifies the sort
private int id2; // Unknown - identifies the sort
private String description;
private Charset charset;
private final Page[] pages = new Page[256];
private final List expansions = new ArrayList<>();
private int maxExpSize = 1;
private CharsetEncoder encoder;
private boolean multi;
private int maxPage;
public Sort() {
pages[0] = new Page();
}
public void add(int ch, int primary, int secondary, int tertiary, int flags) {
ensurePage(ch >>> 8);
if (getPrimary(ch) != 0)
throw new ExitException(String.format("Repeated primary index 0x%x", ch & 0xff));
setPrimary (ch, primary);
setSecondary(ch, secondary);
setTertiary( ch, tertiary);
setFlags(ch, flags);
}
/**
* Run after all sorting order points have been added.
*
* Make sure that all tertiary values of secondary ignorable are greater
* than any normal tertiary value.
*
* And the same for secondaries on primary ignorable.
*/
public void finish() {
int maxSecondary = 0;
int maxTertiary = 0;
for (Page p : pages) {
if (p == null)
continue;
for (int i = 0; i < 256; i++) {
if (((p.flags[i] >>> 4) & 0x3) == 0) {
if (p.getPrimary(i) != 0) {
byte second = p.getSecondary(i);
maxSecondary = Math.max(maxSecondary, second);
if (second != 0) {
maxTertiary = Math.max(maxTertiary, p.getTertiary(i));
}
}
}
}
}
for (Page p : pages) {
if (p == null)
continue;
for (int i = 0; i < 256; i++) {
if (((p.flags[i] >>> 4) & 0x3) != 0) continue;
if (p.getPrimary(i) == 0) {
if (p.getSecondary(i) == 0) {
if (p.getTertiary(i) != 0) {
p.setTertiary(i, p.getTertiary(i) + maxTertiary);
}
} else {
p.setSecondary(i, p.getSecondary(i) + maxSecondary);
}
}
}
}
}
/**
* Return a table indexed by a character value in the target codepage, that gives the complete sort
* position of the character.
*
* This is only used for testing.
*
* @return A table of sort positions.
*/
public char[] getSortPositions() {
char[] tab = new char[256];
for (int i = 1; i < 256; i++) {
tab[i] = (char) (((getPrimary(i) << 8) & 0xff00) | ((getSecondary(i) << 4) & 0xf0) | (getTertiary(i) & 0xf));
}
return tab;
}
/**
* Create a sort key for a given unicode string. The sort key can be compared instead of the original strings
* and will compare based on the sorting represented by this Sort class.
*
* Using a sort key is more efficient if many comparisons are being done (for example if you are sorting a
* list of strings).
*
* @param object This is saved in the sort key for later retrieval and plays no part in the sorting.
* @param s The string for which the sort key is to be created.
* @param second Secondary sort key.
* @param cache A cache for the created keys. This is for saving memory so it is essential that this
* is managed by the caller.
* @return A sort key.
*/
public SortKey createSortKey(T object, String s, int second, Map cache) {
// If there is a cache then look up and return the key.
// This is primarily for memory management, not for speed.
byte[] key;
if (cache != null) {
key = cache.get(s);
if (key != null)
return new SrtSortKey<>(object, key, second);
}
try {
char[] chars;
if (isMulti()) {
chars = s.toCharArray();
} else {
ByteBuffer out = encoder.encode(CharBuffer.wrap(s));
byte[] bval = out.array();
chars = new char[bval.length];
for (int i = 0; i < bval.length; i++)
chars[i] = (char) (bval[i] & 0xff);
}
// In theory you could have a string where every character expands into maxExpSize separate characters
// in the key. However if we allocate enough space to deal with the worst case, then we waste a
// vast amount of memory. So allocate a minimal amount of space, try it and if it fails reallocate the
// maximum amount.
//
// We need +1 for the null bytes, we also +2 for a couple of expanded characters. For a complete
// german map this was always enough in tests.
key = new byte[(chars.length + 1 + 2) * 4];
try {
fillCompleteKey(chars, key);
} catch (ArrayIndexOutOfBoundsException e) {
// Ok try again with the max possible key size allocated.
key = new byte[(chars.length+1) * 4 * maxExpSize];
fillCompleteKey(chars, key);
}
if (cache != null)
cache.put(s, key);
return new SrtSortKey<>(object, key, second);
} catch (CharacterCodingException e) {
return new SrtSortKey<>(object, ZERO_KEY);
}
}
/**
* Create a sort key based on a Label.
*
* The label will contain the actual characters (after transliteration for example)
* @param object This is saved in the sort key for later retrieval and plays no part in the sorting.
* @param label The label, the actual written bytes/chars will be used as input to the sort.
* @param second Secondary sort key.
* @param cache A cache for the created keys. This is for saving memory so it is essential that this
* is managed by the caller.
* @return A sort key.
*/
public SortKey createSortKey(T object, Label label, int second, Map