Author: desruisseaux
Date: Wed Sep 6 00:15:06 2017
New Revision: 1807432
URL: http://svn.apache.org/viewvc?rev=1807432&view=rev
Log:
Initial commit of the code we used for benchmarking Apache SIS and Proj.4.
Added:
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/CoordinateOperationComparator.java
- copied, changed from r1807431,
sis/release-test/maven/src/main/java/org/apache/sis/test/Integration.java
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java
(with props)
Removed:
sis/release-test/maven/src/main/java/org/apache/sis/test/Integration.java
Copied:
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/CoordinateOperationComparator.java
(from r1807431,
sis/release-test/maven/src/main/java/org/apache/sis/test/Integration.java)
URL:
http://svn.apache.org/viewvc/sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/CoordinateOperationComparator.java?p2=sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/CoordinateOperationComparator.java&p1=sis/release-test/maven/src/main/java/org/apache/sis/test/Integration.java&r1=1807431&r2=1807432&rev=1807432&view=diff
==============================================================================
--- sis/release-test/maven/src/main/java/org/apache/sis/test/Integration.java
(original)
+++
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/CoordinateOperationComparator.java
Wed Sep 6 00:15:06 2017
@@ -14,16 +14,399 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.sis.test;
+package org.apache.sis.test.referencing;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Random;
+import org.opengis.geometry.Envelope;
+import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.IdentifiedObject;
+import org.opengis.referencing.crs.GeographicCRS;
+import org.opengis.referencing.operation.*;
+import org.opengis.util.FactoryException;
+import org.apache.sis.geometry.Envelopes;
+import org.apache.sis.geometry.GeneralEnvelope;
+import org.apache.sis.io.wkt.Convention;
+import org.apache.sis.math.Statistics;
+import org.apache.sis.metadata.iso.citation.Citations;
+import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
+import org.apache.sis.metadata.iso.extent.Extents;
+import org.apache.sis.internal.util.StandardDateFormat;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.IdentifiedObjects;
+import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
+import org.apache.sis.referencing.operation.transform.AbstractMathTransform;
+import org.apache.sis.internal.metadata.ReferencingServices;
+import org.apache.sis.storage.gdal.Proj4;
/**
- * Integration tests (empty for now).
+ * Compares coordinate operations performed with Apache SIS and {@literal
Proj.4}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since 0.8
+ * @module
*/
-public final class Integration {
+public final class CoordinateOperationComparator {
+ public static void main(String[] args) throws Exception {
+ for (int n = 0; ; n++) {
+ final CoordinateOperationComparator c;
+ switch (n) {
+ case 0: c = new CoordinateOperationComparator("Cylindrical
Aqual Area (Spherical)", 4053, 3410); break;
+ case 1: c = new CoordinateOperationComparator("Cylindrical
Aqual Area", 4326, 6933); break;
+ case 2: c = new
CoordinateOperationComparator("Pseudo-Mercator", 4326,
3857); break;
+ case 3: c = new CoordinateOperationComparator("Mercator",
4326, 3395); break;
+ case 4: c = new CoordinateOperationComparator("Lambert Conic
Conformal", 4269, 3978); break;
+ case 5: c = new CoordinateOperationComparator("Polar
stereographic", 4326, 3031); break;
+ case 6: c = new CoordinateOperationComparator("Albert Equal
Area", 4269, 5070); break;
+ case 7: c = new CoordinateOperationComparator("Mercator 41
to Mercator", 3994, 3395); break;
+ case 8: c = new CoordinateOperationComparator("Tokyo to
JGD2000", 4301, 4612); break;
+ case 9: c = new CoordinateOperationComparator("Tokyo to
JGD2000 in UTM zone 54", 3095, 3100); break;
+ case 10: c = new CoordinateOperationComparator("OSGB 1936 to
ED50 (UKOOA)", 4277, 4230); break;
+ case 11: c = new CoordinateOperationComparator("Martinique
1938 to RGAF09", 4625, 5489); break;
+ case 12: c = new CoordinateOperationComparator("Stereographic
to stereographic", 2986, 7082); break;
+ default: return;
+ }
+ c.run();
+ }
+ }
+
+ /**
+ * Number of dimensions in all coordinates to be used for the tests.
+ */
+ private static final int DIM = 2;
+
+ /**
+ * Number of test coordinates along <var>x</var> and <var>y</var> axes
(must be a power of 2).
+ * The total number of coordinates used in each iteration will the the
square of this value.
+ */
+ private static final int NUM_PTS = 256;
+
+ /**
+ * Number of "transform projection - inverse projection" cycles to do.
+ * GIGS (Geospatial Integrity of Geoscience Applications) recommends 100
iterations.
+ */
+ private static final int NUM_LOOPS = 100;
+
+ /**
+ * Number of cycles to skip (for HotSpot warmup) before to collect
statistics about performance.
+ * Note that statistics about accuracy are collected for all iterations,
regardless this value.
+ */
+ private static final int WARMUP_LOOPS = 10;
+
+ /**
+ * The coordinates to transform as (xâ,yâ), (xâ,yâ), (xâ,yâ),
(xâ,yâ), <i>etc.</i> tupples.
+ * This array will be initialized by {@link #randomCoordinates()}, then
never modified.
+ */
+ private final double[] inputs;
+
+ /**
+ * The array where to store results of coordinate operations.
+ * This array will be overwritten at each iteration.
+ */
+ private final double[] outputs;
+
+ /**
+ * {@code true} if the CRS is geographic, or {@code false} if the CRS is
projected.
+ * If {@code true}, then distances will be estimated using the nautical
mile as an approximation.
+ *
+ * @see #distance(boolean, double, double)
+ */
+ private final boolean isSourceGeographic, isTargetGeographic;
+
+ /**
+ * The coordinate operation defined by EPSG as provided by Apache SIS.
This is the most authoritative
+ * source of information that we have, since Apache SIS follows more
closely EPSG definitions than many
+ * other map projection libraries. However, axis order may be different
than the order expected by other
+ * implementations like Proj.4.
+ */
+ private final CoordinateOperation authoritative;
+
+ /**
+ * The coordinate operations to compare. The first operation should be the
same than {@link #authoritative},
+ * but with (<var>longitude</var>, <var>latitude</var>) axis order. We use
that axis order for avoiding
+ * complications with the use of Proj.4 or similar libraries.
+ */
+ private final CoordinateOperation[] normalized;
+
+ /**
+ * Where to write benchmark results.
+ */
+ private final PrintWriter out;
+
+ /**
+ * Creates a comparator for coordinate operations between the given pair
of EPSG codes.
+ * Current implementations creates operations for Apache SIS and Proj.4
libraries,
+ * but this list may be expanded in any future version.
+ *
+ * @param outputFile name of the file where to write results, or {@code
null}Â for standard output.
+ * If non-null, a {@code ".txt"} extension will be
added.
+ * @param source EPSG code of source CRS.
+ * @param target EPSG code of target CRS.
+ * @throws IOException if an error occurred while creating the
output file.
+ * @throws FactoryException if an error occurred while instantiating
CRS or coordinate operation.
+ * @throws TransformException if an error occurred while transforming
coordinates.
+ */
+ public CoordinateOperationComparator(final String outputFile, final int
source, final int target)
+ throws IOException, FactoryException, TransformException
+ {
+ this(outputFile,
+ CRS.findOperation( CRS.forCode( "EPSG:" + source),
+ CRS.forCode( "EPSG:" + target),
null),
+ Proj4.createOperation(Proj4.createCRS("+init=epsg:" + source + "
+over", 2),
+ Proj4.createCRS("+init=epsg:" + target + "
+over", 2), true));
+ }
+
+ /**
+ * Creates a comparator for all the given coordinate operations.
+ * The first coordinate operation shall be the authoritative one as
defined by EPSG,
+ * including <cite>domain of validity</cite> and <cite>positional
accuracy</cite> metadata.
+ * This is usually the operation created by Apache SIS. All other
operations are implemented by other libraries.
+ * All operations except the first (authoritative) one shall use the
(<var>longitude</var>, <var>latitude</var>)
+ * axis order, for simpler comparisons with Proj.4 and similar libraries.
+ *
+ * @param operations the operations to compare. The first
operation shall be the authoritative one.
+ * @throws FactoryException if an error occurred while creating a
coordinate operation.
+ * @throws TransformException if an error occurred while computing the
domain of validity.
+ */
+ @SuppressWarnings("UseOfSystemOutOrSystemErr")
+ private CoordinateOperationComparator(final String outputFile,
CoordinateOperation... operations)
+ throws IOException, FactoryException, TransformException
+ {
+ operations = operations.clone();
+ authoritative = operations[0];
+ final AbstractCRS sourceCRS =
AbstractCRS.castOrCopy(authoritative.getSourceCRS());
+ final AbstractCRS targetCRS =
AbstractCRS.castOrCopy(authoritative.getTargetCRS());
+ final AbstractCRS normalizedSource =
sourceCRS.forConvention(AxesConvention.NORMALIZED);
+ final AbstractCRS normalizedTarget =
targetCRS.forConvention(AxesConvention.NORMALIZED);
+ operations[0] = CRS.findOperation(normalizedSource,
normalizedTarget, null);
+ isSourceGeographic = (normalizedSource instanceof GeographicCRS);
+ isTargetGeographic = (normalizedTarget instanceof GeographicCRS);
+ normalized = operations;
+ /*
+ * Get the operation domain of validity in the units of source CRS,
+ * then print a summary about the comparison we are about to do.
+ */
+ GeographicBoundingBox bbox =
CRS.getGeographicBoundingBox(authoritative);
+ bbox = Extents.intersection(bbox, new
DefaultGeographicBoundingBox(-179, 179, -89, 89));
+ final Envelope domain = Envelopes.transform(new GeneralEnvelope(bbox),
normalizedSource);
+ out = (outputFile != null) ? new PrintWriter(new FileWriter(outputFile
+ ".txt")) : new PrintWriter(System.out);
+ print("Source CRS", sourceCRS);
+ print("Target CRS", targetCRS);
+ print("Operation", authoritative);
+ print("Method", method(authoritative));
+ out.println("Domain:\t" + domain);
+ /*
+ * Fills the input array with random coordinates.
+ * This array shall not be modified after construction.
+ */
+ final Random random = new Random();
+ inputs = new double[NUM_PTS * NUM_PTS * DIM];
+ final double minX = domain.getMinimum(0);
+ final double minY = domain.getMinimum(1);
+ final double scaleX = domain.getSpan(0) / NUM_PTS;
+ final double scaleY = domain.getSpan(1) / NUM_PTS;
+ int n = 0;
+ for (int j=0; j<NUM_PTS; j++) {
+ for (int i=0; i<NUM_PTS; i++) {
+ inputs[n++] = minX + scaleX * (i + random.nextDouble() - 0.5);
+ inputs[n++] = minY + scaleY * (j + random.nextDouble() - 0.5);
+ }
+ }
+ assert n == inputs.length : n;
+ for (int i = inputs.length; (i -= DIM) > 0;) {
+ final int j = random.nextInt(i) & ~1;
+ double x = inputs[i ];
+ double y = inputs[i+1];
+ inputs[i ] = inputs[j ];
+ inputs[i+1] = inputs[j+1];
+ inputs[j ] = x;
+ inputs[j+1] = y;
+ }
+ outputs = new double[inputs.length];
+ }
+
+ /**
+ * Prints the name of the given identified objects.
+ *
+ * @param label label to write before the identified object.
+ * @param object object for which to write the name.
+ */
+ private void print(final String label, final IdentifiedObject object) {
+ out.printf("%s:\t%s\t%s%n", label,
IdentifiedObjects.toString(IdentifiedObjects.getIdentifier(object,
Citations.EPSG)), object.getName().getCode());
+ }
+
+ /**
+ * Get the operation method of the given coordinate operation.
+ * This is used for information purpose only.
+ */
+ private static OperationMethod method(final CoordinateOperation op) {
+ if (op instanceof SingleOperation) {
+ return ((SingleOperation) op).getMethod();
+ }
+ if (op instanceof ConcatenatedOperation) {
+ for (CoordinateOperation c : ((ConcatenatedOperation)
op).getOperations()) {
+ OperationMethod method = method(c);
+ if (method != null) return method;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Runs the benchmarks. First, this method compare operation results
without measuring performance.
+ * Then, this method performs "forward operation" followed by "inverse
operation" one hundred times,
+ * measuring performances and drifts at each iteration.
+ *
+ * @throws TransformException if an error occurred while transforming
coordinates.
+ * @throws IOException if an error occurred while writing results.
+ */
+ public void run() throws TransformException, IOException {
+ compareOperationResults();
+ for (final CoordinateOperation op : normalized) {
+ measure(op);
+ }
+ if (out.checkError()) {
+ throw new IOException("Error while writing results file.");
+ }
+ }
+
+ /**
+ * Compares the coordinate operation results of Apache SIS with other
implementations.
+ */
+ private void compareOperationResults() throws TransformException {
+ final int numPts = inputs.length / DIM;
+ normalized[0].getMathTransform().transform(inputs, 0, outputs, 0,
numPts);
+ final Statistics[] stats = new Statistics[normalized.length - 1];
+ for (int i=0; i<stats.length; i++) {
+ stats[i] = new Statistics("Difference");
+ }
+ out.println();
+ final double[] intermediate = new double[inputs.length];
+ for (int j=0; j<stats.length; j++) {
+ final Statistics s = stats[j];
+ normalized[j+1].getMathTransform().transform(inputs, 0,
intermediate, 0, numPts);
+ for (int i = intermediate.length; (i -= DIM) >= 0;) {
+ s.accept(distance(isTargetGeographic, outputs, intermediate,
i));
+ }
+ }
+ out.print("Difference in projection results (metres):");
+ for (final Statistics s : stats) {
+ out.printf("\t%g\t±%g", s.mean(), s.standardDeviation(false));
+ }
+ out.println();
+ }
+
+ /**
+ * Measures performance and drift of the given coordinate operation.
+ */
+ private void measure(final CoordinateOperation op) throws
TransformException {
+ System.arraycopy(inputs, 0, outputs, 0, inputs.length);
+ final int numPts = inputs.length / DIM;
+ final MathTransform tr = op.getMathTransform();
+ final MathTransform inverse = tr.inverse();
+ final Statistics drift = new Statistics("Drift");
+ final Statistics forwardTime = new Statistics("Forward time
(ms)");
+ final Statistics inverseTime = new Statistics("Inverse time
(ms)");
+ final Statistics cumulatedTime = new Statistics("Cumulated time
(ms)");
+ out.println();
+
out.println("ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ");
+ out.println("Measuring drift after " + NUM_LOOPS + " iterations for:");
+ out.println();
+ out.println(tr);
+ if (tr instanceof AbstractMathTransform) {
+ out.println();
+ out.println("Internal:");
+ out.println(((AbstractMathTransform)
tr).toString(Convention.INTERNAL));
+ }
+ out.println();
+ out.println("Mean\tStd. dev.\tMinimum\tMaximum\tForward time
(ms)\tInverse time (ms)");
+ for (int n=0; n<NUM_LOOPS; n++) {
+ final long t0 = System.nanoTime();
+ tr.transform(outputs, 0, outputs, 0, numPts);
+ final long t1 = System.nanoTime();
+ inverse.transform(outputs, 0, outputs, 0, numPts);
+ final long t2 = System.nanoTime();
+ collectDifferences(drift);
+ final double tf = (t1 - t0) / (double)
StandardDateFormat.NANOS_PER_MILLISECOND;
+ final double ti = (t2 - t1) / (double)
StandardDateFormat.NANOS_PER_MILLISECOND;
+ final double tc = (t2 - t0) / (double)
StandardDateFormat.NANOS_PER_MILLISECOND;
+ out.printf("%g\t%g\t%g\t%g\t%g\t%g%n", drift.mean(),
drift.standardDeviation(false), drift.minimum(), drift.maximum(), tf, ti);
+ if (n >= WARMUP_LOOPS) {
+ forwardTime .accept(tf);
+ inverseTime .accept(ti);
+ cumulatedTime.accept(tc);
+ }
+ drift.reset();
+ }
+ out.printf("Average execution time (forward):\t%g\t± %g%n",
forwardTime.mean(), inverseTime.standardDeviation(false));
+ out.printf("Average execution time (inverse):\t%g\t± %g%n",
inverseTime.mean(), inverseTime.standardDeviation(false));
+ out.printf("Average execution time (cumulated):\t%g\t± %g%n",
cumulatedTime.mean(), cumulatedTime.standardDeviation(false));
+ out.println();
+ out.println("Performance (ms):");
+ out.println("Block size\tForward\tStd. dev.\tInverse\tStd.
dev.\tCumulated\tStd. dev.");
+ for (int size = inputs.length / DIM; size >= 1; size /= 2) {
+ forwardTime.reset();
+ inverseTime.reset();
+ cumulatedTime.reset();
+ for (int i=0; i<10; i++) {
+ System.arraycopy(inputs, 0, outputs, 0, inputs.length);
+ long tf = 0, ti = 0, tc = 0;
+ for (int offset = 0; offset < inputs.length; offset += size *
DIM) {
+ final long t0 = System.nanoTime();
+ tr.transform(outputs, offset, outputs, offset, size);
+ final long t1 = System.nanoTime();
+ inverse.transform(outputs, offset, outputs, offset, size);
+ final long t2 = System.nanoTime();
+ tf += (t1 - t0);
+ ti += (t2 - t1);
+ tc += (t2 - t0);
+ }
+ forwardTime .accept(tf / (double)
StandardDateFormat.NANOS_PER_MILLISECOND);
+ inverseTime .accept(ti / (double)
StandardDateFormat.NANOS_PER_MILLISECOND);
+ cumulatedTime.accept(tc / (double)
StandardDateFormat.NANOS_PER_MILLISECOND);
+ }
+ out.printf("%d\t%g\t%g\t%g\t%g\t%g\t%g\n", size,
+ forwardTime.mean(), forwardTime.standardDeviation(false),
+ inverseTime.mean(), inverseTime.standardDeviation(false),
+ cumulatedTime.mean(),
cumulatedTime.standardDeviation(false));
+ collectDifferences(drift);
+ }
+ out.printf("Verification: average difference = %g and maximal
difference = %g%n", drift.mean(), drift.maximum());
+ }
+
+ /**
+ * Adds distances between {@link #inputs} (expected coordinates) and
{@link #outputs}Â (actual coordinates) to
+ * the given statistics. This method is invoked after a full "forward
operation - inverse operation" cycle.
+ *
+ * @param drift where to add statistics about distances between expected
and actual coordinates.
+ */
+ private void collectDifferences(final Statistics drift) {
+ for (int i = inputs.length; (i -= DIM) >= 0;) {
+ drift.accept(distance(isSourceGeographic, inputs, outputs, i));
+ }
+ }
+
/**
- * Do not allow instantiation of this class.
+ * Returns an estimation of the distance between expected and actual
coordinates.
+ *
+ * <p><strong>NOTE:</strong> this method assumes that {@link #DIM} is
equal to 2.
+ * If this is no longer the case, replace {@link Math#hypot(double,
double)} call
+ * by {@link org.apache.sis.math.MathFunctions#magnitude(double...)}.</p>
+ *
+ * @param expected array of expected coordinates.
+ * @param actual array of actual coordinates.
+ * @param i index of the first ordinal value in {@code expected}
and {@code actual} arrays.
*/
- private Integration() {
+ private static double distance(final boolean isGeographic, final double[]
expected, final double[] actual, int i) {
+ double d = Math.hypot(actual[i] - expected[i], actual[++i] -
expected[i]);
+ if (isGeographic) {
+ d *= ReferencingServices.NAUTICAL_MILE * 60;
+ }
+ return d;
}
}
Added:
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java
URL:
http://svn.apache.org/viewvc/sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java?rev=1807432&view=auto
==============================================================================
---
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java
(added)
+++
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java
[UTF-8] Wed Sep 6 00:15:06 2017
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+
+/**
+ * Compares Apache SIS with other map projection libraries.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @version 0.8
+ * @since 0.8
+ * @module
+ */
+package org.apache.sis.test.referencing;
Propchange:
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sis/release-test/maven/src/main/java/org/apache/sis/test/referencing/package-info.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8