/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.routing.core;
import java.util.Comparator;
import java.util.List;
import org.opentripplanner.routing.graph.Vertex;
/**
* A Comparator implementation that will sort a 1D list of Vertex objects such that
* 2D spatial locality is mostly preserved. It implicitly compares the Morton code for
* the vertex's coordinates in a roughly area-preserving (sinusoidal) projection.
*
* @author andrewbyrd
*/
public class MortonVertexComparator implements Comparator<Vertex> {
private static final double METERS_PER_DEGREE_LAT = 111111.111111;
private double meters_per_degree_lon;
private double minLon;
private double refLat;
public MortonVertexComparator() {
minLon = -180;
setRefLat(45);
}
public MortonVertexComparator(List<Vertex> domain) {
// Recenter the projection near the area of interest.
// Comparator handles negative numbers poorly, so must scan for minimum
// longitude.
setRefLat(domain.get(0).getY());
minLon = Double.POSITIVE_INFINITY;
for (Vertex v : domain)
if (v.getX() < minLon)
minLon = v.getX();
}
/* Specify the reference latitude for the projection.
* It is tempting to scale the x units using avg latitude of the two points during each
* comparison, but this will cause a vertex's projected x coordinate to vary slightly
* depending on which other vertex it is compared to. This noise ruins the comparison.
*/
private void setRefLat(double lat) {
refLat = lat;
meters_per_degree_lon = METERS_PER_DEGREE_LAT * Math.cos(lat * Math.PI / 180.0);
}
@Override
public int compare(Vertex v0, Vertex v1) {
double lon0 = v0.getX() - minLon;
double lat0 = v0.getY();
double lon1 = v1.getX() - minLon;
double lat1 = v1.getY();
long x0 = (long) Math.abs(lon0 * meters_per_degree_lon);
long y0 = (long) Math.abs(lat0 * METERS_PER_DEGREE_LAT);
long x1 = (long) Math.abs(lon1 * meters_per_degree_lon);
long y1 = (long) Math.abs(lat1 * METERS_PER_DEGREE_LAT);
// mask higher order bits that are identical
long dx = x0 ^ x1;
long dy = y0 ^ y1;
// determine which dimension has the most significant bit
if (dy < dx && (dy < (dx ^ dy))) {
return (int) (x0 - x1);
} else {
return (int) (y0 - y1);
}
}
}