001 package edu.nrao.sss.model.source.sort; 002 003 import java.util.Comparator; 004 import java.util.Date; 005 import java.util.HashMap; 006 import java.util.Map; 007 import java.util.TimeZone; 008 009 import org.apache.log4j.Logger; 010 011 import edu.nrao.sss.astronomy.CelestialCoordinateSystem; 012 import edu.nrao.sss.astronomy.CoordinateConversionException; 013 import edu.nrao.sss.astronomy.Epoch; 014 import edu.nrao.sss.astronomy.SimpleSkyPosition; 015 import edu.nrao.sss.astronomy.SkyPosition; 016 import edu.nrao.sss.geom.EarthPosition; 017 import edu.nrao.sss.measure.Angle; 018 import edu.nrao.sss.measure.ArcUnits; 019 import edu.nrao.sss.measure.Latitude; 020 import edu.nrao.sss.measure.LocalSiderealTime; 021 import edu.nrao.sss.measure.Longitude; 022 import edu.nrao.sss.model.resource.TelescopeType; 023 import edu.nrao.sss.model.source.Source; 024 import edu.nrao.sss.model.source.SourceCatalogEntry; 025 import edu.nrao.sss.sort.DoubleSortKey; 026 import edu.nrao.sss.sort.Orderable; 027 import edu.nrao.sss.sort.SortOrder; 028 029 /** 030 * Compares {@link Source sources} based on their proximity to a given point 031 * in the sky. 032 * <p> 033 * <b>Version Info:</b> 034 * <table style="margin-left:2em"> 035 * <tr><td>$Revision: 1617 $</td></tr> 036 * <tr><td>$Date: 2008-10-10 16:52:33 -0600 (Fri, 10 Oct 2008) $</td></tr> 037 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 038 * </table></p> 039 * 040 * @author David M. Harland 041 * @since 2008-10-08 042 */ 043 public class SourceProximitySortKey 044 implements Orderable, Comparator<SourceCatalogEntry> 045 { 046 private static final Logger log = Logger.getLogger(SourceProximitySortKey.class); 047 048 private static final EarthPosition VLA_LOCATION = TelescopeType.EVLA.getLocation(); 049 private static final TimeZone VLA_TIME_ZONE = TimeZone.getTimeZone("US/Mountain"); 050 051 //Client-settable properties 052 private SkyPosition centralPosition; 053 private EarthPosition observerLocation; 054 private TimeZone observerTimeZone; 055 private Date requestedTime; 056 057 //Derived from client-settable properties 058 private Date queryTime; 059 private LocalSiderealTime queryLst; 060 private CelestialCoordinateSystem cpCoordSys; 061 private Epoch cpEpoch; 062 private Latitude cpLatitude; 063 private Longitude cpLongitude; 064 065 private DegreesSorter sorter; 066 067 //DMH did a test sorting ~450 sources. Using a map whose keys were 068 //SCEs took ~5s; using a map w/ String keys took ~0.75s. 069 //We therefore try to use String map, but since two diff sources 070 //could have same name, we sometimes go to slow map. 071 072 //This map will hold only those SCEs that have the same name as another 073 //SCE in the sort. If there are n SCEs with the same name, one will 074 //be in the fast map and the other n-1 will be here. 075 private Map<SourceCatalogEntry, Double> proximitiesSlow; 076 077 //The first SCE seen with a given name will be held here. 078 //The SCE name is used as the key. 079 private Map<String, Double> proximitiesFast; 080 081 //This map tell us whether the proximity for a given SCE is in the fast 082 //or slow map. For a given name, the SCE returned as a value by this 083 //map will have its proximity in the fast map. Any other SCEs with that 084 //name will have their proximities held in the slow map. 085 private Map<String, SourceCatalogEntry> fastMapEntries; 086 087 /** 088 * Tells this key to get position information as of the current system time. 089 */ 090 public static final Date NOW = (Date)null; 091 092 /** 093 * Creates a new key for sorting sources by their proximity to a particular 094 * sky position. 095 */ 096 public SourceProximitySortKey() 097 { 098 proximitiesSlow = new HashMap<SourceCatalogEntry, Double>(); 099 proximitiesFast = new HashMap<String, Double>(); 100 fastMapEntries = new HashMap<String, SourceCatalogEntry>(); 101 102 sorter = new DegreesSorter(); 103 104 centralPosition = new SimpleSkyPosition(); 105 106 observerLocation = VLA_LOCATION; 107 observerTimeZone = VLA_TIME_ZONE; 108 109 requestedTime = NOW; 110 queryTime = new Date(); 111 queryLst = new LocalSiderealTime(queryTime, VLA_LOCATION.getLongitude(), 112 VLA_TIME_ZONE); 113 updateCentralPositionVariables(); 114 } 115 116 /** 117 * Returns the angular separation, in degrees, between {@code sce} 118 * and the {@link #setCentralPosition(SkyPosition) central position} 119 * of this object. 120 * 121 * @param sce 122 * a celestial source. 123 * 124 * @return 125 * the angular separation between {@code source} and this object's 126 * central sky position. 127 */ 128 public double getSeparationInDegrees(SourceCatalogEntry sce) 129 { 130 Double degrees = getPrecalculatedDegrees(sce); 131 132 if (degrees == null) 133 degrees = calculateAndUpdateSeparation(sce); 134 135 return degrees == null ? 180.0 : degrees; 136 } 137 138 private Double getPrecalculatedDegrees(SourceCatalogEntry sce) 139 { 140 Double degrees = null; 141 142 String name = sce.getName(); 143 SourceCatalogEntry fastMapEntry = fastMapEntries.get(name); 144 145 //If the returned SCE is not null, we've seen an SCE w/ the same name 146 //as the one sent to this method. It may or may not be the same object. 147 if (fastMapEntry != null) 148 { 149 //If fastMapEntry equals sce, it means its proximity value is 150 //in the fast map. If not equal, then either it is in the 151 //slow map, or it is the first time we've seen this sce 152 //(which happens to have the same name as one or more that 153 //we've already seen). 154 degrees = fastMapEntry.equals(sce) ? proximitiesFast.get(name) 155 : proximitiesSlow.get(sce); 156 } 157 158 return degrees; 159 } 160 161 /** 162 * Sets the point in the sky from which all other sources will be measured. 163 * 164 * @param newPoint 165 * a point in the sky. Sources will be compared to each other based 166 * on their angular distance from this point. A value of <i>null</i> 167 * is not allowed. 168 * 169 * @throws IllegalArgumentException 170 * if {@code newPoint} is <i>null</i>. 171 */ 172 public void setCentralPosition(SkyPosition newPoint) 173 { 174 //Do not accept null position 175 if (newPoint == null) 176 throw new IllegalArgumentException("Central sky position may not be null."); 177 178 centralPosition = newPoint; 179 updateCentralPositionVariables(); 180 compareTime = 0L; 181 proximitiesFast.clear(); 182 proximitiesSlow.clear(); 183 fastMapEntries.clear(); 184 } 185 186 private void updateCentralPositionVariables() 187 { 188 cpCoordSys = centralPosition.getCoordinateSystem(); 189 cpEpoch = centralPosition.getEpoch(); 190 cpLatitude = centralPosition.getLatitude(queryTime); 191 cpLongitude = centralPosition.getLongitude(queryTime); 192 } 193 194 /** 195 * Sets the earth position and time zone to be used in coordinate conversions. 196 * This method is rarely used. The observer's parameters come into play 197 * only when dealing with conversions to or from AZ/EL. The default 198 * position and time zone are those of the EVLA. 199 * 200 * @param location 201 * the location of the observer to be used in coordinate conversion 202 * calculations. A value of <i>null</i> will result in the use of 203 * the EVLA's position. 204 * 205 * @param timeZone 206 * the time zone of the observer to be used in coordinate conversion 207 * calculations. A value of <i>null</i> will result in the use of 208 * the EVLA's position. 209 * 210 * @since 2008-10-08 211 */ 212 public void setObserverParameters(EarthPosition location, TimeZone timeZone) 213 { 214 observerLocation = (location == null) ? VLA_LOCATION : location; 215 observerTimeZone = (timeZone == null) ? VLA_TIME_ZONE : timeZone; 216 217 queryLst.setLocationAndTimeZone(observerLocation.getLongitude(), 218 observerTimeZone); 219 compareTime = 0L; 220 proximitiesFast.clear(); 221 proximitiesSlow.clear(); 222 fastMapEntries.clear(); 223 } 224 225 /** 226 * Sets the time for which position information will be requested. 227 * The constant {@link #NOW} may be used as a signal to use the 228 * current system time. 229 * 230 * @param newTime the time at which position information will be evaluated. 231 */ 232 public void setQueryTime(Date newTime) 233 { 234 requestedTime = newTime; 235 236 queryTime = (requestedTime == NOW) ? new Date() : requestedTime; 237 238 queryLst.setSolarTime(queryTime); 239 240 updateCentralPositionVariables(); 241 242 compareTime = 0L; 243 244 proximitiesFast.clear(); 245 proximitiesSlow.clear(); 246 fastMapEntries.clear(); 247 } 248 249 /* (non-Javadoc) 250 * @see edu.nrao.sss.sort.Orderable#setOrder(edu.nrao.sss.sort.SortOrder) 251 */ 252 public void setOrder(SortOrder newOrder) 253 { 254 sorter.setOrder(newOrder); 255 compareTime = 0L; 256 } 257 258 /* (non-Javadoc) 259 * @see edu.nrao.sss.sort.Orderable#getOrder() 260 */ 261 public SortOrder getOrder() 262 { 263 return sorter.getOrder(); 264 } 265 266 private long compareTime = 0L; 267 268 /* (non-Javadoc) 269 * @see Comparator#compare(Object, Object) 270 */ 271 public int compare(SourceCatalogEntry sce1, SourceCatalogEntry sce2) 272 { 273 //Attempt to figure out whether or not this is a comparison of 274 //an in-progress sort or the first comparison of a new sort. 275 //We do this only when the client wants to use the current time 276 //for position determination. We are trying to use one time for 277 //an entire sort, but update the current time for a new sort. 278 if (requestedTime == NOW) 279 { 280 long now = System.currentTimeMillis(); 281 282 //If we haven't done a comparison in awhile, it's probably a new sort 283 if ((now - compareTime) > 1000L) //1 second 284 { 285 queryTime = new Date(); 286 queryLst.setSolarTime(queryTime); 287 proximitiesFast.clear(); 288 proximitiesSlow.clear(); 289 fastMapEntries.clear(); 290 } 291 292 compareTime = now; 293 } 294 295 Double degrees1 = getPrecalculatedDegrees(sce1); 296 Double degrees2 = getPrecalculatedDegrees(sce2); 297 298 if (degrees1 == null) 299 degrees1 = calculateAndUpdateSeparation(sce1); 300 301 if (degrees2 == null) 302 degrees2 = calculateAndUpdateSeparation(sce2); 303 304 return sorter.compare(degrees1, degrees2); 305 } 306 307 private Double calculateAndUpdateSeparation(SourceCatalogEntry sce) 308 { 309 Double degrees; 310 311 //Use the time to get source from what might be a lookup table 312 Source source = sce.get(queryTime); 313 314 //Source could be null if queryTime is earlier than earliest time 315 //in table. Put at end of the line. 316 if (source == null) 317 { 318 degrees = 180.0; 319 } 320 else 321 { 322 //Convert source's position to system used by centralPosition 323 SkyPosition srcPos = source.getCentralSubsource().getPosition(); 324 325 try 326 { 327 srcPos = srcPos.toPosition(cpCoordSys, cpEpoch, 328 observerLocation, queryLst); 329 } 330 catch (CoordinateConversionException ex) 331 { 332 log.warn("Source position conversion failed for source '" + source.getName() + 333 "'. Using unconverted position.",ex); 334 //Work with unconverted values 335 } 336 337 Angle separation = EarthPosition.calculateAngularSeparation( 338 srcPos.getLatitude(queryTime), cpLatitude, 339 srcPos.getLongitude(queryTime), cpLongitude); 340 341 degrees = separation.toUnits(ArcUnits.DEGREE).doubleValue(); 342 } 343 344 //Use the fast map if it does not already have an entry for an 345 //SCE with this name. Otherwise use the slow map. 346 String name = sce.getName(); 347 if (!fastMapEntries.containsKey(name)) 348 { 349 fastMapEntries.put(name, sce); 350 proximitiesFast.put(name, degrees); 351 } 352 else 353 { 354 proximitiesSlow.put(sce, degrees); 355 } 356 357 return degrees; 358 } 359 360 private class DegreesSorter extends DoubleSortKey implements Comparator<Double> 361 { 362 public int compare(Double d1, Double d2) 363 { 364 return compareObjects(d1, d2); 365 } 366 } 367 }