/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.reader.shp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.DoubleSummaryStatistics;
import java.util.Random;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.graphhopper.GHRequest;
import com.graphhopper.GHResponse;
import com.graphhopper.GraphHopper;
import com.graphhopper.PathWrapper;
import com.graphhopper.reader.osm.GraphHopperOSM;
import com.graphhopper.routing.util.CarFlagEncoder;
import com.graphhopper.routing.util.EncodingManager;
import com.graphhopper.util.DistanceCalc;
import com.graphhopper.util.Helper;
import com.graphhopper.util.PointList;
import com.graphhopper.util.shapes.GHPoint;
/**
* @author Vikas Veshishth
* @author Philip Welch
*/
public class ShapeFileReaderTest {
private static final String shapefile = "/data/gis.osm_roads_free_1.shp";
private static final String pbf = "/data/malta-latest.osm.pbf";
private static final String tempOutputDirFromShp = "target/test-db-shp";
private static final String tempOutputDirFromPbf = "target/test-db-pbf";
private static GraphHopper hopperShp;
private static GraphHopper hopperPbf;
private final DistanceCalc distCalc = Helper.DIST_EARTH;
private static Exception BEFORE_CLASS_EXCEPTION = null;
private static class FromToPair {
final GHPoint from;
final GHPoint to;
FromToPair(double fromLat, double fromLng, double toLat, double toLng) {
this(new GHPoint(fromLat, fromLng), new GHPoint(toLat, toLng));
}
FromToPair(GHPoint from, GHPoint to) {
this.from = from;
this.to = to;
}
PathWrapper getPath(GraphHopper hopper, boolean assertNoErrors) {
GHRequest request = new GHRequest(from, to).setVehicle("car");
GHResponse response = hopper.route(request);
if (assertNoErrors) {
assertFalse(response.hasErrors());
}
if (!response.hasErrors()) {
return response.getBest();
}
return null;
}
}
private static class ExpectedDuration extends FromToPair {
final double minSecs;
final double maxSecs;
private ExpectedDuration(double fromLat, double fromLng, double toLat, double toLng,
double minSecs, double maxSecs) {
super(fromLat, fromLng, toLat, toLng);
this.minSecs = minSecs;
this.maxSecs = maxSecs;
}
}
private static GraphHopper initHopper(GraphHopper gh, String inputFile, String outDir) {
URL resourceURL = ShapeFileReaderTest.class.getResource(inputFile);
try {
inputFile = new File(resourceURL.toURI()).getAbsolutePath();
} catch (Exception e) {
throw new RuntimeException(e);
}
// turn off geometry simplification so geometry should be the same
// between pbf and shapefile readers
gh.setWayPointMaxDistance(0);
return gh.setStoreOnFlush(false).setDataReaderFile(inputFile)
.setGraphHopperLocation(new File(outDir).getAbsolutePath())
.setEncodingManager(new EncodingManager(new CarFlagEncoder()))
.setCHEnabled(false).importOrLoad();
}
/**
* Build the graphs once only for the various tests
*/
@BeforeClass
public static void setupBeforeClass() {
try {
new File(tempOutputDirFromShp).mkdirs();
new File(tempOutputDirFromPbf).mkdirs();
hopperShp = initHopper(new GraphhopperSHP(), shapefile, tempOutputDirFromShp);
hopperPbf = initHopper(new GraphHopperOSM(), pbf, tempOutputDirFromPbf);
} catch (Exception e) {
// Junit silently fails if we get an exception in the setup before
// class,
// so we record it here and explicitly rethrow it
BEFORE_CLASS_EXCEPTION = e;
}
}
@AfterClass
public static void teardownAfterClass() {
try {
hopperShp.close();
hopperShp.clean();
} catch (Exception e) {
}
try {
hopperPbf.close();
hopperPbf.clean();
} catch (Exception e) {
}
}
@Before
public void beforeTest() throws Exception {
// Rethrow the exception from @BeforeClass here so it doesn't silently
// fail.
// (Junit silently fails on exceptions thrown in @BeforeClass but not
// for
// exceptions thrown in @Before)
if (BEFORE_CLASS_EXCEPTION != null) {
throw BEFORE_CLASS_EXCEPTION;
}
}
@Test
public void testOneWay() {
// We setup 2 points very close together on a one-way street.
// As its a one way street, the ordering of the start and end requires
// going around the block to serve them.
// As the scenario is simple, we should get the same results from both
// shapefile and pbf.
// We should also get route distance to be many times physical distance
FromToPair pair = new FromToPair(35.898324, 14.510729, 35.898328, 14.510681);
PathWrapper shp = pair.getPath(hopperShp, true);
PathWrapper pbf = pair.getPath(hopperPbf, true);
double metresShp = shp.getDistance();
double metresPbf = pbf.getDistance();
// should be many times the physical separation between the points (as
// we had to go round the block)
double straightLineDistMetres = distCalc.calcDist(pair.from.lat, pair.from.lon, pair.to.lat,
pair.to.lon);
assertTrue(metresShp > straightLineDistMetres * 25);
// should be the same to within 1 cm
assertEquals(metresShp, metresPbf, 0.01);
}
@Test
public void testGeometrySingleEdgePath() {
// We choose a path along a single edge with a couple of minor bends,
// which we expect to give identical results...
FromToPair pair = new FromToPair(35.911694, 14.492303, 35.911494, 14.490489);
PointList shp = pair.getPath(hopperShp, true).getPoints();
PointList pbf = pair.getPath(hopperPbf, true).getPoints();
assertTrue("The chosen edge had a couple of bends!", shp.getSize() >= 2);
assertSameGeometry(shp, pbf);
}
private void assertSameGeometry(PointList shp, PointList pbf) {
assertEquals(shp.getSize(), pbf.getSize());
for (int i = 0; i < shp.getSize(); i++) {
assertEquals(shp.getLat(i), pbf.getLat(i), 0.0000001);
assertEquals(shp.getLon(i), pbf.getLon(i), 0.0000001);
}
}
@Test
public void testTravelTimesBetweenRandomLocations() {
int nTests = 200;
final Random random = new Random(123);
final GHPoint min = new GHPoint(35.882931, 14.403076);
final GHPoint max = new GHPoint(35.913523, 14.448566);
class RandPointGenerator {
double rand(double min, double max) {
return min + random.nextDouble() * (max - min);
}
GHPoint randPoint() {
return new GHPoint(rand(min.lat, max.lat), rand(min.lon, max.lon));
}
}
RandPointGenerator pointGenerator = new RandPointGenerator();
int nbFails = 0;
DoubleSummaryStatistics stats = new DoubleSummaryStatistics();
for (int i = 0; i < nTests; i++) {
FromToPair pair = new FromToPair(pointGenerator.randPoint(),
pointGenerator.randPoint());
// paths from random points can fail to don't assert on failure
PathWrapper shpPath = pair.getPath(hopperShp, false);
PathWrapper pbfPath = pair.getPath(hopperPbf, false);
// paths between random points can fail to find a route (i.e. be off
// the road network)
if (shpPath == null || pbfPath == null) {
nbFails++;
continue;
}
double shpSecs = getSecondsTravel(shpPath);
double pbfSecs = getSecondsTravel(pbfPath);
double frac = shpSecs / pbfSecs;
double percentageDeviation = Math.abs(1.0 - frac) * 100;
stats.accept(percentageDeviation);
}
assertTrue("Number of fails should be small for the chosen box", nbFails < nTests / 3);
// Test mean fraction. There will be some deviation as not all tags are
// considered etc,
// but we expect it to be small for a large number of tests
double mean = stats.getAverage();
assertTrue("Should have a mean deviation in travel times of less than 1%", mean < 1.0);
}
@Test
public void testTravelTimesBetweenPredefinedLocations() throws URISyntaxException {
// try a couple of test points, with an expected time range that will
// only fail if something is really bad...
ExpectedDuration[] expected = new ExpectedDuration[]{
new ExpectedDuration(35.899167, 14.515171, 35.894126, 14.502983, 60,
60 * 6),
new ExpectedDuration(35.899167, 14.515171, 35.877645, 14.398956, 8 * 60,
25 * 60),
new ExpectedDuration(35.85817, 14.561348, 35.877645, 14.398956, 10 * 60,
30 * 60),
new ExpectedDuration(35.812802, 14.528732, 35.979673, 14.335785, 20 * 60,
50 * 60),};
// The chosen locations should have small deviations in travel times
double tolDiffFromPbf = 0.01;
for (ExpectedDuration ed : expected) {
double secsShp = getSecondsTravel(ed.getPath(hopperShp, true));
double secsPbf = getSecondsTravel(ed.getPath(hopperPbf, true));
double frac = secsShp / secsPbf;
String message = "From (" + ed.from + ") to (" + ed.to + ") expected " + ed.minSecs
+ " <= travelsecs <= " + ed.maxSecs + ", found " + secsShp
+ " secs, pbf was " + secsPbf + " secs, frac diff=" + frac;
assertTrue(message, secsShp >= ed.minSecs);
assertTrue(message, secsShp <= ed.maxSecs);
// we also use a tolerance difference with the pbf
assertTrue(frac > 1 - tolDiffFromPbf);
assertTrue(frac < 1 + tolDiffFromPbf);
}
}
private static double getSecondsTravel(PathWrapper pw) {
long millis = pw.getTime();
double secs = 0.001 * millis;
return secs;
}
}