All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cpsolver.ifs.util.DistanceMetric Maven / Gradle / Ivy

Go to download

The constraint solver library contains a local search based framework that allows modeling of a problem using constraint programming primitives (variables, values, constraints).

The newest version!
package org.cpsolver.ifs.util;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.cpsolver.studentsct.constraint.HardDistanceConflicts;

/**
 * Common class for computing distances and back-to-back instructor / student conflicts.
 * 
 * When property Distances.Ellipsoid is set, the distances are computed using the given (e.g., WGS84, see {@link Ellipsoid}).
 * In the legacy mode (when ellipsoid is not set), distances are computed using Euclidian distance and 1 unit is considered 10 meters.
 * 

* For student back-to-back conflicts, Distances.Speed (in meters per minute) is considered and compared with the break time * of the earlier class. *

* For instructors, the preference is computed using the distance in meters and the three constants * Instructor.NoPreferenceLimit (distance <= limit → no preference), Instructor.DiscouragedLimit (distance <= limit → discouraged), * Instructor.ProhibitedLimit (distance <= limit → strongly discouraged), the back-to-back placement is prohibited when the distance is over the last limit. * * @author Tomas Muller * @version IFS 1.3 (Iterative Forward Search)
* Copyright (C) 2006 - 2014 Tomas Muller
* [email protected]
* http://muller.unitime.org
*
* This library 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 library 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 * Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public * License along with this library; if not see * http://www.gnu.org/licenses/. */ public class DistanceMetric { public static enum Ellipsoid { LEGACY ("Euclidean metric (1 unit equals to 10 meters)", "X-Coordinate", "Y-Coordinate", 0, 0, 0), WGS84 ("WGS-84 (GPS)", 6378137, 6356752.3142, 1.0 / 298.257223563), GRS80 ("GRS-80", 6378137, 6356752.3141, 1.0 / 298.257222101), Airy1830 ("Airy (1830)", 6377563.396, 6356256.909, 1.0 / 299.3249646), Intl1924 ("Int'l 1924", 6378388, 6356911.946, 1.0 / 297), Clarke1880 ("Clarke (1880)", 6378249.145, 6356514.86955, 1.0 / 293.465), GRS67 ("GRS-67", 6378160, 6356774.719, 1.0 / 298.25); private double iA, iB, iF; private String iName, iFirstCoord, iSecondCoord; Ellipsoid(String name, double a, double b) { this(name, "Latitude", "Longitude", a, b, (a - b) / a); } Ellipsoid(String name, double a, double b, double f) { this(name, "Latitude", "Longitude", a, b, f); } Ellipsoid(String name, String xCoord, String yCoord, double a, double b, double f) { iName = name; iFirstCoord = xCoord; iSecondCoord = yCoord; iA = a; iB = b; iF = f; } /** Major semiaxe A * @return major semiaxe A **/ public double a() { return iA; } /** Minor semiaxe B * @return major semiaxe B **/ public double b() { return iB; } /** Flattening (A-B) / A * @return Flattening (A-B) / A **/ public double f() { return iF; } /** Name of this coordinate system * @return elipsoid name **/ public String getEclipsoindName() { return iName; } /** Name of the fist coordinate (e.g., Latitude) * @return first coordinate's name **/ public String getFirstCoordinateName() { return iFirstCoord; } /** Name of the second coordinate (e.g., Longitude) * @return second coordinate's name **/ public String getSecondCoordinateName() { return iSecondCoord; } } /** Elliposid parameters, default to WGS-84 */ private Ellipsoid iModel = Ellipsoid.WGS84; /** Student speed in meters per minute (defaults to 1000 meters in 15 minutes) */ private double iSpeed = 1000.0 / 15; /** Back-to-back classes: maximal distance for no preference */ private double iInstructorNoPreferenceLimit = 0.0; /** Back-to-back classes: maximal distance for discouraged preference */ private double iInstructorDiscouragedLimit = 50.0; /** * Back-to-back classes: maximal distance for strongly discouraged preference * (everything above is prohibited) */ private double iInstructorProhibitedLimit = 200.0; /** * When Distances.ComputeDistanceConflictsBetweenNonBTBClasses is enabled, distance limit (in minutes) * for a long travel. */ private double iInstructorLongTravelInMinutes = 30.0; /** Default distance when given coordinates are null. */ private double iNullDistance = 10000.0; /** Maximal travel time in minutes when no coordinates are given. */ private int iMaxTravelTime = 60; /** Travel times overriding the distances computed from coordintaes */ private Map> iTravelTimes = new HashMap>(); /** Distance cache */ private Map iDistanceCache = new HashMap(); /** True if distances should be considered between classes that are NOT back-to-back */ private boolean iComputeDistanceConflictsBetweenNonBTBClasses = false; /** Reference of the accommodation of students that need short distances */ private String iShortDistanceAccommodationReference = "SD"; /** Allowed distance in minutes (for {@link HardDistanceConflicts}) */ private int iAllowedDistanceInMinutes = 30; /** Hard distance limit in minutes (for {@link HardDistanceConflicts}) */ private int iDistanceHardLimitInMinutes = 60; /** Long distance limit in minutes (for display) */ private int iDistanceLongLimitInMinutes = 60; /** Hard distance conflicts enabled (for {@link HardDistanceConflicts}) */ private boolean iHardDistanceConflicts = false; private final ReentrantReadWriteLock iLock = new ReentrantReadWriteLock(); /** Default properties */ public DistanceMetric() { } public DistanceMetric(DistanceMetric m) { iModel = m.iModel; iSpeed = m.iSpeed; iInstructorNoPreferenceLimit = m.iInstructorNoPreferenceLimit; iInstructorDiscouragedLimit = m.iInstructorDiscouragedLimit; iInstructorProhibitedLimit = m.iInstructorProhibitedLimit; iInstructorLongTravelInMinutes = m.iInstructorLongTravelInMinutes; iNullDistance = m.iNullDistance; iMaxTravelTime = m.iMaxTravelTime; iComputeDistanceConflictsBetweenNonBTBClasses = m.iComputeDistanceConflictsBetweenNonBTBClasses; iShortDistanceAccommodationReference = m.iShortDistanceAccommodationReference; iAllowedDistanceInMinutes = m.iAllowedDistanceInMinutes; iDistanceHardLimitInMinutes = m.iDistanceHardLimitInMinutes; iDistanceLongLimitInMinutes = m.iDistanceLongLimitInMinutes; iHardDistanceConflicts = m.iHardDistanceConflicts; m.iLock.readLock().lock(); try { for (Map.Entry> e: m.iTravelTimes.entrySet()) iTravelTimes.put(e.getKey(), new HashMap(e.getValue())); } finally { m.iLock.readLock().unlock(); } } /** With provided ellipsoid * @param model ellipsoid model **/ public DistanceMetric(Ellipsoid model) { iModel = model; if (iModel == Ellipsoid.LEGACY) { iSpeed = 100.0 / 15; iInstructorDiscouragedLimit = 5.0; iInstructorProhibitedLimit = 20.0; } } /** With provided ellipsoid and student speed * @param model ellipsoid model * @param speed student speed in meters per minute **/ public DistanceMetric(Ellipsoid model, double speed) { iModel = model; iSpeed = speed; } /** Configured using properties * @param properties solver configuration **/ public DistanceMetric(DataProperties properties) { if (Ellipsoid.LEGACY.name().equals(properties.getProperty("Distances.Ellipsoid",Ellipsoid.LEGACY.name()))) { //LEGACY MODE iModel = Ellipsoid.LEGACY; iSpeed = properties.getPropertyDouble("Student.DistanceLimit", 1000.0 / 15) / 10.0; iInstructorNoPreferenceLimit = properties.getPropertyDouble("Instructor.NoPreferenceLimit", 0.0); iInstructorDiscouragedLimit = properties.getPropertyDouble("Instructor.DiscouragedLimit", 5.0); iInstructorProhibitedLimit = properties.getPropertyDouble("Instructor.ProhibitedLimit", 20.0); iNullDistance = properties.getPropertyDouble("Distances.NullDistance", 1000.0); iMaxTravelTime = properties.getPropertyInt("Distances.MaxTravelDistanceInMinutes", 60); } else { iModel = Ellipsoid.valueOf(properties.getProperty("Distances.Ellipsoid", Ellipsoid.WGS84.name())); if (iModel == null) iModel = Ellipsoid.WGS84; iSpeed = properties.getPropertyDouble("Distances.Speed", properties.getPropertyDouble("Student.DistanceLimit", 1000.0 / 15)); iInstructorNoPreferenceLimit = properties.getPropertyDouble("Instructor.NoPreferenceLimit", iInstructorNoPreferenceLimit); iInstructorDiscouragedLimit = properties.getPropertyDouble("Instructor.DiscouragedLimit", iInstructorDiscouragedLimit); iInstructorProhibitedLimit = properties.getPropertyDouble("Instructor.ProhibitedLimit", iInstructorProhibitedLimit); iNullDistance = properties.getPropertyDouble("Distances.NullDistance", iNullDistance); iMaxTravelTime = properties.getPropertyInt("Distances.MaxTravelDistanceInMinutes", 60); } iComputeDistanceConflictsBetweenNonBTBClasses = properties.getPropertyBoolean( "Distances.ComputeDistanceConflictsBetweenNonBTBClasses", iComputeDistanceConflictsBetweenNonBTBClasses); iShortDistanceAccommodationReference = properties.getProperty( "Distances.ShortDistanceAccommodationReference", iShortDistanceAccommodationReference); iInstructorLongTravelInMinutes = properties.getPropertyDouble("Instructor.InstructorLongTravelInMinutes", 30.0); iAllowedDistanceInMinutes = properties.getPropertyInt("HardDistanceConflict.AllowedDistanceInMinutes", iAllowedDistanceInMinutes); iDistanceHardLimitInMinutes = properties.getPropertyInt("HardDistanceConflict.DistanceHardLimitInMinutes", iDistanceHardLimitInMinutes); iDistanceLongLimitInMinutes = properties.getPropertyInt("HardDistanceConflict.DistanceLongLimitInMinutes", iDistanceLongLimitInMinutes); iHardDistanceConflicts = properties.getPropertyBoolean("Sectioning.HardDistanceConflict", iHardDistanceConflicts); } /** Degrees to radians * @param deg degrees * @return radians **/ protected double deg2rad(double deg) { return deg * Math.PI / 180; } /** Compute distance between the two given coordinates * @param lat1 first coordinate's latitude * @param lon1 first coordinate's longitude * @param lat2 second coordinate's latitude * @param lon2 second coordinate's longitude * @return distance in meters * @deprecated Use @{link {@link DistanceMetric#getDistanceInMeters(Long, Double, Double, Long, Double, Double)} instead (to include travel time matrix when available). */ @Deprecated public double getDistanceInMeters(Double lat1, Double lon1, Double lat2, Double lon2) { if (lat1 == null || lat2 == null || lon1 == null || lon2 == null) return iNullDistance; if (lat1.equals(lat2) && lon1.equals(lon2)) return 0.0; // legacy mode -- euclidian distance, 1 unit is 10 meters if (iModel == Ellipsoid.LEGACY) { if (lat1 < 0 || lat2 < 0 || lon1 < 0 || lon2 < 0) return iNullDistance; double dx = lat1 - lat2; double dy = lon1 - lon2; return Math.sqrt(dx * dx + dy * dy); } String id = null; if (lat1 < lat2 || (lat1 == lat2 && lon1 <= lon2)) { id = Long.toHexString(Double.doubleToRawLongBits(lat1)) + Long.toHexString(Double.doubleToRawLongBits(lon1)) + Long.toHexString(Double.doubleToRawLongBits(lat2)) + Long.toHexString(Double.doubleToRawLongBits(lon2)); } else { id = Long.toHexString(Double.doubleToRawLongBits(lat1)) + Long.toHexString(Double.doubleToRawLongBits(lon1)) + Long.toHexString(Double.doubleToRawLongBits(lat2)) + Long.toHexString(Double.doubleToRawLongBits(lon2)); } iLock.readLock().lock(); try { Double distance = iDistanceCache.get(id); if (distance != null) return distance; } finally { iLock.readLock().unlock(); } iLock.writeLock().lock(); try { Double distance = iDistanceCache.get(id); if (distance != null) return distance; double a = iModel.a(), b = iModel.b(), f = iModel.f(); // ellipsoid params double L = deg2rad(lon2-lon1); double U1 = Math.atan((1-f) * Math.tan(deg2rad(lat1))); double U2 = Math.atan((1-f) * Math.tan(deg2rad(lat2))); double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1); double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2); double lambda = L, lambdaP, iterLimit = 100; double cosSqAlpha, cos2SigmaM, sinSigma, cosSigma, sigma, sinLambda, cosLambda; do { sinLambda = Math.sin(lambda); cosLambda = Math.cos(lambda); sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda)); if (sinSigma==0) return 0; // co-incident points cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda; sigma = Math.atan2(sinSigma, cosSigma); double sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; cosSqAlpha = 1 - sinAlpha*sinAlpha; cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha; if (Double.isNaN(cos2SigmaM)) cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (�6) double C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); lambdaP = lambda; lambda = L + (1-C) * f * sinAlpha * (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); } while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0); if (iterLimit==0) return Double.NaN; // formula failed to converge double uSq = cosSqAlpha * (a*a - b*b) / (b*b); double A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); double B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); double deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); // initial & final bearings // double fwdAz = Math.atan2(cosU2*sinLambda, cosU1*sinU2-sinU1*cosU2*cosLambda); // double revAz = Math.atan2(cosU1*sinLambda, -sinU1*cosU2+cosU1*sinU2*cosLambda); // s = s.toFixed(3); // round to 1mm precision distance = b*A*(sigma-deltaSigma); iDistanceCache.put(id, distance); return distance; } finally { iLock.writeLock().unlock(); } } /** * Compute distance in minutes. * Property Distances.Speed (in meters per minute) is used to convert meters to minutes, defaults to 1000 meters per 15 minutes (that means 66.67 meters per minute). * @param lat1 first coordinate's latitude * @param lon1 first coordinate's longitude * @param lat2 second coordinate's latitude * @param lon2 second coordinate's longitude * @return distance in minutes * @deprecated Use @{link {@link DistanceMetric#getDistanceInMinutes(Long, Double, Double, Long, Double, Double)} instead (to include travel time matrix when available). */ @Deprecated public int getDistanceInMinutes(double lat1, double lon1, double lat2, double lon2) { return (int) Math.round(getDistanceInMeters(lat1, lon1, lat2, lon2) / iSpeed); } /** * Converts minutes to meters. * Property Distances.Speed (in meters per minute) is used, defaults to 1000 meters per 15 minutes. * @param min minutes to travel * @return meters to travel */ public double minutes2meters(int min) { return iSpeed * min; } /** Back-to-back classes in rooms within this limit have neutral preference * @return limit in meters **/ public double getInstructorNoPreferenceLimit() { return iInstructorNoPreferenceLimit; } /** Back-to-back classes in rooms within this limit have discouraged preference * @return limit in meters **/ public double getInstructorDiscouragedLimit() { return iInstructorDiscouragedLimit; } /** Back-to-back classes in rooms within this limit have strongly discouraged preference, it is prohibited to exceed this limit. * @return limit in meters **/ public double getInstructorProhibitedLimit() { return iInstructorProhibitedLimit; } /** * When Distances.ComputeDistanceConflictsBetweenNonBTBClasses is enabled, distance limit (in minutes) * for a long travel. * @return travel time in minutes */ public double getInstructorLongTravelInMinutes() { return iInstructorLongTravelInMinutes; } /** True if legacy mode is used (Euclidian distance where 1 unit is 10 meters) * @return true if the ellipsoid model is the old one **/ public boolean isLegacy() { return iModel == Ellipsoid.LEGACY; } /** Maximal travel distance between rooms when no coordinates are given * @return travel time in minutes **/ public int getMaxTravelDistanceInMinutes() { return iMaxTravelTime; } /** Set maximal travel distance between rooms when no coordinates are given * @param maxTravelTime max travel time in minutes */ public void setMaxTravelDistanceInMinutes(int maxTravelTime) { iMaxTravelTime = maxTravelTime; } /** Add travel time between two locations * @param roomId1 first room's id * @param roomId2 second room's id * @param travelTimeInMinutes travel time in minutes **/ public void addTravelTime(Long roomId1, Long roomId2, Integer travelTimeInMinutes) { iLock.writeLock().lock(); try { if (roomId1 == null || roomId2 == null) return; if (roomId1 < roomId2) { Map times = iTravelTimes.get(roomId1); if (times == null) { times = new HashMap(); iTravelTimes.put(roomId1, times); } if (travelTimeInMinutes == null) times.remove(roomId2); else times.put(roomId2, travelTimeInMinutes); } else { Map times = iTravelTimes.get(roomId2); if (times == null) { times = new HashMap(); iTravelTimes.put(roomId2, times); } if (travelTimeInMinutes == null) times.remove(roomId1); else times.put(roomId1, travelTimeInMinutes); } } finally { iLock.writeLock().unlock(); } } /** Return travel time between two locations. * @param roomId1 first room's id * @param roomId2 second room's id * @return travel time in minutes **/ public Integer getTravelTimeInMinutes(Long roomId1, Long roomId2) { iLock.readLock().lock(); try { if (roomId1 == null || roomId2 == null) return null; if (roomId1 < roomId2) { Map times = iTravelTimes.get(roomId1); return (times == null ? null : times.get(roomId2)); } else { Map times = iTravelTimes.get(roomId2); return (times == null ? null : times.get(roomId1)); } } finally { iLock.readLock().unlock(); } } /** Return travel time between two locations. Travel times are used when available, use coordinates otherwise. * @param roomId1 first room's id * @param lat1 first room's latitude * @param lon1 first room's longitude * @param roomId2 second room's id * @param lat2 second room's latitude * @param lon2 second room's longitude * @return distance in minutes **/ public Integer getDistanceInMinutes(Long roomId1, Double lat1, Double lon1, Long roomId2, Double lat2, Double lon2) { Integer distance = getTravelTimeInMinutes(roomId1, roomId2); if (distance != null) return distance; if (lat1 == null || lat2 == null || lon1 == null || lon2 == null) return getMaxTravelDistanceInMinutes(); else return (int) Math.min(getMaxTravelDistanceInMinutes(), Math.round(getDistanceInMeters(lat1, lon1, lat2, lon2) / iSpeed)); } /** Return travel distance between two locations. Travel times are used when available, use coordinates otherwise * @param roomId1 first room's id * @param lat1 first room's latitude * @param lon1 first room's longitude * @param roomId2 second room's id * @param lat2 second room's latitude * @param lon2 second room's longitude * @return distance in meters **/ public double getDistanceInMeters(Long roomId1, Double lat1, Double lon1, Long roomId2, Double lat2, Double lon2) { Integer distance = getTravelTimeInMinutes(roomId1, roomId2); if (distance != null) return minutes2meters(distance); return getDistanceInMeters(lat1, lon1, lat2, lon2); } /** Return travel times matrix * @return travel times matrix **/ public Map> getTravelTimes() { return iTravelTimes; } /** * True if distances should be considered between classes that are NOT back-to-back. Distance in minutes is then * to be compared with the difference between end of the last class and start of the second class plus break time of the first class. * @return true if distances should be considered between classes that are NOT back-to-back **/ public boolean doComputeDistanceConflictsBetweenNonBTBClasses() { return iComputeDistanceConflictsBetweenNonBTBClasses; } public void setComputeDistanceConflictsBetweenNonBTBClasses(boolean computeDistanceConflictsBetweenNonBTBClasses) { iComputeDistanceConflictsBetweenNonBTBClasses = computeDistanceConflictsBetweenNonBTBClasses; } /** * Reference of the accommodation of students that need short distances */ public String getShortDistanceAccommodationReference() { return iShortDistanceAccommodationReference; } /** Allowed distance in minutes (for {@link HardDistanceConflicts}) */ public int getAllowedDistanceInMinutes() { return iAllowedDistanceInMinutes; } /** Hard distance limit in minutes (for {@link HardDistanceConflicts}) */ public int getDistanceHardLimitInMinutes() { return iDistanceHardLimitInMinutes; } /** Long distance limit in minutes (for display) */ public int getDistanceLongLimitInMinutes() { return iDistanceLongLimitInMinutes; } /** Hard distance conflicts enabled (for {@link HardDistanceConflicts}) */ public boolean isHardDistanceConflictsEnabled() { return iHardDistanceConflicts; } /** Few tests * @param args program arguments **/ public static void main(String[] args) { System.out.println("Distance between Prague and Zlin: " + new DistanceMetric().getDistanceInMeters(50.087661, 14.420535, 49.226736, 17.668856) / 1000.0 + " km"); System.out.println("Distance between ENAD and PMU: " + new DistanceMetric().getDistanceInMeters(40.428323, -86.912785, 40.425078, -86.911474) + " m"); System.out.println("Distance between ENAD and ME: " + new DistanceMetric().getDistanceInMeters(40.428323, -86.912785, 40.429338, -86.91267) + " m"); System.out.println("Distance between Prague and Zlin: " + new DistanceMetric().getDistanceInMinutes(50.087661, 14.420535, 49.226736, 17.668856) / 60 + " hours"); System.out.println("Distance between ENAD and PMU: " + new DistanceMetric().getDistanceInMinutes(40.428323, -86.912785, 40.425078, -86.911474) + " minutes"); System.out.println("Distance between ENAD and ME: " + new DistanceMetric().getDistanceInMinutes(40.428323, -86.912785, 40.429338, -86.91267) + " minutes"); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy