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
}
}