/* 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.
*/
import com.rgi.common.BoundingBox;
import com.rgi.common.Pair;
import com.rgi.common.coordinate.referencesystem.profile.CrsProfile;
import com.rgi.common.coordinate.referencesystem.profile.SphericalMercatorCrsProfile;
import com.rgi.geopackage.GeoPackage;
import com.rgi.geopackage.core.SpatialReferenceSystem;
import com.rgi.geopackage.extensions.implementation.BadImplementationException;
import com.rgi.geopackage.extensions.network.AttributeDescription;
import com.rgi.geopackage.extensions.network.AttributedType;
import com.rgi.geopackage.extensions.network.DataType;
import com.rgi.geopackage.extensions.network.GeoPackageNetworkExtension;
import com.rgi.geopackage.extensions.network.Network;
import com.rgi.geopackage.extensions.routing.GeoPackageRoutingExtension;
import com.rgi.geopackage.verification.ConformanceException;
import com.rgi.geopackage.verification.VerificationLevel;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@SuppressWarnings({ "javadoc", "unused" })
public final class GenerateRoutingNetworks
{
private static final Pattern SpacePattern = Pattern.compile("\\s+");
private GenerateRoutingNetworks()
{
// hide the constructor
}
public static void main(final String[] args)
{
final File geoPackageFile = new File("routing_networks.gpkg");
if(geoPackageFile.exists())
{
if(!geoPackageFile.delete())
{
System.err.format("Unable to replace %s, aborting.", geoPackageFile.getAbsoluteFile());
}
}
try(final GeoPackage gpkg = new GeoPackage(geoPackageFile, VerificationLevel.None, GeoPackage.OpenMode.Create))
{
final GeoPackageRoutingExtension routingExtension = gpkg.extensions()
.getExtensionImplementation(GeoPackageRoutingExtension.class);
final GeoPackageNetworkExtension networkExtension = routingExtension.getNetworkExtension();
loadContourDataset(gpkg,
networkExtension,
routingExtension);
loadSqliteDataset("usma_pandolf",
gpkg,
networkExtension,
routingExtension);
loadSqliteDataset("mwtc_pandolf",
gpkg,
networkExtension,
routingExtension);
}
catch(final ClassNotFoundException | SQLException | ConformanceException | IOException | BadImplementationException ex)
{
ex.printStackTrace();
}
}
private static void loadSqliteDataset(final String name,
final GeoPackage geoPackage,
final GeoPackageNetworkExtension networkExtension,
final GeoPackageRoutingExtension routingExtension) throws IOException, ClassNotFoundException, SQLException
{
Class.forName("org.sqlite.JDBC");
final File databaseFile = new File("data/" + name + ".sqlite");
try(final Connection db = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.toURI()))
{
final File nodesFile = new File("data/" + name + "_node_geometries.txt");
final CrsProfile sphericalMercatorCrsProfile = new SphericalMercatorCrsProfile();
final SpatialReferenceSystem spatialReferenceSystem = geoPackage.core().addSpatialReferenceSystem(sphericalMercatorCrsProfile.getName(),
sphericalMercatorCrsProfile.getCoordinateReferenceSystem().getAuthority(),
sphericalMercatorCrsProfile.getCoordinateReferenceSystem().getIdentifier(),
sphericalMercatorCrsProfile.getWellKnownText(),
sphericalMercatorCrsProfile.getDescription());
final Network network = networkExtension.addNetwork(name,
name,
name + " dataset provided by Matt Renner of AGC",
getBoundingBox(nodesFile, 2, 3),
spatialReferenceSystem);
final AttributeDescription slopeAttribute = networkExtension.addAttributeDescription(network,
"slope",
"meters",
DataType.Real,
"slope",
AttributedType.Edge);
final AttributeDescription distanceAttribute = networkExtension.addAttributeDescription(network,
"distance",
"meters",
DataType.Real,
"distance",
AttributedType.Edge);
final AttributeDescription pandolfCostAttribute = networkExtension.addAttributeDescription(network,
"cost_pandolf",
"kcal",
DataType.Real,
"caloric cost walking",
AttributedType.Edge);
final AttributeDescription elevationAttribute = networkExtension.addAttributeDescription(network,
"elevation",
"meters",
DataType.Real,
"elevation",
AttributedType.Node);
final AttributeDescription longitudeAttribute = networkExtension.addAttributeDescription(network,
"longitude",
"degrees",
DataType.Real,
"longitude",
AttributedType.Node);
final AttributeDescription latitudeAttribute = networkExtension.addAttributeDescription(network,
"latitude",
"degrees",
DataType.Real,
"latitude",
AttributedType.Node);
// Add the attributed edges
final String query = String.format("Select %s, %s, %s, %s, %s FROM %s",
"from_node",
"to_node",
"slope",
"length",
"cost_pandolf",
"edges");
try(final PreparedStatement stmt = db.prepareStatement(query))
{
try(ResultSet results = stmt.executeQuery())
{
loadAttributedEdges(networkExtension,
results,
network,
Arrays.asList(slopeAttribute,
distanceAttribute,
pandolfCostAttribute));
}
}
// Add attributed nodes
loadNodeAttributes(networkExtension,
nodesFile,
network,
Arrays.asList(elevationAttribute,
longitudeAttribute,
latitudeAttribute));
routingExtension.addRoutingNetworkDescription(network,
longitudeAttribute,
latitudeAttribute,
elevationAttribute);
}
}
private static void loadContourDataset(final GeoPackage geoPackage,
final GeoPackageNetworkExtension networkExtension,
final GeoPackageRoutingExtension routingExtension) throws SQLException, IOException
{
final File nodeFile = new File("data/contour.1/contour.1.node");
final CrsProfile sphericalMercatorCrsProfile = new SphericalMercatorCrsProfile();
final SpatialReferenceSystem spatialReferenceSystem = geoPackage.core().addSpatialReferenceSystem(sphericalMercatorCrsProfile.getName(),
sphericalMercatorCrsProfile.getCoordinateReferenceSystem().getAuthority(),
sphericalMercatorCrsProfile.getCoordinateReferenceSystem().getIdentifier(),
sphericalMercatorCrsProfile.getWellKnownText(),
sphericalMercatorCrsProfile.getDescription());
final Network network = networkExtension.addNetwork("contour_1",
"contour_1",
"contour.1 dataset provided by Matt Renner of AGC",
getBoundingBox(nodeFile, 1, 2),
spatialReferenceSystem);
final AttributeDescription longitudeAttribute = networkExtension.addAttributeDescription(network,
"longitude",
"degrees",
DataType.Real,
"longitude",
AttributedType.Node);
final AttributeDescription latitudeAttribute = networkExtension.addAttributeDescription(network,
"latitude",
"degrees",
DataType.Real,
"latitude",
AttributedType.Node);
final AttributeDescription elevationAttribute = networkExtension.addAttributeDescription(network,
"elevation",
"meters",
DataType.Real,
"elevation",
AttributedType.Node);
loadNodeAttributes(networkExtension,
nodeFile,
network,
Arrays.asList(longitudeAttribute,
latitudeAttribute,
elevationAttribute));
final File edgeFile = new File("data/contour.1/contour.1.edge");
loadEdges(networkExtension,
edgeFile,
network);
routingExtension.addRoutingNetworkDescription(network,
longitudeAttribute,
latitudeAttribute,
elevationAttribute);
}
private static BoundingBox getBoundingBox(final File triangleFormatNodes,
final int longitudeIndex,
final int latitudeIndex) throws IOException
{
final double[] bbox = { Double.MAX_VALUE, // x min
Double.MAX_VALUE, // y min
-Double.MAX_VALUE, // x max
-Double.MAX_VALUE // y max
};
Files.lines(triangleFormatNodes.toPath())
.skip(1L) // the first line is a header
.filter(line -> !line.startsWith("#"))
.forEach(line -> { final String[] pieces = SpacePattern.split(line.trim());
final double x = Double.valueOf(pieces[longitudeIndex]);
final double y = Double.valueOf(pieces[latitudeIndex]);
if(x < bbox[0])
{
bbox[0] = x;
}
if(x > bbox[2])
{
bbox[2] = x;
}
if(y < bbox[1])
{
bbox[1] = y;
}
if(y > bbox[3])
{
bbox[3] = y;
}
});
return new BoundingBox(bbox[0],
bbox[1],
bbox[2],
bbox[3]);
}
/**
* Puts a file in the Triangle utility node format
* (https://www.cs.cmu.edu/~quake/triangle.node.html) into a network
*/
private static void loadNodeAttributes(final GeoPackageNetworkExtension networkExtension,
final File triangleFormatNodes,
final Network network,
final List<AttributeDescription> attributeDescriptions) throws SQLException, IOException
{
final Function<String, Pair<Integer, List<Object>>> lineToPair = line -> { final String[] pieces = SpacePattern.split(line.trim());
return new Pair<>(Integer.valueOf(pieces[0]), // vertex # (node id)
Arrays.asList(pieces)
.stream()
.skip(1L) // Skip the first element, the vertex number/node id
.limit(attributeDescriptions.size())
.map(Double::valueOf)
.collect(Collectors.toList()));
};
try(Stream<Pair<Integer, List<Object>>> pairs = Files.lines(triangleFormatNodes.toPath())
.skip(1L) // the first line is a header
.filter(line -> !line.startsWith("#"))
.map(lineToPair))
{
networkExtension.addNodes(pairs::iterator,
attributeDescriptions);
}
}
private static void loadEdges(final GeoPackageNetworkExtension networkExtension,
final File triangleFormatEdges,
final Network network) throws SQLException, IOException
{
final Function<String, Pair<Integer, Integer>> lineToPair = line -> { final String[] pieces = SpacePattern.split(line.trim());
// Integer.valueOf(pieces[0]), // edge # (edge id), unused, we use our own id, but it should be the same in most cases
return new Pair<>(Integer.valueOf(pieces[1]), // from node
Integer.valueOf(pieces[2])); // to node
};
try(final Stream<Pair<Integer, Integer>> pairs = Files.lines(triangleFormatEdges.toPath())
.skip(1L) // the first line is a header
.filter(line -> !line.startsWith("#"))
.map(lineToPair))
{
networkExtension.addEdges(network, pairs::iterator);
}
// Now add the links in reverse (i.e., we've added one direction, A->B, now add B->A, since the original data had no directionality
final Function<String, Pair<Integer, Integer>> lineToPair2 = line -> { final String[] pieces = SpacePattern.split(
line.trim());
return new Pair<>(Integer.valueOf(pieces[2]), // from node
Integer.valueOf(pieces[1])); // to node
};
try(final Stream<Pair<Integer, Integer>> pairs = Files.lines(triangleFormatEdges.toPath())
.skip(1L) // the first line is a header
.filter(line -> !line.startsWith("#"))
.map(lineToPair2))
{
networkExtension.addEdges(network, pairs::iterator);
}
}
private static void loadAttributedEdges(final GeoPackageNetworkExtension networkExtension,
final ResultSet resultSet,
final Network network,
final List<AttributeDescription> attributeDescriptions) throws SQLException
{
final Collection<Pair<Pair<Integer, Integer>, List<Object>>> edges = new ArrayList<>();
while(resultSet.next())
{
edges.add(Pair.of(Pair.of(resultSet.getInt(1), // from node
resultSet.getInt(2)), // to node
Arrays.asList(resultSet.getDouble(3), // slope
resultSet.getDouble(4), // distance
resultSet.getDouble(5)))); // pandolf cost
}
networkExtension.addAttributedEdges(edges, attributeDescriptions);
}
}