blob: e22fd2318f8e9ffb7fc2a83fc921569794fff47a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.sis.referencing.gazetteer;
import java.util.Locale;
import javax.measure.Quantity;
import javax.measure.IncommensurableException;
import javax.measure.quantity.Length;
import org.opengis.referencing.operation.TransformException;
import org.opengis.geometry.DirectPosition;
import org.apache.sis.geometry.DirectPosition2D;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.measure.Quantities;
import org.apache.sis.measure.Units;
// Test dependencies
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.sis.test.TestUtilities;
import org.apache.sis.test.TestCase;
/**
* Tests methods from the {@link GeohashReferenceSystem} class.
*
* @author Ross Laidlaw
* @author Martin Desruisseaux (Geomatys)
*/
public final class GeohashReferenceSystemTest extends TestCase {
/**
* Tolerance factor for floating point comparison.
*/
private static final double TOLERANCE = 0.000001;
/**
* WGS84 semi-major axis length divided by semi-minor axis length.
* This is used for estimating how the precision changes when moving
* from equator to a pole.
*/
private static final double B_A = 6356752.314245179 / 6378137;
/**
* Returns a reference system instance to test.
*/
private static GeohashReferenceSystem instance() throws GazetteerException {
return new GeohashReferenceSystem(GeohashReferenceSystem.Format.BASE32, CommonCRS.defaultGeographic());
}
/**
* A geographic coordinates together with the expected geohash.
*/
private static final class Place {
final String name;
final double longitude;
final double latitude;
final String geohash;
Place(final String name, final double longitude, final double latitude, final String geohash) {
this.name = name;
this.latitude = latitude;
this.longitude = longitude;
this.geohash = geohash;
}
}
/**
* A list o places with their expected geohash.
*/
private static final Place[] PLACES = new Place[] {
new Place("Empire State Building", -73.985656, 40.748433, "dr5ru6j2c62q"),
new Place("Statue Of Liberty", -74.044444, 40.689167, "dr5r7p4rx6kz"),
new Place("The White House", -77.036550, 38.897669, "dqcjqcpeq70c"),
new Place("Hoover Dam", -114.737778, 36.015556, "9qqkvh6mzfpz"),
new Place("Golden Gate Bridge", -122.478611, 37.819722, "9q8zhuvgce0m"),
new Place("Mount Rushmore", -103.459825, 43.878947, "9xy3teyv7ke4"),
new Place("Space Needle", -122.349100, 47.620400, "c22yzvh0gmfy")
};
/**
* Creates a new test case.
*/
public GeohashReferenceSystemTest() {
}
/**
* Tests the {@link GeohashReferenceSystem.Coder#getPrecision(DirectPosition)} method.
* Values published in Wikipedia are used as references, with more precision digits
* added from SIS computation.
*
* @throws TransformException if an exception occurred while initializing the reference system.
*/
@Test
public void testGetPrecision() throws TransformException {
final GeohashReferenceSystem.Coder coder = instance().createCoder();
verifyGetPrecision(coder, 1, 2504689, 0.5);
verifyGetPrecision(coder, 2, 626172, 0.5);
verifyGetPrecision(coder, 3, 78272, 0.5);
verifyGetPrecision(coder, 4, 19568, 0.5);
verifyGetPrecision(coder, 5, 2446, 0.5);
verifyGetPrecision(coder, 6, 611.5, 0.05);
verifyGetPrecision(coder, 7, 76.44, 0.005);
verifyGetPrecision(coder, 8, 19.11, 0.005);
}
/**
* A single test case of {@link #testGetPrecision()} for the given hash string length.
*/
private static void verifyGetPrecision(final GeohashReferenceSystem.Coder coder,
final int length, final double expected, final double tolerance)
{
coder.setHashLength(length);
Quantity<Length> worstCase = coder.getPrecision(null);
Quantity<Length> atEquator = coder.getPrecision(new DirectPosition2D(0, 0));
Quantity<Length> atPole = coder.getPrecision(new DirectPosition2D(0, 90));
Quantity<Length> somewhere = coder.getPrecision(new DirectPosition2D(0, 40));
assertEquals(Units.METRE, worstCase.getUnit());
assertEquals(Units.METRE, atEquator.getUnit());
assertEquals(Units.METRE, atPole .getUnit());
assertEquals(Units.METRE, somewhere.getUnit());
assertEquals(expected, worstCase.getValue().doubleValue(), tolerance);
assertEquals(expected, atEquator.getValue().doubleValue(), tolerance);
/*
* If the length is even, then longitude values have one more bit than latitudes,
* which compensate for the fact that the range of longitude values is twice the
* range of latitude values. Consequently, both coordinate values should have the
* same precision at equator, and moving to the pole changes only the radius.
* Otherwise longitude error is twice larger than latitude error. At the pole,
* the longitude error vanishes and only the latitude error matter.
*/
final double f = (length & 1) != 0 ? B_A : B_A/2;
assertEquals(f * expected, atPole.getValue().doubleValue(), tolerance);
/*
* Value should be somewhere between the two extrems. We use a simple average for this test.
*/
final double estimate = (atEquator.getValue().doubleValue() + atPole.getValue().doubleValue()) / 2;
assertEquals(estimate, somewhere.getValue().doubleValue(), estimate / 25);
}
/**
* Tests the {@link GeohashReferenceSystem.Coder#setPrecision(Quantity, DirectPosition)} method.
* Values used as a reference are the same as {@link #testGetPrecision()}.
*
* @throws TransformException if an exception occurred while initializing the reference system.
* @throws IncommensurableException if a precision uses incompatible units of measurement.
*/
@Test
public void testSetPrecision() throws TransformException, IncommensurableException {
final GeohashReferenceSystem.Coder coder = instance().createCoder();
verifySetPrecision(coder, 1, 2504689);
verifySetPrecision(coder, 2, 626172);
verifySetPrecision(coder, 3, 78272);
verifySetPrecision(coder, 4, 19568);
verifySetPrecision(coder, 5, 2446);
verifySetPrecision(coder, 6, 611.5);
verifySetPrecision(coder, 7, 76.44);
verifySetPrecision(coder, 8, 19.11);
}
/**
* Verifies the value computed by {@link GeohashReferenceSystem.Coder#getPrecision()}
* for the given hash string length.
*/
private static void verifySetPrecision(final GeohashReferenceSystem.Coder coder,
final int length, final double precision) throws IncommensurableException
{
final Length atEquator = Quantities.create(precision, Units.METRE);
final Length atPole = Quantities.create(precision*B_A, Units.METRE);
coder.setPrecision(atEquator, null);
assertEquals(length, coder.getHashLength());
coder.setPrecision(atEquator, new DirectPosition2D(0, 0));
assertEquals(length, coder.getHashLength());
coder.setPrecision(atPole, new DirectPosition2D(0, 90));
assertEquals(length, coder.getHashLength());
/*
* Request a slightly finer precision at equator.
* It requires a longer hash code, except for the 2 first cases.
*/
coder.setPrecision(atPole, null);
assertEquals(length < 3 ? length : length+1, coder.getHashLength());
}
/**
* Tests the {@link GeohashReferenceSystem.Coder#encode(double, double)} method.
*
* @throws TransformException if an exception occurred while formatting the geohash.
*/
@Test
public void testEncode() throws TransformException {
final GeohashReferenceSystem.Coder coder = instance().createCoder();
for (final Place place : PLACES) {
assertEquals(place.geohash, coder.encode(place.latitude, place.longitude), place.name);
}
}
/**
* Tests the {@link GeohashReferenceSystem.Coder#encode(DirectPosition)} method.
*
* @throws TransformException if an exception occurred while formatting the geohash.
*/
@Test
public void testEncodePosition() throws TransformException {
final GeohashReferenceSystem.Coder coder = instance().createCoder();
final DirectPosition2D position = new DirectPosition2D(CommonCRS.WGS84.geographic());
for (final Place place : PLACES) {
position.x = place.latitude;
position.y = place.longitude;
assertEquals(place.geohash, coder.encode(position), place.name);
}
}
/**
* Tests the {@link GeohashReferenceSystem.Coder#decode(CharSequence)} method.
*
* @throws TransformException if an exception occurred while parsing the geohash.
*/
@Test
public void testDecode() throws TransformException {
testDecode(instance().createCoder(), 0, 1);
}
/**
* Tests the {@link GeohashReferenceSystem.Coder#decode(CharSequence)} method
* to a different target CRS than the default one.
*
* @throws TransformException if an exception occurred while parsing the geohash.
*/
@Test
public void testDecodeToCRS() throws TransformException {
testDecode(new GeohashReferenceSystem(GeohashReferenceSystem.Format.BASE32,
CommonCRS.WGS84.geographic()).createCoder(), 1, 0);
}
/**
* Implementation of {@link #testDecode()} and {@link #testDecodeToCRS()}.
*/
private void testDecode(final GeohashReferenceSystem.Coder coder, final int λi, final int φi) throws TransformException {
for (final Place place : PLACES) {
final AbstractLocation location = coder.decode(place.geohash);
final DirectPosition result = location.getPosition();
assertEquals(place.longitude, result.getOrdinatei), TOLERANCE, place.name);
assertEquals(place.latitude, result.getOrdinatei), TOLERANCE, place.name);
}
}
/**
* Verifies the metadata.
*
* @throws GazetteerException if an error occurred while creating the instance.
*/
@Test
public void verifyMetadata() throws GazetteerException {
final GeohashReferenceSystem rs = instance();
assertEquals("Mapping", rs.getTheme().toString(Locale.ENGLISH));
assertEquals("Cartographie", rs.getTheme().toString(Locale.FRENCH));
final AbstractLocationType type = TestUtilities.getSingleton(rs.getLocationTypes());
assertEquals("Geohash", type.getName().toString(Locale.ENGLISH));
assertEquals(0, type.getParents().size());
assertEquals(0, type.getChildren().size());
}
}