/**
 * A data source for Radar data files. This class holds a List of data sources and a set of {@link
 * RadarAdapter}-s to adapte each file.
 *
 * @author Unidata Development Team
 * @version $Revision: 1.38 $ $Date: 2007/07/26 19:45:01 $
 */
public abstract class RadarDataSource extends FilesDataSource implements RadarConstants {

  /** _more_ */
  public boolean useDriverTime = false;

  /** _more_ */
  public Object TimeDriver = null;

  /** Radar data appropriate for RHI */
  public static final DataCategory CATEGORY_RHI = DataCategory.parseCategory("RHI", false);

  /** RHI sweep category */
  public static final DataCategory CATEGORY_RHISWEEP =
      DataCategory.parseCategory("RHISWEEP", false);

  /** Radar data appropriate for CAPPIE */
  public static final DataCategory CATEGORY_CAPPI = DataCategory.parseCategory("CAPPI", false);

  /** Radar data appropriate for radar volume */
  public static final DataCategory CATEGORY_VOLUME =
      DataCategory.parseCategory("RADAR_VOLUME", false);

  /** Radar data appropriate for radar vertical cross section */
  public static final DataCategory CATEGORY_VCS =
      DataCategory.parseCategory("RADAR_CROSS_SECTION", false);

  /** Radar data appropriate for radar isosurface */
  public static final DataCategory CATEGORY_ISOSURFACE =
      DataCategory.parseCategory("RADAR_ISOSURFACE", false);

  /** Radar data appropriate for radar isosurface */
  public static final DataCategory CATEGORY_ISOSURFACE_TIME =
      DataCategory.parseCategory("RADAR_ISOSURFACE-TIME", false);

  /** 2d sweep category */
  public static final DataCategory CATEGORY_SWEEP_3D =
      DataCategory.parseCategory("SWEEP-3D", false);

  /** 3d sweep category */
  public static final DataCategory CATEGORY_SWEEP_3D_TIME =
      DataCategory.parseCategory("SWEEP-3D-TIME", false);

  /** The 2D radar sweep category */
  public static final DataCategory CATEGORY_SWEEP_2D =
      DataCategory.parseCategory("SWEEP-2D", false);

  /** The 2D radar sweep category */
  public static final DataCategory CATEGORY_SWEEP_2D_TIME =
      DataCategory.parseCategory("SWEEP-2D-TIME", false);

  /** The radar time height category */
  public static final DataCategory CATEGORY_TH =
      DataCategory.parseCategory("RADAR_TIMEHEIGHT", false);

  /** The radar vertical wind profiler category */
  public static final DataCategory CATEGORY_VWP = DataCategory.parseCategory("RADAR_VWP", false);

  /** A mapping from filename to adapter */
  private Hashtable fileToAdapter = new Hashtable();

  /** The 2D radar sweep category */
  public static final DataCategory CATEGORY_RASTER_2D =
      DataCategory.parseCategory("IMAGE-RASTER", false);

  /** The 2D radar sweep category with time */
  public static final DataCategory CATEGORY_RASTER_2D_TIME =
      DataCategory.parseCategory("IMAGE-RASTER-TIME", false);

  /** Construct a radar data source. */
  public RadarDataSource() {}

  /**
   * Construct a new radar data source.
   *
   * @param descriptor descriptor for this datasource
   * @param sources List of sources of data (filenames, URLs)
   * @param description Description of the files
   * @param properties hashtable of properties. Includes radar location
   * @throws VisADException couldn't create the data
   */
  public RadarDataSource(
      DataSourceDescriptor descriptor, List sources, String description, Hashtable properties)
      throws VisADException {
    super(
        descriptor,
        sources,
        (sources.size() == 1) ? (String) sources.get(0) : description,
        description,
        properties);
  }

  /**
   * Can this data source save its dat to local disk
   *
   * @return can save to local disk
   */
  public boolean canSaveDataToLocalDisk() {
    return !isFileBased() && (getProperty(PROP_SERVICE_HTTP) != null);
  }

  /**
   * Transmogrify the filename
   *
   * @param filename filename
   * @param index which one
   * @return new file name
   */
  protected String processDataFilename(String filename, int index) {
    Hashtable properties = (Hashtable) getProperty(DataSource.PROP_SUBPROPERTIES + index);
    if (properties == null) {
      return filename;
    }
    String httpServer = (String) properties.get(PROP_SERVICE_HTTP);
    if (httpServer != null) {
      return httpServer;
    }
    return filename;
  }

