/*
* 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.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
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.ImgFS;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.general.MapPoint;
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
;
// The mdr.img file
private FileSystem imgfs
;
// 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");
ImgChannel mdrChan
;
try {
// Create the .img file system/archive
FileSystemParam params =
new FileSystemParam
();
tmpName =
File.
createTempFile("mdr",
null,
new File(outputDir
));
tmpName.
deleteOnExit();
imgfs = ImgFS.
createFs(tmpName.
getPath(), params
);
// Create the MDR file within the .img
mdrChan = imgfs.
create(name.
toUpperCase(Locale.
ENGLISH) +
".MDR");
} 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.
setIndexOptions(args
);
// Wrap the MDR channel with the MDRFile object
mdrFile =
new MDRFile
(mdrChan, config
);
try {
ImgChannel srtChan = imgfs.
create(name.
toUpperCase(Locale.
ENGLISH) +
".SRT");
SRTFile srtFile =
new SRTFile
(srtChan
);
srtFile.
setSort(sort
);
srtFile.
write();
// Do not close srtFile here
} catch (FileExistsException e
) {
throw new ExitException
("Could not create SRT file within index file");
}
}
/**
* Create an mdr file, in the format used in a gmapsupp.
*
* @param chan Reference to an open file within the gmapsupp file.
*/
void initForDevice
(ImgChannel chan, Sort sort, MdrConfig baseConfig
) {
// Set the options that we are using for the mdr.
MdrConfig config =
new MdrConfig
(baseConfig
);
config.
setHeaderLen(568);
config.
setWritable(true);
config.
setForDevice(true);
config.
setSort(sort
);
// Wrap the MDR channel with the MDRFile object
mdrFile =
new MDRFile
(chan, config
);
}
/**
* 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();
try {
MapReader mr = info.
getMapReader();
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");
}
}
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(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(region.
getCountry().
getIndex());
Mdr13Record record = mdrFile.
addRegion(region, mdr14
);
regionMap.
put(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 (MapPoint.
isCityType(p.
getType())) {
// 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
) {
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);
}
}
/**
* Called after all maps are processed.
*
* We are building a standalone mdr file, so that is
*/
public void onFinish
() {
// Write out the mdr file
mdrFile.
write();
// Close the mdr.img file, thus causing it to be written out fully.
imgfs.
close();
// 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.
try {
Files.
move(tmpName.
toPath(), Paths.
get(outputName
), StandardCopyOption.
REPLACE_EXISTING);
} catch (IOException e
) {
throw new MapFailedException
("Could not create mdr.img file");
}
}
/**
* Called after all maps processed when making a gmapsupp.img
*
* Here by 'for device' we mean in the format required for uploading to a device, so that is
* the gmapsupp.img file.
*/
void onFinishForDevice
() {
// Write out the mdr file
mdrFile.
write();
}
@
Override
public String getFilename
() {
return outputName
;
}
public int getSize
() {
return (int) tmpName.
length();
}
/**
* 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
;
}
}