Subversion Repositories mkgmap

Rev

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

/*
 * 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: 16-Dec-2006
 */

package uk.me.parabola.mkgmap.main;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import uk.me.parabola.imgfmt.FileExistsException;
import uk.me.parabola.imgfmt.FileNotWritableException;
import uk.me.parabola.imgfmt.FileSystemParam;
import uk.me.parabola.imgfmt.FormatException;
import uk.me.parabola.imgfmt.MapFailedException;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.map.Map;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.srt.SortKey;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.build.MapBuilder;
import uk.me.parabola.mkgmap.combiners.OverviewBuilder;
import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapPoint;
import uk.me.parabola.mkgmap.general.MapRoad;
import uk.me.parabola.mkgmap.reader.plugin.MapReader;

/**
 * Main routine for the command line map-making utility.
 *
 * @author Steve Ratcliffe
 */

public class MapMaker implements MapProcessor {
        private static final Logger log = Logger.getLogger(MapMaker.class);
        private Sort sort;
        private final boolean createOverviewFiles;

        public MapMaker(boolean createOverviewFiles) {
                this.createOverviewFiles = createOverviewFiles;
        }

        public String makeMap(CommandArgs args, String filename) {
                try {
                        LoadableMapDataSource src = loadFromFile(args, filename);
                        sort = args.getSort();
                        log.info("Making Road Name POIs for", filename);
                        makeRoadNamePOIS(args, src);
                        if (createOverviewFiles){
                                if (src.overviewMapLevels() != null){
                                        makeMap(args, src, OverviewBuilder.OVERVIEW_PREFIX);
                                } else {
                                        String fname = OverviewBuilder.getOverviewImgName(args.getMapname());
                                        File f = new File(fname);
                                        if (f.exists()) {
                                                if (f.isFile() )
                                                        f.delete();
                                                else {
                                                        // TODO: error message ?
                                                }
                                        }
                                }
                        }
                        return makeMap(args, src, "");
                } catch (FormatException e) {
                        System.err.println("Bad file format: " + filename);
                        System.err.println(e.getMessage());
                        return filename;
                } catch (FileNotFoundException e) {
                        System.err.println("Could not open file: " + filename);
                        return filename;
                }
        }

        /**
         * Make a map from the given map data source.
         *
         * @param args User supplied arguments.
         * @param src The data source to load.
         * @param mapNameExt
         * @return The output filename for the map.
         */

        private String makeMap(CommandArgs args, LoadableMapDataSource src, String mapNamePrefix) {

                if (src.getBounds().isEmpty())
                        return null;

                FileSystemParam params = new FileSystemParam();
                params.setBlockSize(args.getBlockSize());
                params.setMapDescription(args.getDescription());
                log.info("Started making", args.getMapname(), "(" + args.getDescription() + ")");
                try {
                        Map map = Map.createMap(mapNamePrefix + args.getMapname(), args.getOutputDir(), params, args.getMapname(), sort);
                        setOptions(map, args);

                        MapBuilder builder = new MapBuilder();
                        builder.config(args.getProperties());
                        if (args.getProperties().getProperty("route", false)){
                                if(! OverviewBuilder.OVERVIEW_PREFIX.equals(mapNamePrefix))
                                        builder.setDoRoads(true);
                        }
                        builder.makeMap(map, src);

                        // Collect information on map complete.
                        String outName = map.getFilename();
                        log.info("finished making map", outName, "closing");
                        map.close();
                        return outName;
                } catch (FileExistsException e) {
                        throw new MapFailedException("File exists already", e);
                } catch (FileNotWritableException e) {
                        throw new MapFailedException("Could not create or write to file", e);
                }
        }

        /**
         * Set options from the command line.
         *
         * @param map The map to modify.
         * @param args The command line arguments.
         */

        private void setOptions(Map map, CommandArgs args) {
                map.config(args.getProperties());

                String s = args.getCharset();
                if (s != null)
                        map.setLabelCharset(s, args.isForceUpper());

                Sort sort = args.getSort();
                map.setSort(sort);
        }

        /**
         * Load up from the file.  It is not necessary for the map reader to completely
         * read the whole file in at once, it could pull in map-features as needed.
         *
         * @param args The user supplied parameters.
         * @param name The filename or resource name to be read.
         * @return A LoadableMapDataSource that will be used to construct the map.
         * @throws FileNotFoundException For non existing files.
         * @throws FormatException When the file format is not valid.
         */

        private LoadableMapDataSource loadFromFile(CommandArgs args, String name) throws
                        FileNotFoundException, FormatException
        {
                LoadableMapDataSource src = MapReader.createMapReader(name);
                src.config(args.getProperties());
                log.info("Started loading", name);
                src.load(name);
                log.info("Finished loading", name);
                return src;
        }