  /** handle legacy bundles */
  public void initAfterUnpersistence() {
    super.initAfterUnpersistence();
    Integer mostRecent = null;
    if (getProperties() != null) {
      mostRecent = (Integer) getProperties().get(MOST_RECENT);
      if (mostRecent == null) {
        mostRecent = (Integer) getProperties().get(RADAR_MOST_RECENT);
      }
    }
    if ((mostRecent != null) && (mostRecent.intValue() > 0)) {
      getPollingInfo().setFileCount(mostRecent.intValue());
    }
  }

  /**
   * Make a RadarAdapter from the source
   *
   * @param source source (file or URL) of data
   * @return corresponding RadarAdapter
   * @throws Exception problem creating the adapter.
   */
  protected abstract RadarAdapter makeRadarAdapter(String source) throws Exception;

  /**
   * This is called when the CacheManager detects the need ot clear memory. It is intended to be
   * overwritten by derived classes that are holding cached data that is not in the normal putCache
   * facilities provided by this class since that data is actually managed by the CacheManager
   */
  public void clearCachedData() {
    super.clearCachedData();
    List<RadarAdapter> adapters = getAdapters();
    for (Iterator iter = adapters.iterator(); iter.hasNext(); ) {
      RadarAdapter adapter = (RadarAdapter) iter.next();
      adapter.clearCachedData();
    }
  }

  /**
   * Create, if needed, and return the list of adapters. Will return null if there are no valid
   * adapters.
   *
   * @return List of adapters or null
   */
  protected List<RadarAdapter> getAdapters() {
    if ((adapters == null) || (adapters.size() == 0)) {
      try {
        makeAdapters(sources);
      } catch (Exception exc) {
        setInError(true);
        throw new WrapperException(exc);
      }
    }
    if (adapters.size() == 0) {
      adapters = null;
    }
    return adapters;
  }

  /**
   * Make the adapters for the given list of files
   *
   * @param files Data files
   * @throws Exception When bad things happen
   */
  private void makeAdapters(List files) throws Exception {
    adapters = new ArrayList<RadarAdapter>();
    Hashtable<String, RadarAdapter> oldAdapterMap = fileToAdapter;
    fileToAdapter = new Hashtable<String, RadarAdapter>();

    int cnt = 0;

    final List<String> badFiles = new ArrayList<String>();
    final List<Exception> badExceptions = new ArrayList<Exception>();

    final List<RadarAdapter> goodAdapters = new ArrayList<RadarAdapter>();
    final List<String> goodFiles = new ArrayList<String>();
    visad.util.ThreadManager threadManager =
        new visad.util.ThreadManager("radar data initialization");
    LogUtil.message("Initializing radar files");
    for (Iterator iter = files.iterator(); iter.hasNext(); ) {
      final String filename = iter.next().toString();
      RadarAdapter adapter = (RadarAdapter) oldAdapterMap.get(filename);
      cnt++;
      if (adapter == null) {
        threadManager.addRunnable(
            new visad.util.ThreadManager.MyRunnable() {
              public void run() throws Exception {
                try {
                  RadarAdapter myAdapter = makeRadarAdapter(filename);
                  synchronized (goodAdapters) {
                    goodAdapters.add(myAdapter);
                    goodFiles.add(filename);
                  }
                } catch (Exception e) {
                  synchronized (badExceptions) {
                    badExceptions.add(e);
                    badFiles.add(filename);
                  }
                }
              }
            });
      } else {
        goodAdapters.add(adapter);
        goodFiles.add(filename);
      }
    }

    //        threadManager.debug = true;
    threadManager.runInParallel(getDataContext().getIdv().getMaxDataThreadCount());
    LogUtil.message("");

    for (int i = 0; i < goodAdapters.size(); i++) {
      adapters.add(goodAdapters.get(i));
      fileToAdapter.put(goodFiles.get(i), goodAdapters.get(i));
    }

    if (!badFiles.isEmpty()) {
      StringBuffer buf = new StringBuffer();
      if (badFiles.size() < files.size()) {
        buf.append("<html>");
        buf.append("There were problems reading these files:");
        buf.append("<ul>");
        for (Iterator iterator = badFiles.iterator(); iterator.hasNext(); ) {
          buf.append("<li>");
          buf.append((String) iterator.next());
          buf.append("</li>");
        }
        buf.append("</ul>");
        buf.append("<p>Continue loading good data?<p></html>");
        boolean ok = ucar.unidata.util.GuiUtils.askYesNo("Error reading data", buf.toString());

        badExceptions.get(0).printStackTrace();
        if (ok) {
          files.removeAll(badFiles);
        } else {
          throw new VisADException("error reading files");
        }
      } else {
        throw badExceptions.get(0);
      }
    }
    // clean up any old adapters
    for (String source : oldAdapterMap.keySet()) {
      if (fileToAdapter.get(source) == null) {
        ((RadarAdapter) oldAdapterMap.get(source)).doRemove();
      }
    }
    oldAdapterMap = null;
  }

