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