        void makeRoadNamePOIS(CommandArgs args, LoadableMapDataSource src) {
                String rnp = args.get("road-name-pois", null);
                // are road name POIS wanted?
                if(rnp != null) {
                        rnp = rnp.toUpperCase();
                        int rnpt = 0x640a; // Garmin type 'Locale'
                        if(rnp.length() > 0) {
                                // override type code
                                rnpt = Integer.decode(rnp);
                        }
                        // collect lists of roads that have the same name
                        java.util.Map<String, List<MapRoad>> namedRoads = new HashMap<String, List<MapRoad>>();
                        for(MapLine l : src.getLines()) {
                                if(l.isRoad()) {
                                        MapRoad r = (MapRoad)l;
                                        String rn = r.getName();
                                        if(rn != null) {
                                                List<MapRoad> rl = namedRoads.get(rn);
                                                if(rl == null) {
                                                        rl = new ArrayList<MapRoad>();
                                                        namedRoads.put(rn, rl);
                                                }
                                                rl.add(r);
                                        }
                                }
                        }

                        // generate a POI for each named road

                        // sort by name and coordinate of first point so that
                        // the order is always the same for the same input
                        List<SortKey<MapRoad>> rnpRoads = new ArrayList<SortKey<MapRoad>>();
                        for(List<MapRoad> lr : findConnectedRoadsWithSameName(namedRoads)) {
                                // connected roads are not ordered so just use first in list
                                MapRoad r = lr.get(0);
                                String key = r.getName();
                                List<Coord> points = r.getPoints();
                                if(!points.isEmpty())
                                        key += "_" + points.get(0);
                                rnpRoads.add(sort.createSortKey(r, key));
                        }
                        Collections.sort(rnpRoads);
                        for(SortKey<MapRoad> sr : rnpRoads)
                                src.getPoints().add(makeRoadNamePOI(sr.getObject(), rnpt));
                }
        }

        private boolean roadsAreJoined(MapLine r1, MapLine r2) {
                if (r1.getBounds().intersects(r2.getBounds()) == false)  {
                        return false;
                }

                if(r1 != r2) {
                        for(Coord c1 : r1.getPoints()) {
                                for(Coord c2 : r2.getPoints()) {
                                        if(c1 == c2 || c1.highPrecEquals(c2))
                                                return true;
                                }
                        }
                }
                return false;
        }

        // hairy function to build a set of lists - each list contains
        // the roads that have the same name and are connected

        private Set<List<MapRoad>> findConnectedRoadsWithSameName(java.util.Map<String, List<MapRoad>> namedRoads) {
                // roadGroups is a set to avoid duplicate groups
                Set<List<MapRoad>> roadGroups = new HashSet<List<MapRoad>>();

                // loop over the lists of roads that have the same name
                for(List<MapRoad> allRoadsWithSameName : namedRoads.values()) {
                        // for each road that has the same name, keep track of its group
                        java.util.Map<MapRoad,List<MapRoad>> roadGroupMap = new HashMap<MapRoad,List<MapRoad>>();

                        // loop over all of the roads with the same name
                        for(int i = 0; i < allRoadsWithSameName.size(); ++i) {
                                boolean roadWasJoined = false;
                                for(int j = 0; j < allRoadsWithSameName.size(); ++j) {
                                        if(i != j) {
                                                // see if these two roads are joined
                                                MapRoad ri = allRoadsWithSameName.get(i);
                                                MapRoad rj = allRoadsWithSameName.get(j);
                                                if(roadsAreJoined(ri, rj)) {
                                                        // yes, they are joined so put both in a group
                                                        // and associate the group with each road
                                                        roadWasJoined = true;
                                                        List<MapRoad> groupi = roadGroupMap.get(ri);
                                                        List<MapRoad> groupj = roadGroupMap.get(rj);
                                                        if(groupi == null) {
                                                                // ri is not in a group yet
                                                                if(groupj == null) {
                                                                        // neither is rj so make a new group
                                                                        groupi = new ArrayList<MapRoad>();
                                                                        groupi.add(ri);
                                                                        groupi.add(rj);
                                                                        roadGroupMap.put(ri, groupi);
                                                                        roadGroupMap.put(rj, groupi);
                                                                }
                                                                else {
                                                                        // add ri to groupj
                                                                        groupj.add(ri);
                                                                        roadGroupMap.put(ri, groupj);
                                                                }
                                                        }
                                                        else if(groupj == null) {
                                                                // add rj to groupi
                                                                groupi.add(rj);
                                                                roadGroupMap.put(rj, groupi);
                                                        }
                                                        else if(groupi != groupj) {
                                                                // ri and rj are in separate groups so put
                                                                // all the roads in groupj into groupi
                                                                for(MapRoad r : groupj)
                                                                        roadGroupMap.put(r, groupi);
                                                                groupi.addAll(groupj);
                                                        }
                                                }
                                        }
                                }
                                if(!roadWasJoined) {
                                        // make a group with just one entry
                                        MapRoad ri = allRoadsWithSameName.get(i);
                                        List<MapRoad>group = new ArrayList<MapRoad>();
                                        group.add(ri);
                                        roadGroupMap.put(ri, group);
                                }
                        }

                        // now add the new group(s) to the final result
                        for(List<MapRoad> r : roadGroupMap.values())
                                roadGroups.add(r);
                }
                return roadGroups;
        }

        private MapPoint makeRoadNamePOI(MapRoad road, int type) {
                List<Coord> points = road.getPoints();
                int numPoints = points.size();
                Coord coord;
                // XXX Why not always use an existing point close to
                // numpoints/2 ?
                if ((numPoints & 1) == 0) {
                        int i2 = numPoints / 2;
                        int i1 = i2 - 1;
                        coord = points.get(i1).makeBetweenPoint(points.get(i2), 0.5);
                       
                } else {
                        coord = points.get(numPoints / 2);
                }

                String name = road.getName();
                MapPoint rnp = new MapPoint();

                rnp.setName(name);
                rnp.setRoadNamePOI(true);
                rnp.setType(type);
                rnp.setLocation(coord);
                return rnp;
        }
}