  /**
   * Create the list of times associated with this DataSource.
   *
   * @return list of times.
   */
  protected List doMakeDateTimes() {
    List times = new ArrayList();
    boolean isRealTime = isRealTime();
    List<RadarAdapter> adapters = getAdapters();
    for (int i = 0; i < adapters.size(); i++) {
      if (!isRealTime) {
        times.add(((RadarAdapter) adapters.get(i)).getBaseTime());
      } else {
        String name = i + "th most recent";
        if (i == 0) {
          name = "Most recent";
        }
        if ((i > 0) && (i < DataSource.ordinalNames.length)) {
          name = DataSource.ordinalNames[i] + " most recent";
        }
        times.add(new TwoFacedObject(name, i));
      }
    }
    Collections.sort(times);

    return times;
  }

  /**
   * Are we doing real time
   *
   * @return is real time
   */
  protected boolean isRealTime() {
    return (getPollingInfo().getFileCount() > 0);
  }

  /**
   * Get the data for the given DataChoice and selection criteria.
   *
   * @param dataChoice DataChoice for selection
   * @param category DataCategory for the DataChoice (not used)
   * @param subset subsetting criteria
   * @param requestProperties extra request properties
   * @return the Data object for the request
   * @throws RemoteException couldn't create a remote data object
   * @throws VisADException couldn't create the data
   */
  protected Data getDataInner(
      final DataChoice dataChoice,
      DataCategory category,
      final DataSelection subset,
      final Hashtable requestProperties)
      throws VisADException, RemoteException {

    try {
      List times = null;
      if (subset != null) {
        times = getTimesFromDataSelection(subset, dataChoice);
      }
      if (times == null) {
        times = dataChoice.getSelectedDateTimes();
      }

      List dtimes = subset.getTimeDriverTimes();

      if (dtimes != null && useDriverTime == false) {
        useDriverTime = true;
      }

      List<RadarAdapter> adapters = getAdapters();

      DateTime[] realDateTimes = new DateTime[adapters.size()];
      for (int i = 0; i < adapters.size(); i++) {
        realDateTimes[i] = ((RadarAdapter) adapters.get(i)).getBaseTime();
      }
      Arrays.sort(realDateTimes);
      // Flip it to get youngest date first
      boolean isRealTime = isRealTime();
      if (isRealTime) {
        realDateTimes =
            (DateTime[]) Misc.reverseArray(realDateTimes, new DateTime[realDateTimes.length]);
      }
      // if use time driver
      if (useDriverTime) {
        List tests = resetTimesList(realDateTimes, times);
        if (!compareTimeLists(times, tests)) {
          reloadData();
          adapters = getAdapters();

          realDateTimes = new DateTime[adapters.size()];
          for (int i = 0; i < adapters.size(); i++) {
            realDateTimes[i] = ((RadarAdapter) adapters.get(i)).getBaseTime();
          }
          Arrays.sort(realDateTimes);
          // Flip it to get youngest date first
          isRealTime = isRealTime();
          if (isRealTime) {
            realDateTimes =
                (DateTime[]) Misc.reverseArray(realDateTimes, new DateTime[realDateTimes.length]);
          }
          tests = resetTimesList(realDateTimes, times);
        }
        times = tests;
      }
      // if times are null, then that means all times
      DateTime[] dateTimes = null;

      if ((times == null) || (times.size() == 0)) {
        dateTimes = realDateTimes;
      } else {
        dateTimes = new DateTime[times.size()];
        for (int i = 0; i < times.size(); i++) {
          Object time = times.get(i);
          if (time instanceof TwoFacedObject) {
            int index = ((Integer) ((TwoFacedObject) time).getId()).intValue();
            dateTimes[i] = realDateTimes[index];
          } else if (time instanceof DateTime) {
            dateTimes[i] = (DateTime) time;
          }
        }
      }
      Arrays.sort(dateTimes);
      final Data[] datas = new Data[dateTimes.length];
      int timeIndex = 0;
      final MathType[] mt = {null};
      // create a new field of (Time -> (radar data)).
      // fill in the times array and data array with dates/data
      // only from those adapters which match the selected times.
      // if a data object is null, stick it in the list.
      // if all are null, then the MathType (mt) will never get set,
      // so return null.
      //            System.err.println ("Reading " + adapters.size() + " radar files");
      int cnt = 0;
      ThreadManager threadManager = new visad.util.ThreadManager("radar data reading");

      for (Iterator iter = adapters.iterator(); iter.hasNext(); ) {
        final RadarAdapter adapter = (RadarAdapter) iter.next();
        timeIndex = Arrays.binarySearch(dateTimes, adapter.getBaseTime());
        //              System.err.println ("timeIndex:" + timeIndex);
        if (timeIndex < 0) {
          continue;
        }
        cnt++;
        LogUtil.message("Time: " + (cnt) + "/" + dateTimes.length + " From:" + toString());
        final int theTimeIndex = timeIndex;
        threadManager.addRunnable(
            new visad.util.ThreadManager.MyRunnable() {
              public void run() throws Exception {
                Trace.call1("RDS.getData");
                Data d = adapter.getData(dataChoice, subset, requestProperties);
                Trace.call2("RDS.getData");
                datas[theTimeIndex] = d;
                if (d != null) {
                  mt[0] = d.getType();
                } else {
                }
              }
            });
      }

      try {
        // threadManager.debug = true;
        threadManager.runInParallel(getDataContext().getIdv().getMaxDataThreadCount());
      } catch (VisADException ve) {
        LogUtil.printMessage(ve.toString());
      }

      if (mt[0] == null) {
        return null;
      }

      FunctionType ft = new FunctionType(RealType.Time, mt[0]);
      SampledSet domainSet =
          (dateTimes.length == 1)
              ? (SampledSet) new SingletonSet(new RealTuple(dateTimes))
              : (SampledSet) DateTime.makeTimeSet(dateTimes);
      FieldImpl fi = new FieldImpl(ft, domainSet);
      fi.setSamples(datas, false);
      return fi;
    } catch (Exception exc) {
      logException("Creating obs", exc);
    }
    return null;
  }

