/*
* 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.mdr;
import java.util.Arrays;
import uk.me.parabola.imgfmt.app.BufferedImgFileReader;
import uk.me.parabola.imgfmt.app.FileBackedImgFileWriter;
import uk.me.parabola.imgfmt.app.ImgFile;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.Country;
import uk.me.parabola.imgfmt.app.lbl.Region;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.app.mdr.MdrSection.PointerSizes;
import uk.me.parabola.imgfmt.app.net.RoadDef;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.Point;
import uk.me.parabola.imgfmt.fs.ImgChannel;
/**
* The MDR file. This is embedded into a .img file, either its own
* separate one, or as one file in the gmapsupp.img.
*
* @author Steve Ratcliffe
*/
public class MDRFile extends ImgFile {
private final MDRHeader mdrHeader;
// The sections
private final Mdr1 mdr1;
private final Mdr4 mdr4;
private final Mdr5 mdr5;
private final Mdr6 mdr6;
private final Mdr7 mdr7;
private final Mdr8 mdr8;
private final Mdr9 mdr9;
private final Mdr10 mdr10;
private final Mdr11 mdr11;
private final Mdr12 mdr12;
private final Mdr13 mdr13;
private final Mdr14 mdr14;
private final Mdr15 mdr15;
private final Mdr17 mdr17;
private final Mdr18 mdr18;
private final Mdr19 mdr19;
private final Mdr20 mdr20;
private final Mdr21 mdr21;
private final Mdr22 mdr22;
private final Mdr23 mdr23;
private final Mdr24 mdr24;
private final Mdr25 mdr25;
private final Mdr26 mdr26;
private final Mdr27 mdr27;
private final Mdr28 mdr28;
private final Mdr29 mdr29;
private int currentMap;
private final boolean forDevice;
private final MdrSection[] sections;
private PointerSizes sizes;
public MDRFile(ImgChannel chan, MdrConfig config) {
Sort sort = config.getSort();
forDevice = config.isForDevice();
mdrHeader = new MDRHeader(config.getHeaderLen());
mdrHeader.setSort(sort);
setHeader(mdrHeader);
if (config.isWritable()) {
ImgFileWriter fileWriter = new FileBackedImgFileWriter(chan, config.getOutputDir());
setWriter(fileWriter);
// Position at the start of the writable area.
position(mdrHeader.getHeaderLength());
} else {
setReader(new BufferedImgFileReader(chan));
mdrHeader.readHeader(getReader());
}
// Initialise the sections
mdr1 = new Mdr1(config);
mdr4 = new Mdr4(config);
mdr5 = new Mdr5(config);
mdr6 = new Mdr6(config);
mdr7 = new Mdr7(config);
mdr8 = new Mdr8(config);
mdr9 = new Mdr9(config);
mdr10 = new Mdr10(config);
mdr11 = new Mdr11(config);
mdr12 = new Mdr12(config);
mdr13 = new Mdr13(config);
mdr14 = new Mdr14(config);
mdr15 = new Mdr15(config);
mdr17 = new Mdr17(config);
mdr18 = new Mdr18(config);
mdr19 = new Mdr19(config);
mdr20 = new Mdr20(config);
mdr21 = new Mdr21(config);
mdr22 = new Mdr22(config);
mdr23 = new Mdr23(config);
mdr24 = new Mdr24(config);
mdr25 = new Mdr25(config);
mdr26 = new Mdr26(config);
mdr27 = new Mdr27(config);
mdr28 = new Mdr28(config);
mdr29 = new Mdr29(config);
this.sections = new MdrSection[]{
null,
mdr1, null, null, mdr4, mdr5, mdr6,
mdr7, mdr8, mdr9, mdr10, mdr11, mdr12,
mdr13, mdr14, mdr15, null, mdr17, mdr18, mdr19,
mdr20, mdr21, mdr22, mdr23, mdr24, mdr25,
mdr26, mdr27, mdr28, mdr29,
};
mdr11.setMdr10(mdr10);
}
/**
* Add a map to the index. You must add the map, then all of the items
* that belong to it, before adding the next map.
* @param mapName The numeric name of the map.
* @param codePage The code page of the map.
*/
public void addMap(int mapName, int codePage) {
currentMap++;
mdr1.addMap(mapName, currentMap);
Sort sort = mdrHeader.getSort();
if (sort.getCodepage() != codePage)
System.err.println("WARNING: input files have different code pages");
}
public Mdr14Record addCountry(Country country) {
Mdr14Record record = new Mdr14Record();
String name = country.getLabel().getText();
record.setMapIndex(currentMap);
record.setCountryIndex(country.getIndex());
record.setLblOffset(country.getLabel().getOffset());
record.setName(name);
record.setStrOff(createString(name));
mdr14.addCountry(record);
return record;
}
public Mdr13Record addRegion(Region region, Mdr14Record country) {
Mdr13Record record = new Mdr13Record();
String name = region.getLabel().getText();
record.setMapIndex(currentMap);
record.setLblOffset(region.getLabel().getOffset());
record.setCountryIndex(region.getCountry().getIndex());
record.setRegionIndex(region.getIndex());
record.setName(name);
record.setStrOffset(createString(name));
record.setMdr14(country);
mdr13.addRegion(record);
return record;
}
public void addCity(Mdr5Record city) {
int labelOffset = city.getLblOffset();
if (labelOffset != 0) {
String name = city.getName();
assert name != null : "off=" + labelOffset;
city.setMapIndex(currentMap);
city.setStringOffset(createString(name));
mdr5.addCity(city);
}
}
public void addZip(Zip zip) {
int strOff = createString(zip.getLabel().getText());
mdr6.addZip(currentMap, zip, strOff);
}
public void addPoint(Point point, Mdr5Record city, boolean isCity) {
assert currentMap > 0;
int fullType = point.getType();
if (!MdrUtils.canBeIndexed(fullType))
return;
Label label = point.getLabel();
String name = label.getText();
int strOff = createString(name);
Mdr11Record poi = mdr11.addPoi(currentMap, point, name, strOff);
poi.setCity(city);
poi.setIsCity(isCity);
poi.setType(fullType);
mdr4.addType(point.getType());
}
public void addStreet(RoadDef street, Mdr5Record mdrCity) {
// Add a separate record for each name
for (Label lab : street.getLabels()) {
if (lab == null)
break;
if (lab.getOffset() == 0)
continue;
String name = lab.getText();
String cleanName = cleanUpName(name);
int strOff = createString(cleanName);
// We sort on the dirty name (ie with the Garmin shield codes) although those codes do not
// affect the sort order. The string for mdr15 does not include the shield codes.
mdr7.addStreet(currentMap, name, lab.getOffset(), strOff, mdrCity);
}
}
/**
* Remove shields and other kinds of strange characters. Perform any
* rearrangement of the name to make it searchable.
* @param name The street name as read from the img file.
* @return The name as it will go into the index.
*/
private String cleanUpName(String name) {
return Label.stripGarminCodes(name);
}
public void write() {
mdr15.release();
ImgFileWriter writer = getWriter();
writeSections(writer);
// Now refresh the header
position(0);
getHeader().writeHeader(writer);
}
/**
* Write all the sections out.
*
* The order of all the operations in this method is important. The order
* of the sections in the actual output file doesn't matter at all, so
* they can be re-ordered to suit.
*
* Most of the complexity here is arranging the order of things so that the smallest
* amount of temporary memory is required.
*
* @param writer File is written here.
*/
private void writeSections(ImgFileWriter writer) {
sizes = new MdrMapSection.PointerSizes(sections);
// Deal with the dependencies between the sections. The order of the following
// statements is sometimes important.
mdr28.buildFromRegions(mdr13.getRegions());
mdr23.sortRegions(mdr13.getRegions());
mdr29.buildFromCountries(mdr14.getCountries());
mdr24.sortCountries(mdr14.getCountries());
mdr26.sortMdr28(mdr28.getIndex());
writeSection(writer, 4, mdr4);
mdr1.preWrite();
mdr5.preWrite();
mdr20.preWrite();
// We write the following sections that contain per-map data, in the
// order of the subsections of the reverse index that they are associated
// with.
writeSection(writer, 11, mdr11);
mdr10.setNumberOfPois(mdr11.getNumberOfPois());
mdr12.setIndex(mdr11.getIndex());
mdr19.setPois(mdr11.getPois());
mdr17.addPois(mdr11.getPois());
mdr11.release();
if (forDevice) {
mdr19.preWrite();
writeSection(writer, 19, mdr19);
mdr18.setPoiTypes(mdr19.getPoiTypes());
mdr19.release();
writeSection(writer, 18, mdr18);
}
writeSection(writer, 10, mdr10);
mdr9.setGroups(mdr10.getGroupSizes());
mdr10.release();
// mdr7 depends on the size of mdr20, so mdr20 must be built first
mdr7.preWrite();
mdr20.buildFromStreets(mdr7.getStreets());
writeSection(writer, 7, mdr7);
writeSection(writer, 5, mdr5);
mdr25.sortCities(mdr5.getCities());
mdr27.sortCities(mdr5.getCities());
mdr17.addCities(mdr5.getSortedCities());
mdr5.release();
writeSection(writer, 6, mdr6);
writeSection(writer, 20, mdr20);
mdr20.release();
mdr21.buildFromStreets(mdr7.getStreets());
writeSection(writer, 21, mdr21);
mdr21.release();
mdr22.buildFromStreets(mdr7.getStreets());
mdr8.setIndex(mdr7.getIndex());
mdr17.addStreets(mdr7.getSortedStreets());
mdr7.release();
writeSection(writer, 22, mdr22);
mdr17.addStreetsByCountry(mdr22.getStreets());
mdr22.release();
if (forDevice) {
writeSection(writer, 17, mdr17);
mdr17.release();
}
// The following do not have mdr1 subsections
//writeSection(writer, 8, mdr8);
writeSection(writer, 9, mdr9);
writeSection(writer, 12, mdr12);
writeSection(writer, 13, mdr13);
writeSection(writer, 14, mdr14);
writeSection(writer, 15, mdr15);
writeSection(writer, 23, mdr23);
writeSection(writer, 24, mdr24);
writeSection(writer, 25, mdr25);
mdr28.preWrite(); // TODO reorder writes below so this is not needed. Changes the output file though
writeSection(writer, 26, mdr26);
writeSection(writer, 27, mdr27);
writeSection(writer, 28, mdr28);
writeSection(writer, 29, mdr29);
// write the reverse index last.
mdr1.writeSubSections(writer);
mdrHeader.setPosition(1, writer.position());
mdr1.writeSectData(writer);
mdrHeader.setItemSize(1, mdr1.getItemSize());
mdrHeader.setEnd(1, writer.position());
mdrHeader.setExtraValue(1, mdr1.getExtraValue());
}
/**
* Write out the given single section.
*/
private void writeSection(ImgFileWriter writer, int sectionNumber, MdrSection section) {
// Some sections are just not written in the device config
if (forDevice && Arrays.asList(13, 14, 15, 21, 23, 26, 27, 28).contains(sectionNumber))
return;
section.setSizes(sizes);
mdrHeader.setPosition(sectionNumber, writer.position());
mdr1.setStartPosition(sectionNumber);
section.preWrite();
if (!forDevice && section instanceof MdrMapSection) {
MdrMapSection mapSection = (MdrMapSection) section;
mapSection.setMapIndex(mdr1);
mapSection.initIndex(sectionNumber);
}
if (section instanceof HasHeaderFlags)
mdrHeader.setExtraValue(sectionNumber, ((HasHeaderFlags) section).getExtraValue());
section.writeSectData(writer);
int itemSize = section.getItemSize();
if (itemSize > 0)
mdrHeader.setItemSize(sectionNumber, itemSize);
mdrHeader.setEnd(sectionNumber, writer.position());
mdr1.setEndPosition(sectionNumber);
}
/**
* Creates a string in MDR 15 and returns an offset value that can be
* used to refer to it in the other sections.
* @param str The text of the string.
* @return An offset value.
*/
private int createString(String str) {
return mdr15.createString(str);
}
}