001package org.cpsolver.ifs.util; 002 003import java.util.HashMap; 004import java.util.Map; 005import java.util.concurrent.locks.ReentrantReadWriteLock; 006 007import org.cpsolver.studentsct.constraint.HardDistanceConflicts; 008 009/** 010 * Common class for computing distances and back-to-back instructor / student conflicts. 011 * 012 * When property Distances.Ellipsoid is set, the distances are computed using the given (e.g., WGS84, see {@link Ellipsoid}). 013 * In the legacy mode (when ellipsoid is not set), distances are computed using Euclidian distance and 1 unit is considered 10 meters. 014 * <br><br> 015 * For student back-to-back conflicts, Distances.Speed (in meters per minute) is considered and compared with the break time 016 * of the earlier class. 017 * <br><br> 018 * For instructors, the preference is computed using the distance in meters and the three constants 019 * Instructor.NoPreferenceLimit (distance <= limit → no preference), Instructor.DiscouragedLimit (distance <= limit → discouraged), 020 * Instructor.ProhibitedLimit (distance <= limit → strongly discouraged), the back-to-back placement is prohibited when the distance is over the last limit. 021 * 022 * @author Tomáš Müller 023 * @version IFS 1.3 (Iterative Forward Search)<br> 024 * Copyright (C) 2006 - 2014 Tomáš Müller<br> 025 * <a href="mailto:muller@unitime.org">muller@unitime.org</a><br> 026 * <a href="http://muller.unitime.org">http://muller.unitime.org</a><br> 027 * <br> 028 * This library is free software; you can redistribute it and/or modify 029 * it under the terms of the GNU Lesser General Public License as 030 * published by the Free Software Foundation; either version 3 of the 031 * License, or (at your option) any later version. <br> 032 * <br> 033 * This library is distributed in the hope that it will be useful, but 034 * WITHOUT ANY WARRANTY; without even the implied warranty of 035 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 036 * Lesser General Public License for more details. <br> 037 * <br> 038 * You should have received a copy of the GNU Lesser General Public 039 * License along with this library; if not see 040 * <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>. 041 */ 042public class DistanceMetric { 043 public static enum Ellipsoid { 044 LEGACY ("Euclidean metric (1 unit equals to 10 meters)", "X-Coordinate", "Y-Coordinate", 0, 0, 0), 045 WGS84 ("WGS-84 (GPS)", 6378137, 6356752.3142, 1.0 / 298.257223563), 046 GRS80 ("GRS-80", 6378137, 6356752.3141, 1.0 / 298.257222101), 047 Airy1830 ("Airy (1830)", 6377563.396, 6356256.909, 1.0 / 299.3249646), 048 Intl1924 ("Int'l 1924", 6378388, 6356911.946, 1.0 / 297), 049 Clarke1880 ("Clarke (1880)", 6378249.145, 6356514.86955, 1.0 / 293.465), 050 GRS67 ("GRS-67", 6378160, 6356774.719, 1.0 / 298.25); 051 052 private double iA, iB, iF; 053 private String iName, iFirstCoord, iSecondCoord; 054 055 Ellipsoid(String name, double a, double b) { 056 this(name, "Latitude", "Longitude", a, b, (a - b) / a); 057 } 058 Ellipsoid(String name, double a, double b, double f) { 059 this(name, "Latitude", "Longitude", a, b, f); 060 } 061 Ellipsoid(String name, String xCoord, String yCoord, double a, double b, double f) { 062 iName = name; 063 iFirstCoord = xCoord; iSecondCoord = yCoord; 064 iA = a; iB = b; iF = f; 065 } 066 067 /** Major semiaxe A 068 * @return major semiaxe A 069 **/ 070 public double a() { return iA; } 071 /** Minor semiaxe B 072 * @return major semiaxe B 073 **/ 074 public double b() { return iB; } 075 /** Flattening (A-B) / A 076 * @return Flattening (A-B) / A 077 **/ 078 public double f() { return iF; } 079 080 /** Name of this coordinate system 081 * @return elipsoid name 082 **/ 083 public String getEclipsoindName() { return iName; } 084 /** Name of the fist coordinate (e.g., Latitude) 085 * @return first coordinate's name 086 **/ 087 public String getFirstCoordinateName() { return iFirstCoord; } 088 /** Name of the second coordinate (e.g., Longitude) 089 * @return second coordinate's name 090 **/ 091 public String getSecondCoordinateName() { return iSecondCoord; } 092 } 093 094 /** Elliposid parameters, default to WGS-84 */ 095 private Ellipsoid iModel = Ellipsoid.WGS84; 096 /** Student speed in meters per minute (defaults to 1000 meters in 15 minutes) */ 097 private double iSpeed = 1000.0 / 15; 098 /** Back-to-back classes: maximal distance for no preference */ 099 private double iInstructorNoPreferenceLimit = 0.0; 100 /** Back-to-back classes: maximal distance for discouraged preference */ 101 private double iInstructorDiscouragedLimit = 50.0; 102 /** 103 * Back-to-back classes: maximal distance for strongly discouraged preference 104 * (everything above is prohibited) 105 */ 106 private double iInstructorProhibitedLimit = 200.0; 107 /** 108 * When Distances.ComputeDistanceConflictsBetweenNonBTBClasses is enabled, distance limit (in minutes) 109 * for a long travel. 110 */ 111 private double iInstructorLongTravelInMinutes = 30.0; 112 113 /** Default distance when given coordinates are null. */ 114 private double iNullDistance = 10000.0; 115 /** Maximal travel time in minutes when no coordinates are given. */ 116 private int iMaxTravelTime = 60; 117 /** Travel times overriding the distances computed from coordintaes */ 118 private Map<Long, Map<Long, Integer>> iTravelTimes = new HashMap<Long, Map<Long,Integer>>(); 119 /** Distance cache */ 120 private Map<String, Double> iDistanceCache = new HashMap<String, Double>(); 121 /** True if distances should be considered between classes that are NOT back-to-back */ 122 private boolean iComputeDistanceConflictsBetweenNonBTBClasses = false; 123 /** Reference of the accommodation of students that need short distances */ 124 private String iShortDistanceAccommodationReference = "SD"; 125 /** Allowed distance in minutes (for {@link HardDistanceConflicts}) */ 126 private int iAllowedDistanceInMinutes = 30; 127 /** Hard distance limit in minutes (for {@link HardDistanceConflicts}) */ 128 private int iDistanceHardLimitInMinutes = 60; 129 /** Long distance limit in minutes (for display) */ 130 private int iDistanceLongLimitInMinutes = 60; 131 /** Hard distance conflicts enabled (for {@link HardDistanceConflicts}) */ 132 private boolean iHardDistanceConflicts = false; 133 134 private final ReentrantReadWriteLock iLock = new ReentrantReadWriteLock(); 135 136 /** Default properties */ 137 public DistanceMetric() { 138 } 139 140 public DistanceMetric(DistanceMetric m) { 141 iModel = m.iModel; 142 iSpeed = m.iSpeed; 143 iInstructorNoPreferenceLimit = m.iInstructorNoPreferenceLimit; 144 iInstructorDiscouragedLimit = m.iInstructorDiscouragedLimit; 145 iInstructorProhibitedLimit = m.iInstructorProhibitedLimit; 146 iInstructorLongTravelInMinutes = m.iInstructorLongTravelInMinutes; 147 iNullDistance = m.iNullDistance; 148 iMaxTravelTime = m.iMaxTravelTime; 149 iComputeDistanceConflictsBetweenNonBTBClasses = m.iComputeDistanceConflictsBetweenNonBTBClasses; 150 iShortDistanceAccommodationReference = m.iShortDistanceAccommodationReference; 151 iAllowedDistanceInMinutes = m.iAllowedDistanceInMinutes; 152 iDistanceHardLimitInMinutes = m.iDistanceHardLimitInMinutes; 153 iDistanceLongLimitInMinutes = m.iDistanceLongLimitInMinutes; 154 iHardDistanceConflicts = m.iHardDistanceConflicts; 155 m.iLock.readLock().lock(); 156 try { 157 for (Map.Entry<Long, Map<Long, Integer>> e: m.iTravelTimes.entrySet()) 158 iTravelTimes.put(e.getKey(), new HashMap<Long, Integer>(e.getValue())); 159 } finally { 160 m.iLock.readLock().unlock(); 161 } 162 } 163 164 /** With provided ellipsoid 165 * @param model ellipsoid model 166 **/ 167 public DistanceMetric(Ellipsoid model) { 168 iModel = model; 169 if (iModel == Ellipsoid.LEGACY) { 170 iSpeed = 100.0 / 15; 171 iInstructorDiscouragedLimit = 5.0; 172 iInstructorProhibitedLimit = 20.0; 173 } 174 } 175 176 /** With provided ellipsoid and student speed 177 * @param model ellipsoid model 178 * @param speed student speed in meters per minute 179 **/ 180 public DistanceMetric(Ellipsoid model, double speed) { 181 iModel = model; 182 iSpeed = speed; 183 } 184 185 /** Configured using properties 186 * @param properties solver configuration 187 **/ 188 public DistanceMetric(DataProperties properties) { 189 if (Ellipsoid.LEGACY.name().equals(properties.getProperty("Distances.Ellipsoid",Ellipsoid.LEGACY.name()))) { 190 //LEGACY MODE 191 iModel = Ellipsoid.LEGACY; 192 iSpeed = properties.getPropertyDouble("Student.DistanceLimit", 1000.0 / 15) / 10.0; 193 iInstructorNoPreferenceLimit = properties.getPropertyDouble("Instructor.NoPreferenceLimit", 0.0); 194 iInstructorDiscouragedLimit = properties.getPropertyDouble("Instructor.DiscouragedLimit", 5.0); 195 iInstructorProhibitedLimit = properties.getPropertyDouble("Instructor.ProhibitedLimit", 20.0); 196 iNullDistance = properties.getPropertyDouble("Distances.NullDistance", 1000.0); 197 iMaxTravelTime = properties.getPropertyInt("Distances.MaxTravelDistanceInMinutes", 60); 198 } else { 199 iModel = Ellipsoid.valueOf(properties.getProperty("Distances.Ellipsoid", Ellipsoid.WGS84.name())); 200 if (iModel == null) iModel = Ellipsoid.WGS84; 201 iSpeed = properties.getPropertyDouble("Distances.Speed", properties.getPropertyDouble("Student.DistanceLimit", 1000.0 / 15)); 202 iInstructorNoPreferenceLimit = properties.getPropertyDouble("Instructor.NoPreferenceLimit", iInstructorNoPreferenceLimit); 203 iInstructorDiscouragedLimit = properties.getPropertyDouble("Instructor.DiscouragedLimit", iInstructorDiscouragedLimit); 204 iInstructorProhibitedLimit = properties.getPropertyDouble("Instructor.ProhibitedLimit", iInstructorProhibitedLimit); 205 iNullDistance = properties.getPropertyDouble("Distances.NullDistance", iNullDistance); 206 iMaxTravelTime = properties.getPropertyInt("Distances.MaxTravelDistanceInMinutes", 60); 207 } 208 iComputeDistanceConflictsBetweenNonBTBClasses = properties.getPropertyBoolean( 209 "Distances.ComputeDistanceConflictsBetweenNonBTBClasses", iComputeDistanceConflictsBetweenNonBTBClasses); 210 iShortDistanceAccommodationReference = properties.getProperty( 211 "Distances.ShortDistanceAccommodationReference", iShortDistanceAccommodationReference); 212 iInstructorLongTravelInMinutes = properties.getPropertyDouble("Instructor.InstructorLongTravelInMinutes", 30.0); 213 iAllowedDistanceInMinutes = properties.getPropertyInt("HardDistanceConflict.AllowedDistanceInMinutes", iAllowedDistanceInMinutes); 214 iDistanceHardLimitInMinutes = properties.getPropertyInt("HardDistanceConflict.DistanceHardLimitInMinutes", iDistanceHardLimitInMinutes); 215 iDistanceLongLimitInMinutes = properties.getPropertyInt("HardDistanceConflict.DistanceLongLimitInMinutes", iDistanceLongLimitInMinutes); 216 iHardDistanceConflicts = properties.getPropertyBoolean("Sectioning.HardDistanceConflict", iHardDistanceConflicts); 217 } 218 219 /** Degrees to radians 220 * @param deg degrees 221 * @return radians 222 **/ 223 protected double deg2rad(double deg) { 224 return deg * Math.PI / 180; 225 } 226 227 /** Compute distance between the two given coordinates 228 * @param lat1 first coordinate's latitude 229 * @param lon1 first coordinate's longitude 230 * @param lat2 second coordinate's latitude 231 * @param lon2 second coordinate's longitude 232 * @return distance in meters 233 * @deprecated Use @{link {@link DistanceMetric#getDistanceInMeters(Long, Double, Double, Long, Double, Double)} instead (to include travel time matrix when available). 234 */ 235 @Deprecated 236 public double getDistanceInMeters(Double lat1, Double lon1, Double lat2, Double lon2) { 237 if (lat1 == null || lat2 == null || lon1 == null || lon2 == null) 238 return iNullDistance; 239 240 if (lat1.equals(lat2) && lon1.equals(lon2)) return 0.0; 241 242 // legacy mode -- euclidian distance, 1 unit is 10 meters 243 if (iModel == Ellipsoid.LEGACY) { 244 if (lat1 < 0 || lat2 < 0 || lon1 < 0 || lon2 < 0) return iNullDistance; 245 double dx = lat1 - lat2; 246 double dy = lon1 - lon2; 247 return Math.sqrt(dx * dx + dy * dy); 248 } 249 250 String id = null; 251 if (lat1 < lat2 || (lat1 == lat2 && lon1 <= lon2)) { 252 id = 253 Long.toHexString(Double.doubleToRawLongBits(lat1)) + 254 Long.toHexString(Double.doubleToRawLongBits(lon1)) + 255 Long.toHexString(Double.doubleToRawLongBits(lat2)) + 256 Long.toHexString(Double.doubleToRawLongBits(lon2)); 257 } else { 258 id = 259 Long.toHexString(Double.doubleToRawLongBits(lat1)) + 260 Long.toHexString(Double.doubleToRawLongBits(lon1)) + 261 Long.toHexString(Double.doubleToRawLongBits(lat2)) + 262 Long.toHexString(Double.doubleToRawLongBits(lon2)); 263 } 264 265 iLock.readLock().lock(); 266 try { 267 Double distance = iDistanceCache.get(id); 268 if (distance != null) return distance; 269 } finally { 270 iLock.readLock().unlock(); 271 } 272 273 iLock.writeLock().lock(); 274 try { 275 Double distance = iDistanceCache.get(id); 276 if (distance != null) return distance; 277 278 double a = iModel.a(), b = iModel.b(), f = iModel.f(); // ellipsoid params 279 double L = deg2rad(lon2-lon1); 280 double U1 = Math.atan((1-f) * Math.tan(deg2rad(lat1))); 281 double U2 = Math.atan((1-f) * Math.tan(deg2rad(lat2))); 282 double sinU1 = Math.sin(U1), cosU1 = Math.cos(U1); 283 double sinU2 = Math.sin(U2), cosU2 = Math.cos(U2); 284 285 double lambda = L, lambdaP, iterLimit = 100; 286 double cosSqAlpha, cos2SigmaM, sinSigma, cosSigma, sigma, sinLambda, cosLambda; 287 do { 288 sinLambda = Math.sin(lambda); 289 cosLambda = Math.cos(lambda); 290 sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) + 291 (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda)); 292 if (sinSigma==0) return 0; // co-incident points 293 cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda; 294 sigma = Math.atan2(sinSigma, cosSigma); 295 double sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma; 296 cosSqAlpha = 1 - sinAlpha*sinAlpha; 297 cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha; 298 if (Double.isNaN(cos2SigmaM)) cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (�6) 299 double C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha)); 300 lambdaP = lambda; 301 lambda = L + (1-C) * f * sinAlpha * 302 (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM))); 303 } while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0); 304 if (iterLimit==0) return Double.NaN; // formula failed to converge 305 306 double uSq = cosSqAlpha * (a*a - b*b) / (b*b); 307 double A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq))); 308 double B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq))); 309 double deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- 310 B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM))); 311 312 // initial & final bearings 313 // double fwdAz = Math.atan2(cosU2*sinLambda, cosU1*sinU2-sinU1*cosU2*cosLambda); 314 // double revAz = Math.atan2(cosU1*sinLambda, -sinU1*cosU2+cosU1*sinU2*cosLambda); 315 316 // s = s.toFixed(3); // round to 1mm precision 317 318 distance = b*A*(sigma-deltaSigma); 319 iDistanceCache.put(id, distance); 320 return distance; 321 } finally { 322 iLock.writeLock().unlock(); 323 } 324 } 325 326 /** 327 * Compute distance in minutes. 328 * 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). 329 * @param lat1 first coordinate's latitude 330 * @param lon1 first coordinate's longitude 331 * @param lat2 second coordinate's latitude 332 * @param lon2 second coordinate's longitude 333 * @return distance in minutes 334 * @deprecated Use @{link {@link DistanceMetric#getDistanceInMinutes(Long, Double, Double, Long, Double, Double)} instead (to include travel time matrix when available). 335 */ 336 @Deprecated 337 public int getDistanceInMinutes(double lat1, double lon1, double lat2, double lon2) { 338 return (int) Math.round(getDistanceInMeters(lat1, lon1, lat2, lon2) / iSpeed); 339 } 340 341 /** 342 * Converts minutes to meters. 343 * Property Distances.Speed (in meters per minute) is used, defaults to 1000 meters per 15 minutes. 344 * @param min minutes to travel 345 * @return meters to travel 346 */ 347 public double minutes2meters(int min) { 348 return iSpeed * min; 349 } 350 351 352 /** Back-to-back classes in rooms within this limit have neutral preference 353 * @return limit in meters 354 **/ 355 public double getInstructorNoPreferenceLimit() { 356 return iInstructorNoPreferenceLimit; 357 } 358 359 /** Back-to-back classes in rooms within this limit have discouraged preference 360 * @return limit in meters 361 **/ 362 public double getInstructorDiscouragedLimit() { 363 return iInstructorDiscouragedLimit; 364 } 365 366 /** Back-to-back classes in rooms within this limit have strongly discouraged preference, it is prohibited to exceed this limit. 367 * @return limit in meters 368 **/ 369 public double getInstructorProhibitedLimit() { 370 return iInstructorProhibitedLimit; 371 } 372 373 /** 374 * When Distances.ComputeDistanceConflictsBetweenNonBTBClasses is enabled, distance limit (in minutes) 375 * for a long travel. 376 * @return travel time in minutes 377 */ 378 public double getInstructorLongTravelInMinutes() { 379 return iInstructorLongTravelInMinutes; 380 } 381 382 /** True if legacy mode is used (Euclidian distance where 1 unit is 10 meters) 383 * @return true if the ellipsoid model is the old one 384 **/ 385 public boolean isLegacy() { 386 return iModel == Ellipsoid.LEGACY; 387 } 388 389 /** Maximal travel distance between rooms when no coordinates are given 390 * @return travel time in minutes 391 **/ 392 public int getMaxTravelDistanceInMinutes() { 393 return iMaxTravelTime; 394 } 395 396 /** Set maximal travel distance between rooms when no coordinates are given 397 * @param maxTravelTime max travel time in minutes 398 */ 399 public void setMaxTravelDistanceInMinutes(int maxTravelTime) { 400 iMaxTravelTime = maxTravelTime; 401 } 402 403 /** Add travel time between two locations 404 * @param roomId1 first room's id 405 * @param roomId2 second room's id 406 * @param travelTimeInMinutes travel time in minutes 407 **/ 408 public void addTravelTime(Long roomId1, Long roomId2, Integer travelTimeInMinutes) { 409 iLock.writeLock().lock(); 410 try { 411 if (roomId1 == null || roomId2 == null) return; 412 if (roomId1 < roomId2) { 413 Map<Long, Integer> times = iTravelTimes.get(roomId1); 414 if (times == null) { times = new HashMap<Long, Integer>(); iTravelTimes.put(roomId1, times); } 415 if (travelTimeInMinutes == null) 416 times.remove(roomId2); 417 else 418 times.put(roomId2, travelTimeInMinutes); 419 } else { 420 Map<Long, Integer> times = iTravelTimes.get(roomId2); 421 if (times == null) { times = new HashMap<Long, Integer>(); iTravelTimes.put(roomId2, times); } 422 if (travelTimeInMinutes == null) 423 times.remove(roomId1); 424 else 425 times.put(roomId1, travelTimeInMinutes); 426 } 427 } finally { 428 iLock.writeLock().unlock(); 429 } 430 } 431 432 /** Return travel time between two locations. 433 * @param roomId1 first room's id 434 * @param roomId2 second room's id 435 * @return travel time in minutes 436 **/ 437 public Integer getTravelTimeInMinutes(Long roomId1, Long roomId2) { 438 iLock.readLock().lock(); 439 try { 440 if (roomId1 == null || roomId2 == null) return null; 441 if (roomId1 < roomId2) { 442 Map<Long, Integer> times = iTravelTimes.get(roomId1); 443 return (times == null ? null : times.get(roomId2)); 444 } else { 445 Map<Long, Integer> times = iTravelTimes.get(roomId2); 446 return (times == null ? null : times.get(roomId1)); 447 } 448 } finally { 449 iLock.readLock().unlock(); 450 } 451 } 452 453 /** Return travel time between two locations. Travel times are used when available, use coordinates otherwise. 454 * @param roomId1 first room's id 455 * @param lat1 first room's latitude 456 * @param lon1 first room's longitude 457 * @param roomId2 second room's id 458 * @param lat2 second room's latitude 459 * @param lon2 second room's longitude 460 * @return distance in minutes 461 **/ 462 public Integer getDistanceInMinutes(Long roomId1, Double lat1, Double lon1, Long roomId2, Double lat2, Double lon2) { 463 Integer distance = getTravelTimeInMinutes(roomId1, roomId2); 464 if (distance != null) return distance; 465 466 if (lat1 == null || lat2 == null || lon1 == null || lon2 == null) 467 return getMaxTravelDistanceInMinutes(); 468 else 469 return (int) Math.min(getMaxTravelDistanceInMinutes(), Math.round(getDistanceInMeters(lat1, lon1, lat2, lon2) / iSpeed)); 470 } 471 472 /** Return travel distance between two locations. Travel times are used when available, use coordinates otherwise 473 * @param roomId1 first room's id 474 * @param lat1 first room's latitude 475 * @param lon1 first room's longitude 476 * @param roomId2 second room's id 477 * @param lat2 second room's latitude 478 * @param lon2 second room's longitude 479 * @return distance in meters 480 **/ 481 public double getDistanceInMeters(Long roomId1, Double lat1, Double lon1, Long roomId2, Double lat2, Double lon2) { 482 Integer distance = getTravelTimeInMinutes(roomId1, roomId2); 483 if (distance != null) return minutes2meters(distance); 484 485 return getDistanceInMeters(lat1, lon1, lat2, lon2); 486 } 487 488 /** Return travel times matrix 489 * @return travel times matrix 490 **/ 491 public Map<Long, Map<Long, Integer>> getTravelTimes() { return iTravelTimes; } 492 493 /** 494 * True if distances should be considered between classes that are NOT back-to-back. Distance in minutes is then 495 * 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. 496 * @return true if distances should be considered between classes that are NOT back-to-back 497 **/ 498 public boolean doComputeDistanceConflictsBetweenNonBTBClasses() { 499 return iComputeDistanceConflictsBetweenNonBTBClasses; 500 } 501 502 public void setComputeDistanceConflictsBetweenNonBTBClasses(boolean computeDistanceConflictsBetweenNonBTBClasses) { 503 iComputeDistanceConflictsBetweenNonBTBClasses = computeDistanceConflictsBetweenNonBTBClasses; 504 } 505 506 /** 507 * Reference of the accommodation of students that need short distances 508 */ 509 public String getShortDistanceAccommodationReference() { 510 return iShortDistanceAccommodationReference; 511 } 512 513 /** Allowed distance in minutes (for {@link HardDistanceConflicts}) */ 514 public int getAllowedDistanceInMinutes() { 515 return iAllowedDistanceInMinutes; 516 } 517 /** Hard distance limit in minutes (for {@link HardDistanceConflicts}) */ 518 public int getDistanceHardLimitInMinutes() { 519 return iDistanceHardLimitInMinutes; 520 } 521 /** Long distance limit in minutes (for display) */ 522 public int getDistanceLongLimitInMinutes() { 523 return iDistanceLongLimitInMinutes; 524 } 525 /** Hard distance conflicts enabled (for {@link HardDistanceConflicts}) */ 526 public boolean isHardDistanceConflictsEnabled() { 527 return iHardDistanceConflicts; 528 } 529 530 531 /** Few tests 532 * @param args program arguments 533 **/ 534 public static void main(String[] args) { 535 System.out.println("Distance between Prague and Zlin: " + new DistanceMetric().getDistanceInMeters(50.087661, 14.420535, 49.226736, 17.668856) / 1000.0 + " km"); 536 System.out.println("Distance between ENAD and PMU: " + new DistanceMetric().getDistanceInMeters(40.428323, -86.912785, 40.425078, -86.911474) + " m"); 537 System.out.println("Distance between ENAD and ME: " + new DistanceMetric().getDistanceInMeters(40.428323, -86.912785, 40.429338, -86.91267) + " m"); 538 System.out.println("Distance between Prague and Zlin: " + new DistanceMetric().getDistanceInMinutes(50.087661, 14.420535, 49.226736, 17.668856) / 60 + " hours"); 539 System.out.println("Distance between ENAD and PMU: " + new DistanceMetric().getDistanceInMinutes(40.428323, -86.912785, 40.425078, -86.911474) + " minutes"); 540 System.out.println("Distance between ENAD and ME: " + new DistanceMetric().getDistanceInMinutes(40.428323, -86.912785, 40.429338, -86.91267) + " minutes"); 541 } 542 543}