  private boolean compareTimeLists(List<DateTime> timesA, List<DateTime> timesB) {
    if (timesA.size() != timesB.size()) return false;

    Collections.sort(timesA);
    Collections.sort(timesB);
    for (int i = 0; i < timesA.size(); i++) {
      DateTime a = timesA.get(i);
      DateTime b = timesB.get(i);

      double test = Math.abs(a.getValue() - b.getValue());
      if (test > 65.0) return false;
      // System.out.println("TTTTTTT =  " + test );
    }
    return true;
  }

  /**
   * using the time from the adapter to reset the time list
   *
   * @param realDateTimes _more_
   * @param times _more_
   * @return _more_
   */
  private List resetTimesList(DateTime[] realDateTimes, List<DateTime> times) {
    List results = new ArrayList();
    int len = realDateTimes.length;

    HashSet seenTimes = new HashSet();

    try {
      for (DateTime dateTime : times) {
        Date dttm = ucar.visad.Util.makeDate(dateTime);
        long minTimeDiff = -1;
        Date minDate = null;
        for (int i = 0; i < len; i++) {
          Date sourceDate = ucar.visad.Util.makeDate(realDateTimes[i]);
          long timeDiff = Math.abs(sourceDate.getTime() - dttm.getTime());
          if ((minTimeDiff < 0) || (timeDiff < minTimeDiff)) {
            minTimeDiff = timeDiff;
            minDate = sourceDate;
          }
        }
        if ((minDate != null) && !seenTimes.contains(minDate)) {
          results.add(new DateTime(minDate));
          seenTimes.add(minDate);
        }
      }
    } catch (Exception e) {
    }

    return results;
  }

  /**
   * Get the list of adapters.
   *
   * @return list of adapters.
   */
  protected List<RadarAdapter> getRadarAdapters() {
    return getAdapters();
  }

