2021-12-23 10:42:24 +00:00
|
|
|
package com.onthegomap.planetiler;
|
|
|
|
|
2023-03-18 18:38:04 +00:00
|
|
|
import com.onthegomap.planetiler.archive.TileArchiveConfig;
|
2023-02-05 19:16:05 +00:00
|
|
|
import com.onthegomap.planetiler.archive.TileArchiveMetadata;
|
|
|
|
import com.onthegomap.planetiler.archive.TileArchiveWriter;
|
2023-03-18 18:38:04 +00:00
|
|
|
import com.onthegomap.planetiler.archive.TileArchives;
|
2023-02-05 19:16:05 +00:00
|
|
|
import com.onthegomap.planetiler.archive.WriteableTileArchive;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.collection.FeatureGroup;
|
|
|
|
import com.onthegomap.planetiler.collection.LongLongMap;
|
2022-03-23 00:34:54 +00:00
|
|
|
import com.onthegomap.planetiler.collection.LongLongMultimap;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.config.Arguments;
|
|
|
|
import com.onthegomap.planetiler.config.PlanetilerConfig;
|
2023-01-02 17:19:05 +00:00
|
|
|
import com.onthegomap.planetiler.reader.GeoPackageReader;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.reader.NaturalEarthReader;
|
|
|
|
import com.onthegomap.planetiler.reader.ShapefileReader;
|
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmInputFile;
|
2022-06-03 09:25:17 +00:00
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmNodeBoundsProvider;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.reader.osm.OsmReader;
|
2022-03-03 12:25:24 +00:00
|
|
|
import com.onthegomap.planetiler.stats.ProcessInfo;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.stats.Stats;
|
|
|
|
import com.onthegomap.planetiler.stats.Timers;
|
2023-02-25 12:45:45 +00:00
|
|
|
import com.onthegomap.planetiler.util.AnsiColors;
|
2023-01-02 16:26:00 +00:00
|
|
|
import com.onthegomap.planetiler.util.BuildInfo;
|
2022-03-19 09:46:03 +00:00
|
|
|
import com.onthegomap.planetiler.util.ByteBufferUtil;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.util.Downloader;
|
|
|
|
import com.onthegomap.planetiler.util.FileUtils;
|
2022-03-03 12:25:24 +00:00
|
|
|
import com.onthegomap.planetiler.util.Format;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.util.Geofabrik;
|
|
|
|
import com.onthegomap.planetiler.util.LogUtil;
|
2022-03-19 09:46:03 +00:00
|
|
|
import com.onthegomap.planetiler.util.ResourceUsage;
|
2021-12-23 10:42:24 +00:00
|
|
|
import com.onthegomap.planetiler.util.Translations;
|
|
|
|
import com.onthegomap.planetiler.util.Wikidata;
|
|
|
|
import com.onthegomap.planetiler.worker.RunnableThatThrows;
|
2023-01-17 12:05:45 +00:00
|
|
|
import java.io.IOException;
|
2022-12-15 19:19:22 +00:00
|
|
|
import java.nio.file.FileSystem;
|
2021-09-10 00:46:20 +00:00
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.function.Function;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* High-level API for creating a new map that ties together lower-level utilities in a way that is suitable for the most
|
|
|
|
* common use-cases.
|
|
|
|
* <p>
|
|
|
|
* For example:
|
2022-03-09 02:08:03 +00:00
|
|
|
*
|
|
|
|
* <pre>
|
|
|
|
* <code>
|
2021-09-10 00:46:20 +00:00
|
|
|
* public static void main(String[] args) {
|
2021-12-23 10:42:24 +00:00
|
|
|
* Planetiler.create(arguments)
|
2021-09-10 00:46:20 +00:00
|
|
|
* .setProfile(new CustomProfile())
|
|
|
|
* .addShapefileSource("shapefile", Path.of("shapefile.zip"))
|
|
|
|
* .addNaturalEarthSource("natural_earth", Path.of("natural_earth.zip"))
|
|
|
|
* .addOsmSource("osm", Path.of("source.osm.pbf"))
|
|
|
|
* .setOutput("mbtiles", Path.of("output.mbtiles"))
|
|
|
|
* .run();
|
2022-03-09 02:08:03 +00:00
|
|
|
* }</code>
|
|
|
|
* </pre>
|
2021-09-10 00:46:20 +00:00
|
|
|
* <p>
|
|
|
|
* Each call to a builder API mutates the runner instance and returns it for more chaining.
|
|
|
|
* <p>
|
|
|
|
* See {@code ToiletsOverlayLowLevelApi} or unit tests for examples using the low-level API.
|
|
|
|
*/
|
|
|
|
@SuppressWarnings("UnusedReturnValue")
|
2021-12-23 10:42:24 +00:00
|
|
|
public class Planetiler {
|
2021-09-10 00:46:20 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private static final Logger LOGGER = LoggerFactory.getLogger(Planetiler.class);
|
2021-09-10 00:46:20 +00:00
|
|
|
private final List<Stage> stages = new ArrayList<>();
|
|
|
|
private final List<ToDownload> toDownload = new ArrayList<>();
|
2021-10-20 01:57:47 +00:00
|
|
|
private final List<InputPath> inputPaths = new ArrayList<>();
|
2021-09-10 00:46:20 +00:00
|
|
|
private final Timers.Finishable overallTimer;
|
|
|
|
private final Arguments arguments;
|
|
|
|
private final Stats stats;
|
|
|
|
private final Path tmpDir;
|
2022-03-31 10:42:28 +00:00
|
|
|
private final Path nodeDbPath;
|
|
|
|
private final Path multipolygonPath;
|
|
|
|
private final Path featureDbPath;
|
2021-09-10 00:46:20 +00:00
|
|
|
private final boolean downloadSources;
|
|
|
|
private final boolean onlyDownloadSources;
|
2022-06-03 09:25:17 +00:00
|
|
|
private final boolean parseNodeBounds;
|
2021-09-10 00:46:20 +00:00
|
|
|
private Profile profile = null;
|
2021-12-23 10:42:24 +00:00
|
|
|
private Function<Planetiler, Profile> profileProvider = null;
|
|
|
|
private final PlanetilerConfig config;
|
2021-09-10 00:46:20 +00:00
|
|
|
private FeatureGroup featureGroup;
|
|
|
|
private OsmInputFile osmInputFile;
|
2023-03-18 18:38:04 +00:00
|
|
|
private TileArchiveConfig output;
|
2021-09-10 00:46:20 +00:00
|
|
|
private boolean overwrite = false;
|
|
|
|
private boolean ran = false;
|
|
|
|
// most common OSM languages
|
|
|
|
private List<String> languages = List.of(
|
|
|
|
"en", "ru", "ar", "zh", "ja", "ko", "fr",
|
|
|
|
"de", "fi", "pl", "es", "be", "br", "he"
|
|
|
|
);
|
|
|
|
private Translations translations;
|
|
|
|
private Path wikidataNamesFile;
|
|
|
|
private boolean useWikidata = false;
|
|
|
|
private boolean onlyFetchWikidata = false;
|
|
|
|
private boolean fetchWikidata = false;
|
2023-01-17 12:05:45 +00:00
|
|
|
private TileArchiveMetadata tileArchiveMetadata;
|
2021-09-10 00:46:20 +00:00
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private Planetiler(Arguments arguments) {
|
2021-09-10 00:46:20 +00:00
|
|
|
this.arguments = arguments;
|
|
|
|
stats = arguments.getStats();
|
2022-10-04 23:57:59 +00:00
|
|
|
overallTimer = stats.startStageQuietly("overall");
|
2021-12-23 10:42:24 +00:00
|
|
|
config = PlanetilerConfig.from(arguments);
|
2023-02-25 12:45:45 +00:00
|
|
|
if (config.color() != null) {
|
|
|
|
AnsiColors.setUseColors(config.color());
|
|
|
|
}
|
2021-09-10 00:46:20 +00:00
|
|
|
tmpDir = arguments.file("tmpdir", "temp directory", Path.of("data", "tmp"));
|
|
|
|
onlyDownloadSources = arguments.getBoolean("only_download", "download source data then exit", false);
|
|
|
|
downloadSources = onlyDownloadSources || arguments.getBoolean("download", "download sources", false);
|
2022-03-31 10:42:28 +00:00
|
|
|
|
|
|
|
nodeDbPath = arguments.file("temp_nodes", "temp node db location", tmpDir.resolve("node.db"));
|
|
|
|
multipolygonPath =
|
|
|
|
arguments.file("temp_multipolygons", "temp multipolygon db location", tmpDir.resolve("multipolygon.db"));
|
|
|
|
featureDbPath = arguments.file("temp_features", "temp feature db location", tmpDir.resolve("feature.db"));
|
2022-06-03 09:25:17 +00:00
|
|
|
parseNodeBounds =
|
|
|
|
arguments.getBoolean("osm_parse_node_bounds", "parse bounds from OSM nodes instead of header", false);
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Returns a new empty runner that will get configuration from {@code arguments}. */
|
2021-12-23 10:42:24 +00:00
|
|
|
public static Planetiler create(Arguments arguments) {
|
|
|
|
return new Planetiler(arguments);
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new {@code .osm.pbf} source that will be processed when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code .osm.pbf} file, set {@code name_path=newpath.osm.pbf} in the arguments.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} argument is not set
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see OsmInputFile
|
|
|
|
* @see OsmReader
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addOsmSource(String name, Path defaultPath) {
|
2021-09-10 00:46:20 +00:00
|
|
|
return addOsmSource(name, defaultPath, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new {@code .osm.pbf} source that will be processed when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code .osm.pbf} file, set {@code name_path=newpath.osm.pbf} in the arguments and
|
|
|
|
* to override the download URL set {@code name_url=http://url/of/osm.pbf}.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} argument is not set
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set. As a shortcut, can use "geofabrik:monaco" or
|
2022-03-09 02:08:03 +00:00
|
|
|
* "geofabrik:australia" shorthand to find an extract by name from
|
|
|
|
* <a href="https://download.geofabrik.de/">Geofabrik download site</a> or "aws:latest" to download
|
2021-10-20 01:57:47 +00:00
|
|
|
* the latest {@code planet.osm.pbf} file from <a href="https://registry.opendata.aws/osm/">AWS
|
|
|
|
* Open Data Registry</a>.
|
2021-09-10 00:46:20 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see OsmInputFile
|
|
|
|
* @see OsmReader
|
|
|
|
* @see Downloader
|
|
|
|
* @see Geofabrik
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addOsmSource(String name, Path defaultPath, String defaultUrl) {
|
2021-09-10 00:46:20 +00:00
|
|
|
if (osmInputFile != null) {
|
|
|
|
// TODO: support more than one input OSM file
|
|
|
|
throw new IllegalArgumentException("Currently only one OSM input file is supported");
|
|
|
|
}
|
|
|
|
Path path = getPath(name, "OSM input file", defaultPath, defaultUrl);
|
2022-03-01 01:52:30 +00:00
|
|
|
var thisInputFile = new OsmInputFile(path, config.osmLazyReads());
|
2021-09-10 00:46:20 +00:00
|
|
|
osmInputFile = thisInputFile;
|
2022-03-19 09:46:03 +00:00
|
|
|
// fail fast if there is some issue with madvise on this system
|
2022-03-23 00:34:54 +00:00
|
|
|
if (config.nodeMapMadvise() || config.multipolygonGeometryMadvise()) {
|
2022-03-19 09:46:03 +00:00
|
|
|
ByteBufferUtil.init();
|
|
|
|
}
|
2021-09-10 00:46:20 +00:00
|
|
|
return appendStage(new Stage(
|
|
|
|
name,
|
|
|
|
List.of(
|
|
|
|
name + "_pass1: Pre-process OpenStreetMap input (store node locations then relation members)",
|
|
|
|
name + "_pass2: Process OpenStreetMap nodes, ways, then relations"
|
|
|
|
),
|
|
|
|
ifSourceUsed(name, () -> {
|
2023-01-02 16:26:00 +00:00
|
|
|
var header = osmInputFile.getHeader();
|
2023-03-18 18:38:04 +00:00
|
|
|
tileArchiveMetadata.setExtraMetadata("planetiler:" + name + ":osmosisreplicationtime", header.instant());
|
|
|
|
tileArchiveMetadata.setExtraMetadata("planetiler:" + name + ":osmosisreplicationseq",
|
2023-01-17 12:05:45 +00:00
|
|
|
header.osmosisReplicationSequenceNumber());
|
2023-03-18 18:38:04 +00:00
|
|
|
tileArchiveMetadata.setExtraMetadata("planetiler:" + name + ":osmosisreplicationurl",
|
|
|
|
header.osmosisReplicationBaseUrl());
|
2021-09-10 00:46:20 +00:00
|
|
|
try (
|
2022-03-09 12:22:33 +00:00
|
|
|
var nodeLocations =
|
|
|
|
LongLongMap.from(config.nodeMapType(), config.nodeMapStorage(), nodeDbPath, config.nodeMapMadvise());
|
2022-03-23 00:34:54 +00:00
|
|
|
var multipolygonGeometries = LongLongMultimap.newReplaceableMultimap(
|
|
|
|
config.multipolygonGeometryStorage(), multipolygonPath, config.multipolygonGeometryMadvise());
|
|
|
|
var osmReader = new OsmReader(name, thisInputFile, nodeLocations, multipolygonGeometries, profile(), stats)
|
2021-09-10 00:46:20 +00:00
|
|
|
) {
|
|
|
|
osmReader.pass1(config);
|
|
|
|
osmReader.pass2(featureGroup, config);
|
|
|
|
} finally {
|
|
|
|
FileUtils.delete(nodeDbPath);
|
2022-03-23 00:34:54 +00:00
|
|
|
FileUtils.delete(multipolygonPath);
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-09 02:08:03 +00:00
|
|
|
* Adds a new ESRI shapefile source that will be processed using a projection inferred from the shapefile when
|
|
|
|
* {@link #run()} is called.
|
2021-09-10 00:46:20 +00:00
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code shapefile} file, set {@code name_path=newpath.shp.zip} in the arguments.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a
|
|
|
|
* {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file
|
|
|
|
* containing the shapefile components.
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see ShapefileReader
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addShapefileSource(String name, Path defaultPath) {
|
2021-09-10 00:46:20 +00:00
|
|
|
return addShapefileSource(null, name, defaultPath);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-09 02:08:03 +00:00
|
|
|
* Adds a new ESRI shapefile source that will be processed using an explicit projection when {@link #run()} is called.
|
2021-09-10 00:46:20 +00:00
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code shapefile} file, set {@code name_path=newpath.shp.zip} in the arguments.
|
|
|
|
*
|
2022-03-09 02:08:03 +00:00
|
|
|
* @param projection the Coordinate Reference System authority code to use, parsed with
|
|
|
|
* {@link org.geotools.referencing.CRS#decode(String)}
|
2021-09-10 00:46:20 +00:00
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a
|
|
|
|
* {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file
|
|
|
|
* containing the shapefile components.
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see ShapefileReader
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addShapefileSource(String projection, String name, Path defaultPath) {
|
2021-09-10 00:46:20 +00:00
|
|
|
return addShapefileSource(projection, name, defaultPath, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-09 02:08:03 +00:00
|
|
|
* Adds a new ESRI shapefile source that will be processed with a projection inferred from the shapefile when
|
|
|
|
* {@link #run()} is called.
|
2021-09-10 00:46:20 +00:00
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code shapefile} file, set {@code name_path=newpath.shp.zip} in the arguments and
|
|
|
|
* to override the download URL set {@code name_url=http://url/of/shapefile.zip}.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a
|
|
|
|
* {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file
|
|
|
|
* containing the shapefile components.
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set
|
2021-09-10 00:46:20 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see ShapefileReader
|
|
|
|
* @see Downloader
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addShapefileSource(String name, Path defaultPath, String defaultUrl) {
|
2021-09-10 00:46:20 +00:00
|
|
|
return addShapefileSource(null, name, defaultPath, defaultUrl);
|
|
|
|
}
|
|
|
|
|
2022-12-15 19:19:22 +00:00
|
|
|
/**
|
2023-01-01 22:29:00 +00:00
|
|
|
* Adds a new ESRI shapefile glob source that will process all files under {@param basePath} matching
|
|
|
|
* {@param globPattern}. {@param basePath} may be a directory or ZIP archive.
|
2022-12-15 19:19:22 +00:00
|
|
|
*
|
|
|
|
* @param sourceName string to use in stats and logs to identify this stage
|
|
|
|
* @param basePath path to the directory containing shapefiles to process
|
|
|
|
* @param globPattern string to match filenames against, as described in {@link FileSystem#getPathMatcher(String)}.
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see ShapefileReader
|
|
|
|
*/
|
2023-01-01 22:29:00 +00:00
|
|
|
public Planetiler addShapefileGlobSource(String sourceName, Path basePath, String globPattern) {
|
|
|
|
return addShapefileGlobSource(null, sourceName, basePath, globPattern, null);
|
2022-12-15 19:19:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-01-01 22:29:00 +00:00
|
|
|
* Adds a new ESRI shapefile glob source that will process all files under {@param basePath} matching
|
|
|
|
* {@param globPattern} using an explicit projection. {@param basePath} may be a directory or ZIP archive.
|
|
|
|
* <p>
|
|
|
|
* If {@param globPattern} matches a ZIP archive, all files ending in {@code .shp} within the archive will be used for
|
|
|
|
* this source.
|
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
2022-12-15 19:19:22 +00:00
|
|
|
*
|
2023-01-01 22:29:00 +00:00
|
|
|
* @param projection the Coordinate Reference System authority code to use, parsed with
|
|
|
|
* {@link org.geotools.referencing.CRS#decode(String)}
|
2022-12-15 19:19:22 +00:00
|
|
|
* @param sourceName string to use in stats and logs to identify this stage
|
2023-01-01 22:29:00 +00:00
|
|
|
* @param basePath path to the directory or zip file containing shapefiles to process
|
2022-12-15 19:19:22 +00:00
|
|
|
* @param globPattern string to match filenames against, as described in {@link FileSystem#getPathMatcher(String)}.
|
2023-01-01 22:29:00 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set
|
2022-12-15 19:19:22 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see ShapefileReader
|
|
|
|
*/
|
2023-01-01 22:29:00 +00:00
|
|
|
public Planetiler addShapefileGlobSource(String projection, String sourceName, Path basePath,
|
|
|
|
String globPattern, String defaultUrl) {
|
|
|
|
Path dirPath = getPath(sourceName, "shapefile glob", basePath, defaultUrl);
|
|
|
|
|
|
|
|
return addStage(sourceName, "Process all files matching " + dirPath + "/" + globPattern,
|
|
|
|
ifSourceUsed(sourceName, () -> {
|
|
|
|
var sourcePaths = FileUtils.walkPathWithPattern(basePath, globPattern,
|
|
|
|
zipPath -> FileUtils.walkPathWithPattern(zipPath, "*.shp"));
|
|
|
|
ShapefileReader.processWithProjection(projection, sourceName, sourcePaths, featureGroup, config,
|
|
|
|
profile, stats);
|
|
|
|
}));
|
2022-12-15 19:19:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
/**
|
|
|
|
* Adds a new ESRI shapefile source that will be processed with an explicit projection when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code shapefile} file, set {@code name_path=newpath.shp.zip} in the arguments and
|
|
|
|
* to override the download URL set {@code name_url=http://url/of/shapefile.zip}.
|
|
|
|
*
|
2022-03-09 02:08:03 +00:00
|
|
|
* @param projection the Coordinate Reference System authority code to use, parsed with
|
|
|
|
* {@link org.geotools.referencing.CRS#decode(String)}
|
2021-09-10 00:46:20 +00:00
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments. Can be a
|
|
|
|
* {@code .shp} file with other shapefile components in the same directory, or a {@code .zip} file
|
|
|
|
* containing the shapefile components.
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set
|
2021-09-10 00:46:20 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see ShapefileReader
|
|
|
|
* @see Downloader
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addShapefileSource(String projection, String name, Path defaultPath, String defaultUrl) {
|
2021-09-10 00:46:20 +00:00
|
|
|
Path path = getPath(name, "shapefile", defaultPath, defaultUrl);
|
|
|
|
return addStage(name, "Process features in " + path,
|
2023-01-01 22:29:00 +00:00
|
|
|
ifSourceUsed(name, () -> {
|
|
|
|
List<Path> sourcePaths = List.of(path);
|
|
|
|
if (FileUtils.hasExtension(path, "zip") || Files.isDirectory(path)) {
|
|
|
|
sourcePaths = FileUtils.walkPathWithPattern(path, "*.shp");
|
|
|
|
}
|
|
|
|
|
|
|
|
ShapefileReader.processWithProjection(projection, name, sourcePaths, featureGroup, config, profile, stats);
|
|
|
|
}));
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
2023-01-02 17:19:05 +00:00
|
|
|
/**
|
|
|
|
* Adds a new OGC GeoPackage source that will be processed when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code geopackage} file, set {@code name_path=newpath.gpkg} in the arguments and to
|
|
|
|
* override the download URL set {@code name_url=http://url/of/file.gpkg}.
|
2023-01-26 01:56:30 +00:00
|
|
|
* <p>
|
|
|
|
* If given a path to a ZIP file containing one or more GeoPackages, each {@code .gpkg} file within will be extracted
|
|
|
|
* to a temporary directory at runtime.
|
2023-01-02 17:19:05 +00:00
|
|
|
*
|
2023-01-26 01:56:30 +00:00
|
|
|
* @param projection the Coordinate Reference System authority code to use, parsed with
|
|
|
|
* {@link org.geotools.referencing.CRS#decode(String)}
|
2023-01-02 17:19:05 +00:00
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set
|
2023-01-02 17:19:05 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see GeoPackageReader
|
|
|
|
* @see Downloader
|
|
|
|
*/
|
2023-01-26 01:56:30 +00:00
|
|
|
public Planetiler addGeoPackageSource(String projection, String name, Path defaultPath, String defaultUrl) {
|
2023-01-02 17:19:05 +00:00
|
|
|
Path path = getPath(name, "geopackage", defaultPath, defaultUrl);
|
2023-03-19 18:01:17 +00:00
|
|
|
boolean keepUnzipped = getKeepUnzipped(name);
|
2023-01-02 17:19:05 +00:00
|
|
|
return addStage(name, "Process features in " + path,
|
2023-01-26 01:56:30 +00:00
|
|
|
ifSourceUsed(name, () -> {
|
|
|
|
List<Path> sourcePaths = List.of(path);
|
|
|
|
if (FileUtils.hasExtension(path, "zip")) {
|
|
|
|
sourcePaths = FileUtils.walkPathWithPattern(path, "*.gpkg");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (sourcePaths.isEmpty()) {
|
|
|
|
throw new IllegalArgumentException("No .gpkg files found in " + path);
|
|
|
|
}
|
|
|
|
|
2023-03-19 18:01:17 +00:00
|
|
|
GeoPackageReader.process(projection, name, sourcePaths,
|
|
|
|
keepUnzipped ? path.resolveSibling(path.getFileName() + "-unzipped") : tmpDir, featureGroup, config, profile,
|
|
|
|
stats, keepUnzipped);
|
2023-01-26 01:56:30 +00:00
|
|
|
}));
|
2023-01-02 17:19:05 +00:00
|
|
|
}
|
|
|
|
|
2023-01-26 01:56:30 +00:00
|
|
|
/**
|
|
|
|
* Adds a new OGC GeoPackage source that will be processed when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code geopackage} file, set {@code name_path=newpath.gpkg} in the arguments and to
|
|
|
|
* override the download URL set {@code name_url=http://url/of/file.gpkg}.
|
|
|
|
* <p>
|
|
|
|
* If given a path to a ZIP file containing one or more GeoPackages, each {@code .gpkg} file within will be extracted
|
|
|
|
* to a temporary directory at runtime.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name_path} key is not set through arguments
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set
|
2023-01-26 01:56:30 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see GeoPackageReader
|
|
|
|
* @see Downloader
|
|
|
|
*/
|
|
|
|
public Planetiler addGeoPackageSource(String name, Path defaultPath, String defaultUrl) {
|
|
|
|
return addGeoPackageSource(null, name, defaultPath, defaultUrl);
|
|
|
|
}
|
2023-01-02 17:19:05 +00:00
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
/**
|
|
|
|
* Adds a new Natural Earth sqlite file source that will be processed when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code sqlite} file, set {@code name_path=newpath.zip} in the arguments and to
|
|
|
|
* override the download URL set {@code name_url=http://url/of/natural_earth.zip}.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name} key is not set through arguments. Can be the
|
|
|
|
* {@code .sqlite} file or a {@code .zip} file containing the sqlite file.
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see NaturalEarthReader
|
2023-03-18 18:38:04 +00:00
|
|
|
* @deprecated can be replaced by {@link #addGeoPackageSource(String, Path, String)}.
|
2021-09-10 00:46:20 +00:00
|
|
|
*/
|
2023-01-26 01:56:30 +00:00
|
|
|
@Deprecated(forRemoval = true)
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addNaturalEarthSource(String name, Path defaultPath) {
|
2021-09-10 00:46:20 +00:00
|
|
|
return addNaturalEarthSource(name, defaultPath, null);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new Natural Earth sqlite file source that will be processed when {@link #run()} is called.
|
|
|
|
* <p>
|
|
|
|
* If the file does not exist and {@code download=true} argument is set, then the file will first be downloaded from
|
|
|
|
* {@code defaultUrl}.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the {@code sqlite} file, set {@code name_path=newpath.zip} in the arguments and to
|
|
|
|
* override the download URL set {@code name_url=http://url/of/natural_earth.zip}.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param defaultPath path to the input file to use if {@code name} key is not set through arguments. Can be the
|
|
|
|
* {@code .sqlite} file or a {@code .zip} file containing the sqlite file.
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultUrl remote URL that the file to download if {@code download=true} argument is set and
|
|
|
|
* {@code name_url} argument is not set
|
2021-09-10 00:46:20 +00:00
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see NaturalEarthReader
|
|
|
|
* @see Downloader
|
2023-03-18 18:38:04 +00:00
|
|
|
* @deprecated can be replaced by {@link #addGeoPackageSource(String, Path, String)}.
|
2021-09-10 00:46:20 +00:00
|
|
|
*/
|
2023-01-26 01:56:30 +00:00
|
|
|
@Deprecated(forRemoval = true)
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addNaturalEarthSource(String name, Path defaultPath, String defaultUrl) {
|
2021-09-10 00:46:20 +00:00
|
|
|
Path path = getPath(name, "sqlite db", defaultPath, defaultUrl);
|
2023-03-19 18:01:17 +00:00
|
|
|
boolean keepUnzipped = getKeepUnzipped(name);
|
2021-09-10 00:46:20 +00:00
|
|
|
return addStage(name, "Process features in " + path, ifSourceUsed(name, () -> NaturalEarthReader
|
2023-03-19 18:01:17 +00:00
|
|
|
.process(name, path, keepUnzipped ? path.resolveSibling(path.getFileName() + "-unzipped") : tmpDir, featureGroup,
|
|
|
|
config, profile, stats, keepUnzipped)));
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adds a new stage that will be invoked when {@link #run()} is called.
|
|
|
|
*
|
|
|
|
* @param name string to use in stats and logs to identify this stage
|
|
|
|
* @param description details to print when logging what stages will run
|
|
|
|
* @param task the task to run
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler addStage(String name, String description, RunnableThatThrows task) {
|
2021-09-10 00:46:20 +00:00
|
|
|
return appendStage(new Stage(name, description, task));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the default languages that will be used by {@link #translations()} when not overridden by {@code languages}
|
|
|
|
* argument.
|
|
|
|
*
|
|
|
|
* @param languages the list of languages to use when {@code name} argument is not set
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler setDefaultLanguages(List<String> languages) {
|
2021-09-10 00:46:20 +00:00
|
|
|
this.languages = languages;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-09 02:08:03 +00:00
|
|
|
* Updates {@link #translations()} to use name translations fetched from wikidata based on the
|
|
|
|
* <a href="https://www.wikidata.org/wiki/Wikidata:OpenStreetMap">wikidata tag</a> on OSM elements.
|
2021-09-10 00:46:20 +00:00
|
|
|
* <p>
|
|
|
|
* When either {@code only_fetch_wikidata} or {@code fetch_wikidata} arguments are set to true, this downloads
|
|
|
|
* translations for every OSM element that the profile cares about and stores them to {@code defaultWikidataCache} (or
|
|
|
|
* the value of the {@code wikidata_cache} argument) before processing any sources.
|
|
|
|
* <p>
|
|
|
|
* As long as {@code use_wikidata} is not set to false, then previously-downloaded wikidata translations will be
|
2022-03-27 09:49:58 +00:00
|
|
|
* loaded from the cache file, so you can run with {@code fetch_wikidata=true} once, then without it each subsequent
|
2021-09-10 00:46:20 +00:00
|
|
|
* run to only download translations once.
|
|
|
|
*
|
|
|
|
* @param defaultWikidataCache Path to store downloaded wikidata name translations to, and to read them from on
|
|
|
|
* subsequent runs. Overridden by {@code wikidata_cache} argument value.
|
|
|
|
* @return this runner for chaining
|
|
|
|
* @see Wikidata
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler fetchWikidataNameTranslations(Path defaultWikidataCache) {
|
2021-09-10 00:46:20 +00:00
|
|
|
onlyFetchWikidata = arguments
|
|
|
|
.getBoolean("only_fetch_wikidata", "fetch wikidata translations then quit", onlyFetchWikidata);
|
|
|
|
fetchWikidata =
|
|
|
|
onlyFetchWikidata || arguments.getBoolean("fetch_wikidata", "fetch wikidata translations then continue",
|
|
|
|
fetchWikidata);
|
|
|
|
useWikidata = fetchWikidata || arguments.getBoolean("use_wikidata", "use wikidata translations", true);
|
|
|
|
wikidataNamesFile = arguments.file("wikidata_cache", "wikidata cache file", defaultWikidataCache);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Translations translations() {
|
|
|
|
if (translations == null) {
|
|
|
|
boolean transliterate = arguments.getBoolean("transliterate", "attempt to transliterate latin names", true);
|
|
|
|
List<String> languages = arguments.getList("languages", "languages to use", this.languages);
|
|
|
|
translations = Translations.defaultProvider(languages).setShouldTransliterate(transliterate);
|
|
|
|
}
|
|
|
|
return translations;
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
private Planetiler appendStage(Stage stage) {
|
2021-09-10 00:46:20 +00:00
|
|
|
if (stages.stream().anyMatch(other -> stage.id.equals(other.id))) {
|
|
|
|
throw new IllegalArgumentException("Duplicate stage name: " + stage.id);
|
|
|
|
}
|
|
|
|
stages.add(stage);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2023-03-19 18:01:17 +00:00
|
|
|
private boolean getKeepUnzipped(String name) {
|
|
|
|
return arguments.getBoolean(name + "_keep_unzipped",
|
|
|
|
"keep unzipped " + name + " after reading", config.keepUnzippedSources());
|
|
|
|
}
|
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
/** Sets the profile implementation that controls how source feature map to output map elements. */
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler setProfile(Profile profile) {
|
2021-09-10 00:46:20 +00:00
|
|
|
this.profile = profile;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets a profile that needs information from this runner to be instantiated.
|
|
|
|
* <p>
|
|
|
|
* Construction will be deferred until all inputs are read.
|
|
|
|
*/
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler setProfile(Function<Planetiler, Profile> profileProvider) {
|
2021-09-10 00:46:20 +00:00
|
|
|
this.profileProvider = profileProvider;
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-03-18 18:38:04 +00:00
|
|
|
* Sets the location of the output archive to write rendered tiles to.
|
2021-09-10 00:46:20 +00:00
|
|
|
*
|
2023-03-18 18:38:04 +00:00
|
|
|
* @deprecated Use {@link #setOutput(String)} instead
|
2021-09-10 00:46:20 +00:00
|
|
|
*/
|
2023-03-18 18:38:04 +00:00
|
|
|
@Deprecated(forRemoval = true)
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler setOutput(String argument, Path fallback) {
|
2023-03-18 18:38:04 +00:00
|
|
|
this.output =
|
|
|
|
TileArchiveConfig
|
|
|
|
.from(arguments.getString("output|" + argument, "output tile archive path", fallback.toString()));
|
2021-09-10 00:46:20 +00:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-03-18 18:38:04 +00:00
|
|
|
* Sets the location of the output archive to write rendered tiles to. Fails if the archive already exists.
|
2021-09-10 00:46:20 +00:00
|
|
|
* <p>
|
2023-03-18 18:38:04 +00:00
|
|
|
* To override the location of the file, set {@code argument=newpath} in the arguments. To set options for the output
|
|
|
|
* drive add {@code output.mbtiles?arg=value} or add command-line argument {@code mbtiles_arg=value}.
|
2021-09-10 00:46:20 +00:00
|
|
|
*
|
2023-03-18 18:38:04 +00:00
|
|
|
* @param defaultOutputUri The default output URI string to write to.
|
2021-09-10 00:46:20 +00:00
|
|
|
* @return this runner instance for chaining
|
2023-03-18 18:38:04 +00:00
|
|
|
* @see TileArchiveConfig For details on URI string formats and options.
|
|
|
|
*/
|
|
|
|
public Planetiler setOutput(String defaultOutputUri) {
|
|
|
|
this.output = TileArchiveConfig.from(arguments.getString("output", "output tile archive URI", defaultOutputUri));
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Alias for {@link #setOutput(String)} which infers the output type based on extension. */
|
|
|
|
public Planetiler setOutput(Path path) {
|
|
|
|
return setOutput(path.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the location of the output archive to write rendered tiles to.
|
|
|
|
*
|
|
|
|
* @deprecated Use {@link #overwriteOutput(String)} instead
|
2021-09-10 00:46:20 +00:00
|
|
|
*/
|
2023-03-18 18:38:04 +00:00
|
|
|
@Deprecated(forRemoval = true)
|
2021-12-23 10:42:24 +00:00
|
|
|
public Planetiler overwriteOutput(String argument, Path fallback) {
|
2021-09-10 00:46:20 +00:00
|
|
|
this.overwrite = true;
|
|
|
|
return setOutput(argument, fallback);
|
|
|
|
}
|
|
|
|
|
2023-03-18 18:38:04 +00:00
|
|
|
/**
|
|
|
|
* Sets the location of the output archive to write rendered tiles to. Overwrites if the archive already exists.
|
|
|
|
* <p>
|
|
|
|
* To override the location of the file, set {@code argument=newpath} in the arguments. To set options for the output
|
|
|
|
* drive add {@code output.mbtiles?arg=value} or add command-line argument {@code mbtiles_arg=value}.
|
|
|
|
*
|
|
|
|
* @param defaultOutputUri The default output URI string to write to.
|
|
|
|
* @return this runner instance for chaining
|
|
|
|
* @see TileArchiveConfig For details on URI string formats and options.
|
|
|
|
*/
|
|
|
|
public Planetiler overwriteOutput(String defaultOutputUri) {
|
|
|
|
this.overwrite = true;
|
|
|
|
return setOutput(defaultOutputUri);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Alias for {@link #overwriteOutput(String)} which infers the output type based on extension. */
|
|
|
|
public Planetiler overwriteOutput(Path defaultOutput) {
|
|
|
|
return overwriteOutput(defaultOutput.toString());
|
|
|
|
}
|
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
/**
|
|
|
|
* Reads all elements from all sourced that have been added, generates map features according to the profile, and
|
2023-01-17 12:05:45 +00:00
|
|
|
* writes the rendered tiles to the output archive.
|
2021-09-10 00:46:20 +00:00
|
|
|
*
|
|
|
|
* @throws IllegalArgumentException if expected inputs have not been provided
|
|
|
|
* @throws Exception if an error occurs while processing
|
|
|
|
*/
|
|
|
|
public void run() throws Exception {
|
2022-11-26 11:59:13 +00:00
|
|
|
var showVersion = arguments.getBoolean("version", "show version then exit", false);
|
2023-01-02 16:26:00 +00:00
|
|
|
var buildInfo = BuildInfo.get();
|
|
|
|
if (buildInfo != null && LOGGER.isInfoEnabled()) {
|
|
|
|
LOGGER.info("Planetiler build git hash: {}", buildInfo.githash());
|
|
|
|
LOGGER.info("Planetiler build version: {}", buildInfo.version());
|
|
|
|
LOGGER.info("Planetiler build timestamp: {}", buildInfo.buildTimeString());
|
|
|
|
}
|
2022-11-26 11:59:13 +00:00
|
|
|
if (showVersion) {
|
|
|
|
System.exit(0);
|
|
|
|
}
|
2021-09-10 00:46:20 +00:00
|
|
|
if (profile() == null) {
|
|
|
|
throw new IllegalArgumentException("No profile specified");
|
|
|
|
}
|
|
|
|
if (output == null) {
|
|
|
|
throw new IllegalArgumentException("No output specified");
|
|
|
|
}
|
|
|
|
if (stages.isEmpty()) {
|
|
|
|
throw new IllegalArgumentException("No sources specified");
|
|
|
|
}
|
|
|
|
if (ran) {
|
|
|
|
throw new IllegalArgumentException("Can only run once");
|
|
|
|
}
|
|
|
|
ran = true;
|
|
|
|
|
2021-10-20 01:57:47 +00:00
|
|
|
if (arguments.getBoolean("help", "show arguments then exit", false)) {
|
|
|
|
System.exit(0);
|
|
|
|
} else if (onlyDownloadSources) {
|
2021-09-10 00:46:20 +00:00
|
|
|
// don't check files if not generating map
|
2022-03-03 12:25:24 +00:00
|
|
|
} else if (overwrite || config.force()) {
|
2023-03-18 18:38:04 +00:00
|
|
|
output.delete();
|
|
|
|
} else if (output.exists()) {
|
|
|
|
throw new IllegalArgumentException(output.uri() + " already exists, use the --force argument to overwrite.");
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
2023-03-18 18:38:04 +00:00
|
|
|
LOGGER.info("Building {} profile into {} in these phases:", profile.getClass().getSimpleName(), output.uri());
|
2021-09-10 00:46:20 +00:00
|
|
|
|
|
|
|
if (!toDownload.isEmpty()) {
|
2022-06-03 09:25:17 +00:00
|
|
|
LOGGER.info(" download: Download sources {}", toDownload.stream().map(d -> d.id).toList());
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!onlyDownloadSources && fetchWikidata) {
|
|
|
|
LOGGER.info(" wikidata: Fetch translations from wikidata query service");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!onlyDownloadSources && !onlyFetchWikidata) {
|
|
|
|
for (Stage stage : stages) {
|
|
|
|
for (String details : stage.details) {
|
2022-06-03 09:25:17 +00:00
|
|
|
LOGGER.info(" {}", details);
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
LOGGER.info(" sort: Sort rendered features by tile ID");
|
2023-01-17 12:05:45 +00:00
|
|
|
LOGGER.info(" archive: Encode each tile and write to {}", output);
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
|
2022-03-19 09:46:03 +00:00
|
|
|
// in case any temp files are left from a previous run...
|
2022-03-31 10:42:28 +00:00
|
|
|
FileUtils.delete(tmpDir, nodeDbPath, featureDbPath, multipolygonPath);
|
|
|
|
Files.createDirectories(tmpDir);
|
2023-03-18 18:38:04 +00:00
|
|
|
FileUtils.createParentDirectories(nodeDbPath, featureDbPath, multipolygonPath, output.getLocalPath());
|
2022-03-19 09:46:03 +00:00
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
if (!toDownload.isEmpty()) {
|
|
|
|
download();
|
|
|
|
}
|
|
|
|
ensureInputFilesExist();
|
2022-03-31 10:42:28 +00:00
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
if (fetchWikidata) {
|
|
|
|
Wikidata.fetch(osmInputFile(), wikidataNamesFile, config(), profile(), stats());
|
|
|
|
}
|
|
|
|
if (useWikidata) {
|
2022-12-01 22:26:04 +00:00
|
|
|
translations().addFallbackTranslationProvider(Wikidata.load(wikidataNamesFile));
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
2022-03-03 12:25:24 +00:00
|
|
|
if (onlyDownloadSources || onlyFetchWikidata) {
|
|
|
|
return; // exit only if just fetching wikidata or downloading sources
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
2022-03-04 10:18:27 +00:00
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
if (osmInputFile != null) {
|
2022-03-16 09:42:44 +00:00
|
|
|
checkDiskSpace();
|
|
|
|
checkMemory();
|
2022-06-03 09:25:17 +00:00
|
|
|
var bounds = config.bounds();
|
|
|
|
if (!parseNodeBounds) {
|
|
|
|
bounds.addFallbackProvider(osmInputFile);
|
|
|
|
}
|
|
|
|
bounds.addFallbackProvider(new OsmNodeBoundsProvider(osmInputFile, config, stats));
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
2023-03-18 18:38:04 +00:00
|
|
|
// must construct this after bounds providers are added in order to infer bounds from the input source if not provided
|
|
|
|
tileArchiveMetadata = new TileArchiveMetadata(profile, config);
|
2021-09-10 00:46:20 +00:00
|
|
|
|
2023-03-18 18:38:04 +00:00
|
|
|
try (WriteableTileArchive archive = TileArchives.newWriter(output, config)) {
|
2023-01-27 02:43:07 +00:00
|
|
|
featureGroup =
|
|
|
|
FeatureGroup.newDiskBackedFeatureGroup(archive.tileOrder(), featureDbPath, profile, config, stats);
|
|
|
|
stats.monitorFile("nodes", nodeDbPath);
|
|
|
|
stats.monitorFile("features", featureDbPath);
|
|
|
|
stats.monitorFile("multipolygons", multipolygonPath);
|
2023-03-18 18:38:04 +00:00
|
|
|
stats.monitorFile("archive", output.getLocalPath());
|
2021-09-10 00:46:20 +00:00
|
|
|
|
2023-01-27 02:43:07 +00:00
|
|
|
for (Stage stage : stages) {
|
|
|
|
stage.task.run();
|
2022-03-03 12:25:24 +00:00
|
|
|
}
|
2021-09-10 00:46:20 +00:00
|
|
|
|
2023-01-27 02:43:07 +00:00
|
|
|
LOGGER.info("Deleting node.db to make room for output file");
|
|
|
|
profile.release();
|
|
|
|
for (var inputPath : inputPaths) {
|
|
|
|
if (inputPath.freeAfterReading()) {
|
|
|
|
LOGGER.info("Deleting {} ({}) to make room for output file", inputPath.id, inputPath.path);
|
|
|
|
FileUtils.delete(inputPath.path());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
featureGroup.prepare();
|
2021-09-10 00:46:20 +00:00
|
|
|
|
2023-03-18 18:38:04 +00:00
|
|
|
TileArchiveWriter.writeOutput(featureGroup, archive, output::size, tileArchiveMetadata,
|
|
|
|
config, stats);
|
2023-01-17 12:05:45 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
throw new IllegalStateException("Unable to write to " + output, e);
|
|
|
|
}
|
2021-09-10 00:46:20 +00:00
|
|
|
|
|
|
|
overallTimer.stop();
|
|
|
|
LOGGER.info("FINISHED!");
|
|
|
|
stats.printSummary();
|
|
|
|
stats.close();
|
|
|
|
}
|
|
|
|
|
2022-03-03 12:25:24 +00:00
|
|
|
private void checkDiskSpace() {
|
2022-03-19 09:46:03 +00:00
|
|
|
ResourceUsage readPhase = new ResourceUsage("read phase disk");
|
|
|
|
ResourceUsage writePhase = new ResourceUsage("write phase disk");
|
2022-03-03 12:25:24 +00:00
|
|
|
long osmSize = osmInputFile.diskUsageBytes();
|
2022-03-19 09:46:03 +00:00
|
|
|
long nodeMapSize =
|
2022-03-23 00:34:54 +00:00
|
|
|
OsmReader.estimateNodeLocationUsage(config.nodeMapType(), config.nodeMapStorage(), osmSize, tmpDir).diskUsage();
|
|
|
|
long multipolygonGeometrySize =
|
|
|
|
OsmReader.estimateMultipolygonGeometryUsage(config.multipolygonGeometryStorage(), osmSize, tmpDir).diskUsage();
|
2022-03-03 12:25:24 +00:00
|
|
|
long featureSize = profile.estimateIntermediateDiskBytes(osmSize);
|
|
|
|
long outputSize = profile.estimateOutputBytes(osmSize);
|
|
|
|
|
2022-03-23 00:34:54 +00:00
|
|
|
// node locations and multipolygon geometries only needed while reading inputs
|
2022-03-31 10:42:28 +00:00
|
|
|
readPhase.addDisk(nodeDbPath, nodeMapSize, "temporary node location cache");
|
|
|
|
readPhase.addDisk(multipolygonPath, multipolygonGeometrySize, "temporary multipolygon geometry cache");
|
2022-03-19 09:46:03 +00:00
|
|
|
// feature db persists across read/write phase
|
2022-03-31 10:42:28 +00:00
|
|
|
readPhase.addDisk(featureDbPath, featureSize, "temporary feature storage");
|
|
|
|
writePhase.addDisk(featureDbPath, featureSize, "temporary feature storage");
|
2022-03-19 09:46:03 +00:00
|
|
|
// output only needed during write phase
|
2023-03-18 18:38:04 +00:00
|
|
|
writePhase.addDisk(output.getLocalPath(), outputSize, "archive output");
|
2022-03-19 09:46:03 +00:00
|
|
|
// if the user opts to remove an input source after reading to free up additional space for the output...
|
|
|
|
for (var input : inputPaths) {
|
|
|
|
if (input.freeAfterReading()) {
|
|
|
|
writePhase.addDisk(input.path, -FileUtils.size(input.path), "delete " + input.id + " source after reading");
|
2022-03-03 12:25:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-19 09:46:03 +00:00
|
|
|
readPhase.checkAgainstLimits(config.force(), true);
|
|
|
|
writePhase.checkAgainstLimits(config.force(), true);
|
2022-03-03 12:25:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void checkMemory() {
|
2022-03-19 09:46:03 +00:00
|
|
|
Format format = Format.defaultInstance();
|
|
|
|
ResourceUsage check = new ResourceUsage("read phase");
|
2022-03-23 00:34:54 +00:00
|
|
|
ResourceUsage nodeMapUsages = OsmReader.estimateNodeLocationUsage(config.nodeMapType(), config.nodeMapStorage(),
|
2022-03-19 09:46:03 +00:00
|
|
|
osmInputFile.diskUsageBytes(), tmpDir);
|
2022-03-23 00:34:54 +00:00
|
|
|
ResourceUsage multipolygonGeometryUsages =
|
|
|
|
OsmReader.estimateMultipolygonGeometryUsage(config.nodeMapStorage(), osmInputFile.diskUsageBytes(), tmpDir);
|
|
|
|
long memoryMappedFiles = nodeMapUsages.diskUsage() + multipolygonGeometryUsages.diskUsage();
|
2022-03-19 09:46:03 +00:00
|
|
|
|
2022-03-23 00:34:54 +00:00
|
|
|
check
|
|
|
|
.addAll(nodeMapUsages)
|
|
|
|
.addAll(multipolygonGeometryUsages)
|
2022-03-19 09:46:03 +00:00
|
|
|
.addMemory(profile().estimateRamRequired(osmInputFile.diskUsageBytes()), "temporary profile storage");
|
|
|
|
|
|
|
|
check.checkAgainstLimits(config().force(), true);
|
|
|
|
|
|
|
|
// check off-heap memory if we can get it
|
|
|
|
ProcessInfo.getSystemFreeMemoryBytes().ifPresent(extraMemory -> {
|
2022-03-23 00:34:54 +00:00
|
|
|
if (extraMemory < memoryMappedFiles) {
|
|
|
|
LOGGER.warn(
|
|
|
|
"""
|
|
|
|
Planetiler will use ~%s memory-mapped files for node locations and multipolygon geometries but the OS only
|
|
|
|
has %s available to cache pages, this may slow the import down. To speed up, run on a machine with more
|
|
|
|
memory or reduce the -Xmx setting.
|
|
|
|
"""
|
|
|
|
.formatted(
|
|
|
|
format.storage(memoryMappedFiles),
|
|
|
|
format.storage(extraMemory)
|
|
|
|
));
|
|
|
|
} else {
|
|
|
|
LOGGER.debug("✓ %s temporary files and %s of free memory for OS to cache them".formatted(
|
|
|
|
format.storage(memoryMappedFiles),
|
2022-03-19 09:46:03 +00:00
|
|
|
format.storage(extraMemory)
|
2022-03-23 00:34:54 +00:00
|
|
|
|
2022-03-19 09:46:03 +00:00
|
|
|
));
|
2022-03-03 12:25:24 +00:00
|
|
|
}
|
2022-03-19 09:46:03 +00:00
|
|
|
});
|
2022-03-03 12:25:24 +00:00
|
|
|
}
|
|
|
|
|
2021-09-10 00:46:20 +00:00
|
|
|
public Arguments arguments() {
|
|
|
|
return arguments;
|
|
|
|
}
|
|
|
|
|
|
|
|
public OsmInputFile osmInputFile() {
|
|
|
|
return osmInputFile;
|
|
|
|
}
|
|
|
|
|
2021-12-23 10:42:24 +00:00
|
|
|
public PlanetilerConfig config() {
|
2021-09-10 00:46:20 +00:00
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Profile profile() {
|
|
|
|
if (profile == null && profileProvider != null) {
|
|
|
|
profile = profileProvider.apply(this);
|
|
|
|
}
|
|
|
|
return profile;
|
|
|
|
}
|
|
|
|
|
|
|
|
public Stats stats() {
|
|
|
|
return stats;
|
|
|
|
}
|
|
|
|
|
|
|
|
private RunnableThatThrows ifSourceUsed(String name, RunnableThatThrows task) {
|
|
|
|
return () -> {
|
|
|
|
if (profile.caresAboutSource(name)) {
|
|
|
|
task.run();
|
|
|
|
} else {
|
|
|
|
LogUtil.setStage(name);
|
|
|
|
LOGGER.info("Skipping since profile does not use it");
|
|
|
|
LogUtil.clearStage();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private Path getPath(String name, String type, Path defaultPath, String defaultUrl) {
|
|
|
|
Path path = arguments.file(name + "_path", name + " " + type + " path", defaultPath);
|
2022-03-03 12:25:24 +00:00
|
|
|
boolean freeAfterReading = arguments.getBoolean("free_" + name + "_after_read",
|
|
|
|
"delete " + name + " input file after reading to make space for output (reduces peak disk usage)", false);
|
2021-09-10 00:46:20 +00:00
|
|
|
if (downloadSources) {
|
|
|
|
String url = arguments.getString(name + "_url", name + " " + type + " url", defaultUrl);
|
|
|
|
if (!Files.exists(path) && url != null) {
|
|
|
|
toDownload.add(new ToDownload(name, url, path));
|
|
|
|
}
|
|
|
|
}
|
2022-03-03 12:25:24 +00:00
|
|
|
inputPaths.add(new InputPath(name, path, freeAfterReading));
|
2021-09-10 00:46:20 +00:00
|
|
|
return path;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void download() {
|
|
|
|
var timer = stats.startStage("download");
|
2021-10-20 01:57:47 +00:00
|
|
|
Downloader downloader = Downloader.create(config(), stats());
|
2021-09-10 00:46:20 +00:00
|
|
|
for (ToDownload toDownload : toDownload) {
|
2021-10-20 01:57:47 +00:00
|
|
|
if (profile.caresAboutSource(toDownload.id)) {
|
|
|
|
downloader.add(toDownload.id, toDownload.url, toDownload.path);
|
|
|
|
}
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
downloader.run();
|
|
|
|
timer.stop();
|
|
|
|
}
|
|
|
|
|
|
|
|
private void ensureInputFilesExist() {
|
2021-10-20 01:57:47 +00:00
|
|
|
for (InputPath inputPath : inputPaths) {
|
|
|
|
if (profile.caresAboutSource(inputPath.id) && !Files.exists(inputPath.path)) {
|
|
|
|
throw new IllegalArgumentException(inputPath.path + " does not exist");
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 01:45:56 +00:00
|
|
|
private record Stage(String id, List<String> details, RunnableThatThrows task) {
|
2021-09-10 00:46:20 +00:00
|
|
|
|
|
|
|
Stage(String id, String description, RunnableThatThrows task) {
|
|
|
|
this(id, List.of(id + ": " + description), task);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 01:45:56 +00:00
|
|
|
private record ToDownload(String id, String url, Path path) {}
|
2021-10-20 01:57:47 +00:00
|
|
|
|
2022-03-03 12:25:24 +00:00
|
|
|
private record InputPath(String id, Path path, boolean freeAfterReading) {}
|
2021-09-10 00:46:20 +00:00
|
|
|
}
|