/*
* 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.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
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.ExitException;
import uk.me.parabola.imgfmt.FormatException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.log.Logger;
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.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 abstract class OsmMapDataSource extends MapperBasedMapDataSource
implements LoadableMapDataSource, LoadableOsmDataSource
{
private static final Logger log = Logger.getLogger(OsmMapDataSource.class);
private Style style;
private final OsmReadingHooks[] POSSIBLE_HOOKS = {
new SeaGenerator(),
new MultiPolygonFinishHook(),
new RelationStyleHook(),
new LinkDestinationHook(),
new UnusedElementsRemoverHook(),
new RoutingHook(),
new HighwayHooks(),
new LocationHook(),
new POIGeneratorHook(),
new HousenumberHooks(),
};
protected OsmConverter converter;
private final Set<String> usedTags = new HashSet<>();
protected ElementSaver elementSaver;
protected OsmReadingHooks osmReadingHooks;
/**
* 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) {
if (style != null) {
levelSpec = style.getOption(optionName);
log.debug("getting " + optionName + " from style:", levelSpec);
}
}
return levelSpec;
}
@Override
public void load(String name) throws FileNotFoundException, FormatException {
InputStream is = Utils.openFile(name);
load(is);
}
/**
* 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) {
List<String> copyrightArray = new ArrayList<>();
try {
File file = new File(copyrightFileName);
BufferedReader reader = Files.newBufferedReader(file.toPath(), Charset.forName("utf-8"));
String text;
while ((text = reader.readLine()) != null) {
copyrightArray.add(text);
}
reader.close();
} catch (FileNotFoundException e) {
throw new ExitException("Could not open copyright file " + copyrightFileName);
} catch (IOException e) {
throw new ExitException("Error reading copyright file " + copyrightFileName);
}
String[] copyright = new String[copyrightArray.size()];
copyrightArray.toArray(copyright);
return copyright;
}
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();
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);
}
}
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<OsmReadingHooks>();
for (OsmReadingHooks p : getPossibleHooks()) {
if (p.init(saver, props)){
plugins.add(p);
if (p instanceof RelationStyleHook)
((RelationStyleHook) p).setStyle(style);
}
}
OsmReadingHooks hooks;
switch (plugins.size()) {
case 0:
hooks = new OsmReadingHooksAdaptor();
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;
}
private Map<String, Set<String>> readDeleteTagsFile(String fileName) {
Map<String, Set<String>> deletedTags = new HashMap<String,Set<String>>();
try {
BufferedReader br = new BufferedReader(new FileReader(fileName));
String line;
while((line = br.readLine()) != null) {
line = line.trim();
if(line.length() > 0 && !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<String>());
} else {
Set<String> vals = deletedTags.get(parts[0]);
if (vals == null)
vals = new HashSet<String>();
vals.add(parts[1]);
deletedTags.put(parts[0], vals);
}
} else {
log.error("Ignoring bad line in deleted tags file: " + line);
}
}
}
br.close();
}
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();
Style style = StyleImpl.readStyle(props);
setStyle(style);
usedTags.addAll(style.getUsedTags());
converter = new StyledConverter(style, mapper, props);
}
public OsmConverter getConverter() {
return converter;
}
public Set<String> getUsedTags() {
return usedTags;
}
@Override
public Boolean getDriveOnLeft(){
return converter.getDriveOnLeft();
}
}