  /** Gets called by the {@link DataManager} when this DataSource has been removed. */
  public void doRemove() {
    if (getAdapters() != null) {
      for (RadarAdapter ra : getAdapters()) {
        ra.doRemove();
      }
    }
    super.doRemove();
  }
}
示例#2
0
/**
 * DataSource for Web Map Servers
 *
 * @author IDV development team
 * @version $Revision: 1.33 $ $Date: 2007/05/04 22:23:20 $
 */
public class WmsDataSource extends DataSourceImpl {

  /** request property */
  public static final String PROP_BOUNDS = "prop.wms.bounds";

  /** request property */
  public static final String PROP_ICONPATH = "prop.wms.iconpath";

  /** request property */
  public static final String PROP_WRITEFILE = "prop.wms.writefile";

  /** request property */
  public static final String PROP_RESOLUTION = "prop.wms.resolution";

  /** request property */
  public static final String PROP_IMAGEWIDTH = "prop.wms.imagewidth";

  /** request property */
  public static final String PROP_IMAGEHEIGHT = "prop.wms.imageheight";

  /** request property */
  public static final String PROP_LAYER = "prop.wms.layer";

  /** request property */
  public static final String PROP_LAYERS = "prop.wms.layers";

  /** categories */
  private List categories = DataCategory.parseCategories("GIS-WMS", false);

  /** This allows us to abort concurrent reads */
  private Object loadId;

  /** List of selections (layers) */
  private List wmsSelections;

  /** _more_ */
  private boolean maintainRatio = false;

  /** _more_ */
  private JCheckBox maintainRatioCbx;

  /**
   * The bytes of the last image we loaded. Keep this around for resonding easily to the write image
   * request
   */
  byte[] lastImageContent;

  /** The url of the last image */
  String lastUrl;

  /** _more_ */
  private List layerList = new ArrayList();

  /** Dummy constructor so this object can get unpersisted. */
  public WmsDataSource() {}

  /**
   * Create a WmsDataSource from the specification given.
   *
   * @param descriptor descriptor for the data source
   * @param selections The selections
   * @param properties extra properties
   * @throws VisADException some problem occurred creating data
   */
  public WmsDataSource(DataSourceDescriptor descriptor, List selections, Hashtable properties)
      throws VisADException {
    super(descriptor, "WMS data source", "WMS data source", properties);
    this.wmsSelections = new ArrayList(selections);
    initWmsDataSource();
  }

  /**
   * _more_
   *
   * @return _more_
   */
  public List getLayerList() {
    return layerList;
  }

  /** Initialize after we have been unpersisted */
  public void initAfterUnpersistence() {
    super.initAfterUnpersistence();

    // Check if we were created with the background layers. If so then use the new list
    if (wmsSelections != null) {
      List backgroundImages = getDataContext().getIdv().getBackgroundImages();
      for (Object o : wmsSelections) {
        if (backgroundImages.contains(o)) {
          wmsSelections = backgroundImages;
          break;
        }
      }
    }

    initWmsDataSource();
  }

  /** Initialization method */
  private void initWmsDataSource() {}

  /** Create the data choices associated with this source. */
  protected void doMakeDataChoices() {
    layerList = new ArrayList();
    for (int i = 0; i < wmsSelections.size(); i++) {
      WmsSelection selection = (WmsSelection) wmsSelections.get(i);
      layerList.add(new TwoFacedObject(selection.getTitle(), selection.getLayer()));
    }
    for (int i = 0; i < wmsSelections.size(); i++) {
      Hashtable properties = new Hashtable();
      properties.put(PROP_LAYERS, layerList);
      properties.put(PROP_LAYER, layerList.get(i));
      properties.put(DataChoice.PROP_ICON, "/auxdata/ui/icons/Earth16.gif");
      WmsSelection selection = (WmsSelection) wmsSelections.get(i);

      addDataChoice(
          new DirectDataChoice(
              this, selection, selection.toString(), selection.toString(), categories, properties));
    }
  }

  /** A local cache */
  List cachedData = new ArrayList();

  /** The urls */
  List cachedUrls = new ArrayList();

