Subversion Repositories mkgmap

Rev

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

/*
 * Copyright (C) 2007 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: 22-Sep-2007
 */

package uk.me.parabola.mkgmap.reader.osm;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.CommandArgs;
import uk.me.parabola.mkgmap.general.LevelInfo;
import uk.me.parabola.mkgmap.general.LoadableMapDataSource;
import uk.me.parabola.mkgmap.osmstyle.StyleImpl;
import uk.me.parabola.mkgmap.osmstyle.StyledConverter;
import uk.me.parabola.mkgmap.reader.MapperBasedMapDataSource;
import uk.me.parabola.mkgmap.reader.osm.bin.OsmBinHandler;
import uk.me.parabola.mkgmap.reader.osm.o5m.O5mBinHandler;
import uk.me.parabola.mkgmap.reader.osm.xml.OsmXmlHandler;
import uk.me.parabola.util.EnhancedProperties;

/**
 * Base class for OSM map sources.  It exists so that more than
 * one version of the api can be supported at a time.
 *
 * @author Steve Ratcliffe
 */

public class OsmMapDataSource extends MapperBasedMapDataSource implements LoadableMapDataSource {
        private static final Logger log = Logger.getLogger(OsmMapDataSource.class);

        private Style style;
       
        // attention, the order of the hooks is important!
        private final OsmReadingHooks[] POSSIBLE_HOOKS = {
                        new SeaGenerator(),
                        new MultiPolygonFinishHook(),
                        new RelationStyleHook(),
                        new LinkDestinationHook(),
                        new UnusedElementsRemoverHook(),
                        new RoutingHook(),
                        new HighwayHooks(),
                        new LocationHook(),
                        new POIGeneratorHook(),
                        new ResidentialHook(),
                        new HousenumberHooks(),
        };
        protected OsmConverter converter;
        private final Set<String> usedTags = new HashSet<>();
        protected ElementSaver elementSaver;
        protected OsmReadingHooks osmReadingHooks;
       
        protected static final List<OsmHandler> handlers;
        static {
                handlers = new ArrayList<>();
                handlers.add(new OsmBinHandler());
                handlers.add(new O5mBinHandler());
                handlers.add(new OsmXmlHandler()); // must be last
        }


        /**
         * Get the maps levels to be used for the current map.  This can be
         * specified in a number of ways in order:
         * <ol>
         * <li>On the command line with the --levels flag.
         * The format is a comma (or space) separated list of level/resolution
         * pairs.  Eg --levels=0:24,1:22,2:20
         * If the flag is given without an argument then the command line override
         * is turned off for maps following that option.
         *
         * <li>In the style options file.  This works just like the command line
         * option, but it applies whenever the given style is used and not overridden
         * on the command line.
         *
         * <li>A default setting.
         * </ol>
         *
         * <p>I'd advise that new styles specify their own set of levels.
         *
         * @return An array of level information, basically a [level,resolution]
         * pair.
         */

        public LevelInfo[] mapLevels() {
                String levelSpec = getLevelSpec("levels");
                if (levelSpec == null)
                        levelSpec = LevelInfo.DEFAULT_LEVELS;

                return LevelInfo.createFromString(levelSpec);
        }

        @Override
        public LevelInfo[] overviewMapLevels() {
                String levelSpec = getLevelSpec("overview-levels");
               
                if (levelSpec == null)
                        return null;
                LevelInfo[] levels = LevelInfo.createFromString(levelSpec);
                for (int i = 0; i < levels.length; i++)
                        levels[i] = new LevelInfo(levels.length-i-1,levels[i].getBits());
                return levels;
        }
               
        private String getLevelSpec (String optionName){
                // First try command line, then style, then our default.
                String levelSpec = getConfig().getProperty(optionName);
                log.debug(optionName, levelSpec, ", ", ((levelSpec!=null)?levelSpec.length():""));
                if ((levelSpec == null || levelSpec.length() < 2) && style != null) {
                        levelSpec = style.getOption(optionName);
                        log.debug("getting " + optionName + " from style:", levelSpec);
                }
                return levelSpec;
        }
       
        @Override
        public void load(String name, boolean addBackground) throws FileNotFoundException {
                try (InputStream is = Utils.openFile(name)) {
                        parse(is, name);
                } catch (FileNotFoundException e) {
                        throw e;
                } catch (IOException e) {
                        // exception thrown from implicit call to close() on resource variable 'is'
                }
               
                elementSaver.finishLoading();

                osmReadingHooks.end();
                osmReadingHooks = null;
               
                // now convert the saved elements
                elementSaver.convert(getConverter());
               
                if (addBackground)
                        addBackground();
        }

