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 &lt;= limit &rarr; no preference), Instructor.DiscouragedLimit (distance &lt;= limit &rarr; discouraged),
020 * Instructor.ProhibitedLimit (distance &lt;= limit &rarr; 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}