  /**
   * Actually get the data identified by the given DataChoce. The default is to call the
   * getDataInner that does not take the requestProperties. This allows other, non unidata.data
   * DataSource-s (that follow the old API) to work.
   *
   * @param dataChoice The data choice that identifies the requested data.
   * @param category The data category of the request.
   * @param dataSelection Identifies any subsetting of the data.
   * @param requestProperties Hashtable that holds any detailed request properties.
   * @return The visad.Data object
   * @throws RemoteException Java RMI problem
   * @throws VisADException VisAD problem
   */
  protected Data getDataInner(
      DataChoice dataChoice,
      DataCategory category,
      DataSelection dataSelection,
      Hashtable requestProperties)
      throws VisADException, RemoteException {

    loadId = JobManager.getManager().stopAndRestart(loadId, "WMSControl");
    Object myLoadId = loadId;

    if (requestProperties == null) {
      requestProperties = new Hashtable();
    }
    WmsSelection wmsInfo = (WmsSelection) dataChoice.getId();

    // Look if there was a layer that overrides the one in the data choice
    Object tfoLayer = requestProperties.get(PROP_LAYER);
    if ((tfoLayer != null) && (tfoLayer instanceof TwoFacedObject)) {
      String layer = ((TwoFacedObject) tfoLayer).getId().toString();
      for (int i = 0; i < wmsSelections.size(); i++) {
        WmsSelection tmpSelection = (WmsSelection) wmsSelections.get(i);
        if (Misc.equals(tmpSelection.getLayer(), layer)) {
          wmsInfo = tmpSelection;
          break;
        }
      }
    }

    GeoLocationInfo boundsToUse = (GeoLocationInfo) requestProperties.get(PROP_BOUNDS);

    Image image = null;
    FieldImpl xyData = null;
    byte[] imageContent = null;

    //        System.err.println(wmsInfo.getImageFile());
    if (wmsInfo.getImageFile() != null) {
      try {
        boundsToUse = new GeoLocationInfo(90, -180, -90, 180);
        InputStream is = IOUtil.getInputStream(wmsInfo.getImageFile());
        imageContent = IOUtil.readBytes(is, myLoadId);
        image = Toolkit.getDefaultToolkit().createImage(imageContent);
        //                javax.swing.JLabel l = new javax.swing.JLabel(new
        // javax.swing.ImageIcon(image));
        //                l.setBackground(Color.red);
        //                ucar.unidata.util.GuiUtils.showOkCancelDialog(null,null, l,null);
        xyData = ucar.visad.Util.makeField(image, 0, false, true);
      } catch (Exception iexc) {
        logException("There was an error accessing the image:\n" + wmsInfo.getImageFile(), iexc);
        return null;
      }
    } else {
      String writeFile = (String) requestProperties.get(PROP_WRITEFILE);

      int imageWidth = Misc.getProperty(requestProperties, PROP_IMAGEWIDTH, 800);
      int imageHeight = Misc.getProperty(requestProperties, PROP_IMAGEHEIGHT, -1);
      double resolution = Misc.getProperty(requestProperties, PROP_RESOLUTION, (float) 1.0);

      if (wmsInfo.getLegendIcon() != null) {
        requestProperties.put(PROP_ICONPATH, wmsInfo.getLegendIcon());
      }
      if (!wmsInfo.getAllowSubsets() || (boundsToUse == null)) {
        boundsToUse = wmsInfo.getBounds();
      } else {
        boundsToUse.rectify(wmsInfo.getBounds(), 0.0);
        boundsToUse.snapToGrid();
        boundsToUse.rectify(wmsInfo.getBounds(), 0.0);
      }

      double widthDegrees = boundsToUse.getMaxLon() - boundsToUse.getMinLon();
      double heightDegrees = boundsToUse.getMaxLat() - boundsToUse.getMinLat();

      if ((widthDegrees == 0) || (heightDegrees == 0)) {
        return null;
      }

      if (wmsInfo.getFixedWidth() > -1) {
        imageWidth = wmsInfo.getFixedWidth();
      }
      if (wmsInfo.getFixedHeight() > -1) {
        imageHeight = wmsInfo.getFixedHeight();
      } else {
        if (imageHeight < 0) {
          imageHeight =
              Math.abs((int) (imageWidth * boundsToUse.getDegreesY() / boundsToUse.getDegreesX()));
        }
      }
      imageWidth = Math.min(Math.max(imageWidth, 50), 2056);
      imageHeight = Math.min(Math.max(imageHeight, 50), 2056);

      if (maintainRatio) {
        imageHeight = (int) (imageWidth * (heightDegrees / widthDegrees));
      }

      double diff = Math.abs(boundsToUse.getMinLon() - boundsToUse.getMaxLon());
      String url =
          wmsInfo.assembleRequest(
              boundsToUse, (int) (imageWidth / resolution), (int) (imageHeight / resolution));

      String cacheGroup = "WMS";
      synchronized (cachedUrls) {
        if (writeFile == null) {
          for (int i = 0; i < cachedUrls.size(); i++) {
            if (url.equals(cachedUrls.get(i))) {
              image = (Image) cachedData.get(i);
              break;
            }
          }
        }
      }

      try {
        if (image == null) {
          if (Misc.equals(url, lastUrl) && (lastImageContent != null)) {
            imageContent = lastImageContent;
          } else {
          }

          if (imageContent == null) {
            long t1 = System.currentTimeMillis();
            //                    System.err.println("getting image:" + url);
            LogUtil.message("Reading WMS image: " + wmsInfo);
            // System.err.println ("url:" + url);

            InputStream is = IOUtil.getInputStream(url);
            long t2 = System.currentTimeMillis();
            imageContent = IOUtil.readBytes(is, myLoadId);
            long t3 = System.currentTimeMillis();
            LogUtil.message("");
            //                    System.err.println("Done");
          }
          // If it is null then there is another thread that is doing
          // a subsequent read
          lastImageContent = null;
          if (imageContent == null) {
            return null;
          }
          Trace.call2("Getting image");
          Trace.call1("Making image");
          image = Toolkit.getDefaultToolkit().createImage(imageContent);
          // Wait on the image
          image = ucar.unidata.ui.ImageUtils.waitOnImage(image);
          if (image == null) {
            throw new IllegalStateException();
          }

          Trace.call2("Making image");
          lastImageContent = imageContent;
          lastUrl = url;
          updateDetailsText();
          if (!JobManager.getManager().canContinue(myLoadId)) {
            Trace.call2("WMSControl.loadImage");
            return null;
          }
          synchronized (cachedUrls) {
            if (cachedUrls.size() > 5) {
              cachedUrls.remove(cachedUrls.size() - 1);
              cachedData.remove(cachedData.size() - 1);
            }
            // For now don't cache
            //      cachedUrls.add(0, url);
            //                    cachedData.add(0, image);
          }
        }
        ImageHelper ih = new ImageHelper();
        int width = image.getWidth(ih);
        if (ih.badImage) {
          throw new IllegalStateException();
        }
        long tt1 = System.currentTimeMillis();

        xyData = ucar.visad.Util.makeField(image, 0, false, true);
        long tt2 = System.currentTimeMillis();
        //      System.err.println("time to make field:" + (tt2-tt1));
      } catch (Exception iexc) {
        if (imageContent != null) {
          String msg = new String(imageContent);
          //  System.err.println ("msg:" + msg);
          /* Check to see if this is of the form:

          <?xml version='1.0' encoding="UTF-8" standalone="no" ?>
          <!DOCTYPE ServiceExceptionReport SYSTEM "http://www.digitalearth.gov/wmt/xml/exception_1_1_0.dtd ">
          <ServiceExceptionReport version="1.1.0">
           <ServiceException>
             Service denied due to system overload. Please try again later.
           </ServiceException>
          </ServiceExceptionReport>

          */
          if (msg.indexOf("<ServiceExceptionReport") >= 0) {
            try {
              StringBuffer errors = new StringBuffer();
              errors.append("\n");
              Element root = XmlUtil.getRoot(msg);
              List children = XmlUtil.findChildren(root, "ServiceException");
              for (int i = 0; i < children.size(); i++) {

                Element node = (Element) children.get(i);
                String code = XmlUtil.getAttribute(node, "code", (String) null);
                String body = XmlUtil.getChildText(node);
                if (code != null) {
                  errors.append(code + "\n");
                }
                errors.append(body.trim() + "\n");
              }
              LogUtil.userErrorMessage(
                  "Error accessing image with the url:\n" + url + "\nError:\n" + errors);
            } catch (Exception exc) {
              LogUtil.userErrorMessage(
                  "Error accessing image with the url:\n"
                      + url
                      + "\nError:\n"
                      + StringUtil.stripTags(msg));
            }
            return null;
          }

          msg = StringUtil.replace(msg, "\n", " ").toLowerCase();
          if (StringUtil.stringMatch(msg, "service\\s*exception")) {
            if (StringUtil.stringMatch(msg, "cannot\\s*be\\s*less\\s*than")) {
              return null;
            }
          }
          if (msg.indexOf("error") >= 0) {
            LogUtil.userErrorMessage(
                "There was an error accessing the image with the url:\n"
                    + url
                    + "\nError:\n"
                    + new String(imageContent));
            return null;
          }
        }
        logException("There was an error accessing the image with the url:\n" + url, iexc);
        return null;
      }

      if (writeFile != null) {
        try {
          ImageXmlDataSource.writeToFile(writeFile, boundsToUse, imageContent, wmsInfo.getFormat());
        } catch (Exception exc) {
          throw new IllegalArgumentException(
              "Error writing image xml file:" + writeFile + " " + exc);
        }
      }
    }

    Linear2DSet domain = (Linear2DSet) xyData.getDomainSet();
    Linear2DSet imageDomain =
        new Linear2DSet(
            RealTupleType.SpatialEarth2DTuple,
            boundsToUse.getMinLon(),
            boundsToUse.getMaxLon(),
            domain.getX().getLength(),
            boundsToUse.getMaxLat(),
            boundsToUse.getMinLat(),
            domain.getY().getLength());

    // System.err.println("image domain:" + imageDomain);

    /*
    new Linear2DSet(RealTupleType.SpatialEarth2DTuple,
                        boundsToUse.getMinLon(), boundsToUse.getMaxLon(),
                        domain.getX().getLength(),
                        boundsToUse.getMinLat() +diff, boundsToUse.getMinLat(),
                        domain.getY().getLength());*/

    FieldImpl field = GridUtil.setSpatialDomain(xyData, imageDomain, true);

    return field;
  }