        protected void parse(InputStream is, String name) {
                for (OsmHandler h : handlers) {
                        if (h.isFileSupported(name)) {
                                try {
                                        OsmHandler handler = h.getClass().getDeclaredConstructor().newInstance();
                                        setupHandler(handler);
                                        handler.parse(is);
                                        break;
                                } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
                                                | InvocationTargetException | NoSuchMethodException | SecurityException e) {
                                        // TODO Auto-generated catch block
                                        log.error("Unexpected error", e);
                                }
                        }
                }
               
        }

        /**
         * There are no copyright messages in the OSM files themselves.  So we
         * include a fixed set of strings on the assumption that .osm files
         * are probably going to have the OSM copyright statements.
         *
         * @return A list of copyright messages as a String array.
         */

        public String[] copyrightMessages() {
                String copyrightFileName = getConfig().getProperty("copyright-file", null);
                if (copyrightFileName != null) {
                        return readCopyrightFile(copyrightFileName);
                }
                String note = getConfig().getProperty("copyright-message",
                                "OpenStreetMap.org contributors. See: http://wiki.openstreetmap.org/index.php/Attribution");
                return new String[] { note };
        }

        protected void setStyle(Style style) {
                this.style = style;
        }

        /**
         * Common code to setup the file handler.
         * @param handler The file handler.
         */

        protected void setupHandler(OsmHandler handler) {
                createElementSaver();
                createConverter();
               
                handler.setIgnoreBounds(getConfig().getProperty("ignore-osm-bounds", false));
               
                osmReadingHooks = pluginChain(elementSaver, getConfig());

                handler.setElementSaver(elementSaver);
                handler.setHooks(osmReadingHooks);

                handler.setUsedTags(getUsedTags());

                String deleteTagsFileName = getConfig().getProperty("delete-tags-file");
                if(deleteTagsFileName != null) {
                        Map<String, Set<String>> deltags = readDeleteTagsFile(deleteTagsFileName);
                        handler.setTagsToDelete(deltags);
                }
                if (getConfig().getProperty("ignore-fixme-values", false)) {
                        handler.setDeleteFixmeValues(true);
                }
                if (getConfig().getProperty("keep-empty-value-tags", false)) {
                        handler.setDeleteEmptyValueTag(false);
                }
        }
       
        protected void createElementSaver() {
                elementSaver = new ElementSaver(getConfig());
        }
       
        public ElementSaver getElementSaver() {
                return elementSaver;
        }

        protected OsmReadingHooks[] getPossibleHooks() {
                return this.POSSIBLE_HOOKS;
        }
       
        protected OsmReadingHooks pluginChain(ElementSaver saver, EnhancedProperties props) {
                List<OsmReadingHooks> plugins = new ArrayList<>();
                for (OsmReadingHooks p : getPossibleHooks()) {
                        if (p instanceof ResidentialHook && style != null && !style.getUsedTags().contains("mkgmap:residential"))
                                continue;
                        if (p.init(saver, props, style)){
                                plugins.add(p);
                        }
                }

                OsmReadingHooks hooks;
                switch (plugins.size()) {
                case 0:
                        hooks = new NullHook();
                        break;
                case 1:
                        hooks = plugins.get(0);
                        break;
                default:
                        OsmReadingHooksChain chain = new OsmReadingHooksChain();
                        for (OsmReadingHooks p : plugins) {
                                chain.add(p);
                        }
                        hooks = chain;
                }
                usedTags.addAll(hooks.getUsedTags());
                return hooks;
        }

        /** do nothing hook */
        private class NullHook implements OsmReadingHooks {}

        private static Map<String, Set<String>> readDeleteTagsFile(String fileName) {
                Map<String, Set<String>> deletedTags = new HashMap<>();
               
                // 14Jan20 Changed from using DefaultCharset to UTF-8
                try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(fileName), StandardCharsets.UTF_8))) {
                        String line;
                        while ((line = br.readLine()) != null) {
                                line = line.trim();
                                if (line.isEmpty())
                                        continue;
                                if (line.charAt(0) == '\uFEFF') { // BOM
                                        line = line.substring(1);
                                        if (line.isEmpty())
                                                continue;
                                }
                                if (!line.startsWith("#") && !line.startsWith(";")) {
                                        String[] parts = line.split("=");
                                        if (parts.length == 2) {
                                                parts[0] = parts[0].trim();
                                                parts[1] = parts[1].trim();
                                                if ("*".equals(parts[1])) {
                                                        deletedTags.put(parts[0], new HashSet<>());
                                                } else {
                                                        deletedTags.computeIfAbsent(parts[0], k-> new HashSet<>()).add(parts[1]);
                                                }
                                        } else {
                                                log.error("Ignoring bad line in deleted tags file: " + line);
                                        }
                                }
                        }
                }
                catch(FileNotFoundException e) {
                        log.error("Could not open delete tags file " + fileName);
                }
                catch(IOException e) {
                        log.error("Error reading delete tags file " + fileName);
                }

                if(deletedTags.isEmpty())
                        deletedTags = null;

                return deletedTags;
        }

        /**
         * Create the appropriate converter from osm to garmin styles.
         *
         */

        protected void createConverter() {
                EnhancedProperties props = getConfig();
                setStyle(StyleImpl.readStyle(props));

                usedTags.addAll(style.getUsedTags());
                // make sure that we don't remove tags which are only used with the mkgmap:from-node: prefix
                style.getUsedTags().stream().filter(s -> s.startsWith(POIGeneratorHook.FROM_NODE_PREFIX))
                                .map(s -> s.substring(POIGeneratorHook.FROM_NODE_PREFIX.length())).forEach(usedTags::add);
                usedTags.addAll(CommandArgs.getNameTags(props));
                converter = new StyledConverter(style, mapper, props);
        }

        public OsmConverter getConverter() {
                return converter;
        }

        public Set<String> getUsedTags() {
                return usedTags;
        }
       
        @Override
        public Boolean getDriveOnLeft(){
                return converter.getDriveOnLeft();
        }

        @Override
        public boolean isFileSupported(String name) {
                return true; // we always try xml reader if nothing else matched
        }

        @Override
        public int getPoiDispFlag() {
                return 0; // no info in OSM data  
        }
}