Subversion Repositories mkgmap

Rev

Rev 4533 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;
        }
}