  /**
   * _more_
   *
   * @param comps _more_
   */
  public void getPropertiesComponents(List comps) {
    super.getPropertiesComponents(comps);
    maintainRatioCbx = new JCheckBox("Match Ration", maintainRatio);
    comps.add(GuiUtils.filler());
    comps.add(GuiUtils.left(maintainRatioCbx));
  }

  /**
   * _more_
   *
   * @return _more_
   */
  public boolean applyProperties() {
    if (!super.applyProperties()) {
      return false;
    }
    maintainRatio = maintainRatioCbx.isSelected();
    return true;
  }

  /**
   * Get the description. This adds on the last url requested.
   *
   * @return description
   */
  public String getFullDescription() {
    StringBuffer sb = new StringBuffer(super.getFullDescription());
    if (lastUrl != null) {
      sb.append("<p><b>Last request:</b> " + lastUrl);
    }

    sb.append(
        "Images<p><table><tr><td><b>Name</b></td><td><b>Layer</b></td><td><b>Server</b></td></tr>\n");
    for (int i = 0; i < wmsSelections.size(); i++) {
      WmsSelection selection = (WmsSelection) wmsSelections.get(i);
      sb.append("<tr><td>");
      sb.append(selection.getTitle());
      sb.append("</td><td>");
      sb.append(selection.getLayer());
      sb.append("</td><td>");
      sb.append(selection.getServer());
      sb.append("</td></tr>");
    }
    sb.append("</table>");

    return sb.toString();
  }

