/*
* 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.mkgmap.combiners;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.FileSystemParam;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.lbl.Country;
import uk.me.parabola.imgfmt.app.lbl.POIRecord;
import uk.me.parabola.imgfmt.app.lbl.Region;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.app.map.MapReader;
import uk.me.parabola.imgfmt.app.mdr.MDRFile;
import uk.me.parabola.imgfmt.app.mdr.Mdr13Record;
import uk.me.parabola.imgfmt.app.mdr.Mdr14Record;
import uk.me.parabola.imgfmt.app.mdr.Mdr5Record;
import uk.me.parabola.imgfmt.app.mdr.MdrConfig;
import uk.me.parabola.imgfmt.app.net.RoadDef;
import uk.me.parabola.imgfmt.app.srt.SRTFile;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.Point;
import uk.me.parabola.imgfmt.fs.FileSystem;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.imgfmt.sys.FileImgChannel;
import uk.me.parabola.imgfmt.sys.ImgFS;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.srt.SrtTextReader;
/**
* Create the global index file. This consists of an img file containing
* an MDR file and optionally an SRT file.
*
* @author Steve Ratcliffe
*/
public class MdrBuilder implements Combiner {
private MDRFile mdrFile;
// Push things onto this stack to have them closed in the reverse order.
private final Deque<Closeable> toClose = new ArrayDeque<>();
// We write to a temporary file name, and then rename once all is OK.
private File tmpName;
private String outputName;
/**
* Create the mdr file and initialise.
* It has a name that is based on the overview-mapname option, as does
* the associated MDX file.
*
* @param args The command line arguments.
*/
public void init(CommandArgs args) {
String name = args.get("overview-mapname", "osmmap");
String outputDir = args.getOutputDir();
outputName = Utils.joinPath(outputDir, name + "_mdr.img");
FileSystem fs;
ImgChannel mdrChan;
try {
// Create the .img file system/archive
FileSystemParam params = new FileSystemParam();
params.setBlockSize(args.get("block-size", 16384));
tmpName = File.createTempFile("mdr", null, new File(outputDir));
tmpName.deleteOnExit();
fs = ImgFS.createFs(tmpName.getPath(), params);
toClose.push(fs);
// Create the MDR file within the .img
mdrChan = fs.create(name.toUpperCase(Locale.ENGLISH) + ".MDR");
toClose.push(mdrChan);
} catch (IOException e) {
throw new ExitException("Could not create global index file");
}
// Create the sort description
Sort sort = SrtTextReader.sortForCodepage(args.getCodePage());
// Set the options that we are using for the mdr.
MdrConfig config = new MdrConfig();
config.setHeaderLen(568);
config.setWritable(true);
config.setForDevice(false);
config.setOutputDir(outputDir);
config.setSort(sort);
config.setSplitName(args.get("split-name-index", false));
// Wrap the MDR channel with the MDRFile object
mdrFile = new MDRFile(mdrChan, config);
toClose.push(mdrFile);
try {
ImgChannel srtChan = fs.create(name.toUpperCase(Locale.ENGLISH) + ".SRT");
SRTFile srtFile = new SRTFile(srtChan);
srtFile.setSort(sort);
srtFile.write();
srtFile.close();
//toClose.push(srtFile);
} catch (FileExistsException e) {
throw new ExitException("Could not create SRT file within index file");
}
}
void initForDevice(Sort sort, String outputDir, boolean splitName) {
// Set the options that we are using for the mdr.
MdrConfig config = new MdrConfig();
config.setHeaderLen(568);
config.setWritable(true);
config.setForDevice(true);
config.setSort(sort);
config.setSplitName(splitName);
// Wrap the MDR channel with the MDRFile object
try {
tmpName = File.createTempFile("mdr", null, new File(outputDir));
tmpName.deleteOnExit();
ImgChannel channel = new FileImgChannel(tmpName.getPath(), "rw");
mdrFile = new MDRFile(channel, config);
toClose.push(mdrFile);
} catch (IOException e) {
throw new ExitException("Could not create temporary index file");
}
}
/**
* Adds a new map to the file. We need to read in the img file and
* extract all the information that can be indexed from it.
*
* @param info An interface to read the map.
*/
public void onMapEnd(FileInfo info) {
if (!info.isImg())
return;
// Add the map name
mdrFile.addMap(info.getHexname(), info.getCodePage());
String filename = info.getFilename();
MapReader mr = null;
try {
mr = new MapReader(filename);
AreaMaps maps = new AreaMaps();
maps.countries = addCountries(mr);
maps.regions = addRegions(mr, maps);
List<Mdr5Record> mdrCityList = fetchCities(mr, maps);
maps.cityList = mdrCityList;
addPoints(mr, maps);
addCities(mdrCityList);
addStreets(mr, mdrCityList);
addZips(mr);
} catch (FileNotFoundException e) {
throw new ExitException("Could not open " + filename + " when creating mdr file");
} finally {
Utils.closeFile(mr);
}
}
private Map<Integer, Mdr14Record> addCountries(MapReader mr) {
Map<Integer, Mdr14Record> countryMap = new HashMap<>();
List<Country> countries = mr.getCountries();
for (Country c : countries) {
if (c != null) {
Mdr14Record record = mdrFile.addCountry(c);
countryMap.put((int) c.getIndex(), record);
}
}
return countryMap;
}
private Map<Integer, Mdr13Record> addRegions(MapReader mr, AreaMaps maps) {
Map<Integer, Mdr13Record> regionMap = new HashMap<>();
List<Region> regions = mr.getRegions();
for (Region region : regions) {
if (region != null) {
Mdr14Record mdr14 = maps.countries.get((int) region.getCountry().getIndex());
Mdr13Record record = mdrFile.addRegion(region, mdr14);
regionMap.put((int) region.getIndex(), record);
}
}
return regionMap;
}
/**
* There is not complete information that we need about a city in the city
* section, it has to be completed from the points section. So we fetch
* and create the mdr5s first before points.
*/
private List<Mdr5Record> fetchCities(MapReader mr, AreaMaps maps) {
Map<Integer, Mdr5Record> cityMap = maps.cities;
List<Mdr5Record> cityList = new ArrayList<>();
List<City> cities = mr.getCities();
for (City c : cities) {
int regionCountryNumber = c.getRegionCountryNumber();
Mdr13Record mdrRegion = null;
Mdr14Record mdrCountry;
if ((regionCountryNumber & 0x4000) == 0) {
mdrRegion = maps.regions.get(regionCountryNumber);
mdrCountry = mdrRegion.getMdr14();
} else {
mdrCountry = maps.countries.get(regionCountryNumber & 0x3fff);
}
Mdr5Record mdrCity = new Mdr5Record();
mdrCity.setCityIndex(c.getIndex());
mdrCity.setRegionIndex(c.getRegionCountryNumber());
mdrCity.setMdrRegion(mdrRegion);
mdrCity.setMdrCountry(mdrCountry);
mdrCity.setLblOffset(c.getLblOffset());
mdrCity.setName(c.getName());
int key = (c.getSubdivNumber() << 8) + (c.getPointIndex() & 0xff);
assert key < 0xffffff;
cityMap.put(key, mdrCity);
cityList.add(mdrCity);
}
return cityList;
}
/**
* Now really add the cities.
* @param cityList The previously saved cities.
*/
private void addCities(List<Mdr5Record> cityList) {
for (Mdr5Record c : cityList) {
mdrFile.addCity(c);
}
}
private void addZips(MapReader mr) {
List<Zip> zips = mr.getZips();
for (Zip zip : zips)
mdrFile.addZip(zip);
}
/**
* Read points from this map and add them to the index.
* @param mr The currently open map.
* @param maps Maps of regions, cities countries etc.
*/
private void addPoints(MapReader mr, AreaMaps maps) {
List<Point> list = mr.pointsForLevel(0, MapReader.WITHOUT_EXT_TYPE_DATA);
for (Point p : list) {
Label label = p.getLabel();
if (p.getNumber() > 256) {
continue;
}
Mdr5Record mdrCity = null;
boolean isCity;
if (p.getType() >= 0x1 && p.getType() <= 0x11) {
// This is itself a city, it gets a reference to its own MDR 5 record.
// and we also use it to set the name of the city.
mdrCity = maps.cities.get((p.getSubdiv().getNumber() << 8) + p.getNumber());
if (mdrCity != null) {
mdrCity.setLblOffset(label.getOffset());
mdrCity.setName(label.getText());
}
isCity = true;
} else {
// This is not a city, but we have information about which city
// it is in. If so then add the mdr5 record number of the city.
POIRecord poi = p.getPOIRecord();
City c = poi.getCity();
if (c != null)
mdrCity = getMdr5FromCity(maps, c);
isCity = false;
}
if (label != null && !label.getText().trim().isEmpty())
mdrFile.addPoint(p, mdrCity, isCity);
}
}
private void addStreets(MapReader mr, List<Mdr5Record> cityList) {
List<RoadDef> roads = mr.getRoads();
for (RoadDef road : roads) {
String name = road.getName();
if (name == null || name.isEmpty())
continue;
List<City> cities = road.getCities();
if (cities.isEmpty())
mdrFile.addStreet(road, null);
else {
for (City city : cities){
Mdr5Record mdrCity = cityList.get(city.getIndex() - 1);
if (mdrCity.getMapIndex() == 0)
mdrCity = null;
mdrFile.addStreet(road, mdrCity);
}
}
}
}
private Mdr5Record getMdr5FromCity(AreaMaps cityMap, City c) {
if (c == null)
return null;
if (c.getPointIndex() > 0) {
return cityMap.cities.get((c.getSubdivNumber() << 8) + (c.getPointIndex() & 0xff));
} else {
return cityMap.cityList.get(c.getIndex() - 1);
}
}
public void onFinish() {
// Write out the mdr file
mdrFile.write();
// Close everything
for (Closeable file : toClose)
Utils.closeFile(file);
// Rename from the temporary file to the proper name. On windows the target file must
// not exist for rename to work, so we are forced to remove it first.
File outputName = new File(this.outputName);
outputName.delete();
boolean ok = tmpName.renameTo(outputName);
if (!ok)
throw new MapFailedException("Could not create mdr.img file");
}
void onFinishForDevice() {
// Write out the mdr file
mdrFile.write();
// Close everything
for (Closeable file : toClose)
Utils.closeFile(file);
}
public String getFilename() {
return outputName;
}
public int getSize() {
return (int) tmpName.length();
}
public String getFileName() {
return tmpName.getPath();
}
/**
* Holds lookup maps for cities, regions and countries. Used to
* link streets, pois to cities, regions and countries.
*
* These are only held for a single map at a time, which is
* sufficient to link them all up.
*/
private class AreaMaps {
private final Map<Integer, Mdr5Record> cities = new HashMap<>();
private Map<Integer, Mdr13Record> regions;
private Map<Integer, Mdr14Record> countries;
private List<Mdr5Record> cityList;
}
}