/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Reinventing Geospatial, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.rgi.dem2gh;
import com.graphhopper.GraphHopper;
import com.graphhopper.reader.dem.ElevationProvider;
import com.graphhopper.util.CmdArgs;
import com.rgi.common.Pair;
import com.rgi.common.coordinate.CoordinateReferenceSystem;
import com.rgi.routingnetworks.dem.DemRoutingNetworkStoreReader;
import com.rgi.routingnetworks.image.ImageRoutingNetworkStoreWriter;
import com.rgi.store.routingnetworks.Edge;
import com.rgi.store.routingnetworks.RoutingNetworkStoreException;
import com.rgi.store.routingnetworks.RoutingNetworkStoreReader;
import com.rgi.store.routingnetworks.RoutingNetworkStoreWriter;
import com.rgi.store.routingnetworks.Utility;
import com.rgi.store.routingnetworks.osm.OsmXmlRoutingNetworkStoreWriter;
import org.kohsuke.args4j.CmdLineParser;
import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.math.RoundingMode;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.text.DecimalFormat;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* @author Luke Lambert
*/
@SuppressWarnings({"UseOfSystemOutOrSystemErr", "MagicNumber"})
public final class Dem2Graphhopper
{
private Dem2Graphhopper()
{
}
@SuppressWarnings("NumericCastThatLosesPrecision")
public static void main(final String[] args)
{
final CommandLineOptions options = new CommandLineOptions();
final CmdLineParser parser = new CmdLineParser(options);
try
{
parser.parseArgument(args);
}
catch(final Throwable th)
{
System.err.println(th.getMessage());
parser.printUsage(System.out);
}
final String inputFilename = options.getInputFile().getName();
final String baseOutputFileName = inputFilename.substring(0, inputFilename.lastIndexOf('.'));
try
{
final long startTime = System.currentTimeMillis();
final DemRoutingNetworkStoreReader demNetworkReader = createDemRoutingNetworkStoreReader(options);
final RoutingNetworkStoreReader osmRoutingNetworkStoreReader = addHighwayTags(demNetworkReader);
final File osmXmlOutputFile = writeOsmNetwork(baseOutputFileName, osmRoutingNetworkStoreReader);
writeGraphHopperBinaryNetwork(baseOutputFileName, osmXmlOutputFile);
if(options.getOutputRasterizedNetwork())
{
final int imageWidth = (int)(demNetworkReader.getRasterWidth() * options.getOutputRasterScale());
final int imageHeight = (int)(demNetworkReader.getRasterHeight()* options.getOutputRasterScale());
writeImageNetwork(baseOutputFileName,
demNetworkReader,
imageWidth,
imageHeight);
}
System.out.format("Total process finished in %s seconds\n",
elapsedTime(System.currentTimeMillis() - startTime));
}
catch(final Throwable th)
{
System.err.println(th.getMessage());
}
}
private static void writeImageNetwork(final String baseOutputFileName,
final RoutingNetworkStoreReader networkReader,
final int imageWidth,
final int imageHeight) throws RoutingNetworkStoreException
{
final long startTime = System.currentTimeMillis();
final File rasterizedNetworkFile = new File(baseOutputFileName + ".network.tif");
System.out.format("Writing rasterized network to %s...",
rasterizedNetworkFile.getName());
//noinspection NumericCastThatLosesPrecision
new ImageRoutingNetworkStoreWriter(rasterizedNetworkFile,
imageWidth,
imageHeight,
new Color(255, 255, 255, 0), // Transparent
Color.BLACK,
networkReader.getBounds(),
new ConsoleProgressCallback()).write(networkReader.getNodes(),
networkReader.getEdges(),
networkReader.getNodeDimensionality(),
networkReader.getNodeAttributeDescriptions(),
networkReader.getEdgeAttributeDescriptions(),
networkReader.getCoordinateReferenceSystem());
System.out.format(" ...finished! (%s)\n",
elapsedTime(System.currentTimeMillis() - startTime));
}
private static RoutingNetworkStoreReader addHighwayTags(final RoutingNetworkStoreReader inputRoutingNetworkStoreReader) throws RoutingNetworkStoreException
{
final List<Pair<String, Type>> edgeAttributeDescriptions = new ArrayList(inputRoutingNetworkStoreReader.getEdgeAttributeDescriptions());
edgeAttributeDescriptions.add(Pair.of("highway", String.class));
return Utility.transform(node -> node,
edge -> { final List<Object> attributes = new ArrayList(edge.getAttributes());
attributes.add("footway");
return new Edge(edge.getIdentifier(),
edge.getFrom(),
edge.getTo(),
edge.getEdgeDirectionality(),
attributes);
},
inputRoutingNetworkStoreReader.getNodeAttributeDescriptions(),
edgeAttributeDescriptions,
inputRoutingNetworkStoreReader.getNodes(),
inputRoutingNetworkStoreReader.getEdges(),
inputRoutingNetworkStoreReader.getCoordinateReferenceSystem(),
inputRoutingNetworkStoreReader.getDescription(),
inputRoutingNetworkStoreReader.getNodeDimensionality());
}
private static void writeGraphHopperBinaryNetwork(final String baseOutputFileName,
final File osmXmlFile) throws IOException
{
final long startTime = System.currentTimeMillis();
final String graphHopperOutputDirectoryName = baseOutputFileName + "-gh";
final String[] inputs = { "graph.flag_encoders=foot",
"graph.elevation.dataaccess=RAM_STORE",
"prepare.ch.weightings=no",
"graph.dataaccess=RAM_STORE",
"graph.location=" + graphHopperOutputDirectoryName, // where to store the results
"osmreader.osm=" + osmXmlFile // input osm
};
final GraphHopper graphHopper = new GraphHopper().init(CmdArgs.read(inputs));
try
{
final ElevationProvider tagElevationProvider = new TagElevationProvider();
tagElevationProvider.setBaseURL(osmXmlFile.getPath());
graphHopper.setElevation(true);
graphHopper.setElevationProvider(tagElevationProvider);
graphHopper.importOrLoad(); // Creates binary output
final File graphHopperOutputDirectory = new File(graphHopperOutputDirectoryName);
try
{
// Create Zip from binary folder output
zipDirectory(graphHopperOutputDirectory, 9);
}
finally
{
// Delete the temporary folder
if(graphHopperOutputDirectory.exists())
{
recursivelyDeleteDirectory(graphHopperOutputDirectory);
}
}
}
finally
{
graphHopper.close();
}
System.out.format(" ...finished! (%s)\n",
elapsedTime(System.currentTimeMillis() - startTime));
}
private static void zipDirectory(final File directory,
final int compressionLevel) throws IOException
{
final String graphHopperZipFilename = directory.getName() + ".zip";
try(final FileOutputStream fileOutputStream = new FileOutputStream(graphHopperZipFilename))
{
try(final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream))
{
try(final ZipOutputStream zipOutputStream = new ZipOutputStream(bufferedOutputStream))
{
zipOutputStream.setLevel(compressionLevel);
final String[] directoryList = directory.list();
if(directoryList != null)
{
@SuppressWarnings("CheckForOutOfMemoryOnLargeArrayAllocation")
final byte[] buffer = new byte[1024];
for(final String subFilename : directoryList)
{
zipOutputStream.putNextEntry(new ZipEntry(directory.getName() + '/' + subFilename));
try(final FileInputStream fileInputStream = new FileInputStream(directory.getAbsolutePath() + '/' + subFilename))
{
int readLength;
//noinspection NestedAssignment
while((readLength = fileInputStream.read(buffer)) > 0)
{
zipOutputStream.write(buffer, 0, readLength);
}
}
finally
{
zipOutputStream.closeEntry();
}
}
}
}
}
}
}
private static void recursivelyDeleteDirectory(final File graphHopperOutputDirectory) throws IOException
{
// Example courtesy of: https://stackoverflow.com/a/27917071/16434
Files.walkFileTree(graphHopperOutputDirectory.toPath(),
new SimpleFileVisitor<Path>()
{
@Override
public FileVisitResult visitFile(final Path file,
final BasicFileAttributes attrs) throws IOException
{
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(final Path dir,
final IOException exc) throws IOException
{
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}
private static File writeOsmNetwork(final String baseOutputFileName,
final RoutingNetworkStoreReader networkReader) throws RoutingNetworkStoreException
{
final long startTime = System.currentTimeMillis();
final File osmXmlOutputFile = new File(baseOutputFileName + ".osm.xml");
System.out.format("Writing OSM XML network to %s...",
osmXmlOutputFile.getName());
final RoutingNetworkStoreWriter networkWriter = new OsmXmlRoutingNetworkStoreWriter(osmXmlOutputFile,
networkReader.getBounds(),
networkReader.getDescription());
networkWriter.write(networkReader.getNodes(),
networkReader.getEdges(),
networkReader.getNodeDimensionality(),
networkReader.getNodeAttributeDescriptions(),
networkReader.getEdgeAttributeDescriptions(),
networkReader.getCoordinateReferenceSystem());
System.out.format(" ...finished! (%s)\n",
elapsedTime(System.currentTimeMillis() - startTime));
return osmXmlOutputFile;
}
private static DemRoutingNetworkStoreReader createDemRoutingNetworkStoreReader(final CommandLineOptions options) throws RoutingNetworkStoreException
{
final long startTime = System.currentTimeMillis();
System.out.format("Reading the elevation data, and creating the network from %s... \n",
options.getInputFile().getName());
final DemRoutingNetworkStoreReader demNetworkReader = new DemRoutingNetworkStoreReader(options.getInputFile(),
options.getRasterBand(),
options.getContourElevationInterval(),
options.getNoDataValue(),
options.getCoordinatePrecision(),
options.getSimplificationTolerance(),
options.getTriangulationTolerance(),
new CoordinateReferenceSystem("EPSG", 4326),
new ConsoleProgressCallback());
System.out.format("\n...finished! (%s)\n",
elapsedTime(System.currentTimeMillis() - startTime));
return demNetworkReader;
}
static String elapsedTime(final long milliseconds)
{
Duration duration = Duration.ofMillis(milliseconds);
final StringBuilder timeString = new StringBuilder();
if(duration.toDays() > 0)
{
timeString.append(duration.toDays()).append("d ");
}
duration = duration.minusDays(duration.toDays());
if(duration.toHours() > 0)
{
timeString.append(duration.toHours()).append("h ");
}
duration = duration.minusHours(duration.toHours());
if(duration.toMinutes() > 0)
{
timeString.append(duration.toMinutes()).append("m ");
}
duration = duration.minusMinutes(duration.toMinutes());
final double seconds = duration.toNanos() / 1.0e9d;
final DecimalFormat df = new DecimalFormat("#.##");
df.setRoundingMode(RoundingMode.DOWN);
timeString.append(df.format(seconds))
.append('s');
return timeString.toString();
}
}