  /**
   * See if this DataSource should cache or not
   *
   * @param data Data to cache
   * @return false
   */
  protected boolean shouldCache(Data data) {
    return false;
  }

  /**
   * Create a list of times for this data source. Since shapefiles don't have any times, return an
   * empty List.
   *
   * @return an empty List
   */
  protected List doMakeDateTimes() {
    return new ArrayList();
  }

  /**
   * Set the WmsSelections property.
   *
   * @param value The new value for WmsSelections
   */
  public void setWmsSelections(List value) {
    wmsSelections = value;
  }

  /**
   * Get the WmsSelections property.
   *
   * @return The WmsSelections
   */
  public List getWmsSelections() {
    return wmsSelections;
  }

  /**
   * overwrite
   *
   * @param o object
   * @return equals
   */
  public boolean equals(Object o) {
    if (!super.equals(o)) {
      return false;
    }
    WmsDataSource that = (WmsDataSource) o;
    return Misc.equals(this.wmsSelections, that.wmsSelections);
  }

  /**
   * Set the MaintainRatio property.
   *
   * @param value The new value for MaintainRatio
   */
  public void setMaintainRatio(boolean value) {
    this.maintainRatio = value;
  }

  /**
   * Get the MaintainRatio property.
   *
   * @return The MaintainRatio
   */
  public boolean getMaintainRatio() {
    return this.maintainRatio;
  }
}