001    /*
002     * $Id: AtcfStormDataSource.java,v 1.1 2012/01/04 20:40:51 tommyj Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.data.cyclone;
032    
033    import java.io.ByteArrayInputStream;
034    import java.io.ByteArrayOutputStream;
035    import java.io.File;
036    import java.io.FileNotFoundException;
037    import java.io.IOException;
038    import java.net.URL;
039    import java.text.SimpleDateFormat;
040    import java.util.ArrayList;
041    import java.util.Calendar;
042    import java.util.Date;
043    import java.util.GregorianCalendar;
044    import java.util.Hashtable;
045    import java.util.List;
046    import java.util.TimeZone;
047    import java.util.zip.GZIPInputStream;
048    
049    import org.apache.commons.net.ftp.FTP;
050    import org.apache.commons.net.ftp.FTPClient;
051    
052    import ucar.unidata.data.BadDataException;
053    import ucar.unidata.data.DataSourceDescriptor;
054    import ucar.unidata.util.DateUtil;
055    import ucar.unidata.util.IOUtil;
056    import ucar.unidata.util.StringUtil;
057    import visad.DateTime;
058    import visad.Real;
059    import visad.RealType;
060    import visad.VisADException;
061    import visad.georef.EarthLocation;
062    import visad.georef.EarthLocationLite;
063    
064    /**
065     */
066    public class AtcfStormDataSource extends StormDataSource {
067    
068            /** _more_ */
069            private int BASEIDX = 0;
070    
071            /** _more_ */
072            private int IDX_BASIN = BASEIDX++;
073    
074            /** _more_ */
075            private int IDX_CY = BASEIDX++;
076    
077            /** _more_ */
078            private int IDX_YYYYMMDDHH = BASEIDX++;
079    
080            /** _more_ */
081            private int IDX_TECHNUM = BASEIDX++;
082    
083            /** _more_ */
084            private int IDX_TECH = BASEIDX++;
085    
086            /** _more_ */
087            private int IDX_TAU = BASEIDX++;
088    
089            /** _more_ */
090            private int IDX_LAT = BASEIDX++;
091    
092            /** _more_ */
093            private int IDX_LON = BASEIDX++;
094    
095            /** _more_ */
096            private int IDX_VMAX = BASEIDX++;
097    
098            /** _more_ */
099            private int IDX_MSLP = BASEIDX++;
100    
101            /** _more_ */
102            private int IDX_TY = BASEIDX++;
103    
104            /** _more_ */
105            private int IDX_RAD = BASEIDX++;
106    
107            /** _more_ */
108            private int IDX_WINDCODE = BASEIDX++;
109    
110            /** _more_ */
111            private int IDX_RAD1 = BASEIDX++;
112    
113            /** _more_ */
114            private int IDX_RAD2 = BASEIDX++;
115    
116            /** _more_ */
117            private int IDX_RAD3 = BASEIDX++;
118    
119            /** _more_ */
120            private int IDX_RAD4 = BASEIDX++;
121    
122            /** _more_ */
123            private int IDX_RADP = BASEIDX++;
124    
125            /** _more_ */
126            private int IDX_RRP = BASEIDX++;
127    
128            /** _more_ */
129            private int IDX_MRD = BASEIDX++;
130    
131            /** _more_ */
132            private int IDX_GUSTS = BASEIDX++;
133    
134            /** _more_ */
135            private int IDX_EYE = BASEIDX++;
136    
137            /** _more_ */
138            private int IDX_SUBREGION = BASEIDX++;
139    
140            /** _more_ */
141            private int IDX_MAXSEAS = BASEIDX++;
142    
143            /** _more_ */
144            private int IDX_INITIALS = BASEIDX++;
145    
146            /** _more_ */
147            private int IDX_DIR = BASEIDX++;
148    
149            /** _more_ */
150            private int IDX_SPEED = BASEIDX++;
151    
152            /** _more_ */
153            private int IDX_STORMNAME = BASEIDX++;
154    
155            /** _more_ */
156            private int IDX_DEPTH = BASEIDX++;
157    
158            /** _more_ */
159            private int IDX_SEAS = BASEIDX++;
160    
161            /** _more_ */
162            private int IDX_SEASCODE = BASEIDX++;
163    
164            /** _more_ */
165            private int IDX_SEAS1 = BASEIDX++;
166    
167            /** _more_ */
168            private int IDX_SEAS2 = BASEIDX++;
169    
170            /** _more_ */
171            private int IDX_SEAS3 = BASEIDX++;
172    
173            /** _more_ */
174            private int IDX_SEAS4 = BASEIDX++;
175    
176            /** _more_ */
177            private static final String PREFIX_ANALYSIS = "a";
178    
179            /** _more_ */
180            private static final String PREFIX_BEST = "b";
181    
182            /** _more_ */
183            private static final String WAY_BEST = "BEST";
184    
185            /** _more_ */
186            private static final String WAY_CARQ = "CARQ";
187    
188            /** _more_ */
189            private static final String WAY_WRNG = "WRNG";
190    
191            /** _more_ */
192            private static String DEFAULT_PATH = "ftp://anonymous:password@ftp.nhc.noaa.gov/atcf";
193    
194            /** _more_ */
195            private String path;
196    
197            /** _more_ */
198            private List<StormInfo> stormInfos;
199    
200            /** _more_ */
201            private StormTrackCollection localTracks;
202    
203            /**
204             * _more_
205             * 
206             * @throws Exception
207             *             _more_
208             */
209            public AtcfStormDataSource() throws Exception {
210            }
211    
212            /**
213             * _more_
214             * 
215             * @return _more_
216             */
217            public String getFullDescription() {
218                    return "ATCF Data Source<br>Path:" + path;
219            }
220    
221            /**
222             * _more_
223             * 
224             * @param descriptor
225             *            _more_
226             * @param url
227             *            _more_
228             * @param properties
229             *            _more_
230             */
231            public AtcfStormDataSource(DataSourceDescriptor descriptor, String url,
232                            Hashtable properties) {
233                    super(descriptor, "ATCF Storm Data", "ATCF Storm Data", properties);
234                    if ((url == null) || (url.trim().length() == 0)
235                                    || url.trim().equalsIgnoreCase("default")) {
236                            url = DEFAULT_PATH;
237                    }
238                    path = url;
239            }
240    
241            /**
242             * _more_
243             * 
244             * @return _more_
245             */
246            public String getId() {
247                    return "atcf";
248            }
249    
250            /**
251             * _more_
252             * 
253             * @param suffix
254             *            _more_
255             * 
256             * @return _more_
257             */
258            private String getFullPath(String suffix) {
259                    return path + "/" + suffix;
260            }
261    
262            /**
263             * _more_
264             */
265            protected void initializeStormData() {
266                    try {
267                            incrOutstandingGetDataCalls();
268                            stormInfos = new ArrayList<StormInfo>();
269                            if (path.toLowerCase().endsWith(".atcf")
270                                            || path.toLowerCase().endsWith(".gz")
271                                            || path.toLowerCase().endsWith(".dat")) {
272                                    String name = IOUtil.stripExtension(IOUtil.getFileTail(path));
273                                    StormInfo si = new StormInfo(name, new DateTime(new Date()));
274                                    stormInfos.add(si);
275                                    localTracks = new StormTrackCollection();
276                                    readTracks(si, localTracks, path, null, true);
277                                    List<StormTrack> trackList = localTracks.getTracks();
278    
279                                    if (trackList.size() > 0) {
280                                            si.setStartTime(trackList.get(0).getStartTime());
281                                    }
282                                    return;
283                            }
284    
285                            byte[] techs = readFile(getFullPath("nhc_techlist.dat"), true);
286                            if (techs != null) {
287                                    /*
288                                     * NUM TECH ERRS RETIRED COLOR DEFAULTS INT-DEFS RADII-DEFS
289                                     * LONG-NAME 00 CARQ 0 0 0 0 0 1 Combined ARQ Position 00 WRNG 0
290                                     * 0 0 0 0 1 Warning
291                                     */
292                                    int cnt = 0;
293                                    for (String line : StringUtil.split(new String(techs), "\n",
294                                                    true, true)) {
295                                            if (cnt++ == 0) {
296                                                    continue;
297                                            }
298                                            if (line.length() > 67) {
299                                                    String id = line.substring(3, 10).trim();
300                                                    String name = line.substring(67).trim();
301                                                    // System.out.println (id + ":" +name);
302                                                    getWay(id, name);
303                                            }
304                                    }
305                            }
306    
307                            // byte[] bytes = readFile(getFullPath("archive/storm.table"),
308                            byte[] bytes = readFile(getFullPath("index/storm_list.txt"), false);
309                            String stormTable = new String(bytes);
310                            List lines = StringUtil.split(stormTable, "\n", true, true);
311    
312                            SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHH");
313                            fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
314                            for (int i = 0; i < lines.size(); i++) {
315                                    String line = (String) lines.get(i);
316                                    List toks = StringUtil.split(line, ",", true);
317                                    String name = (String) toks.get(0);
318                                    String basin = (String) toks.get(1);
319                                    String number = (String) toks.get(7);
320                                    String year = (String) toks.get(8);
321                                    int y = new Integer(year).intValue();
322                                    String id = basin + "_" + number + "_" + year;
323                                    if (name.equals("UNNAMED")) {
324                                            name = id;
325                                    }
326                                    String dttm = (String) toks.get(11);
327                                    Date date = fmt.parse(dttm);
328                                    StormInfo si = new StormInfo(id, name, basin, number,
329                                                    new DateTime(date));
330                                    stormInfos.add(si);
331    
332                            }
333                    } catch (Exception exc) {
334                            logException("Error initializing ATCF data", exc);
335                    } finally {
336                            decrOutstandingGetDataCalls();
337                    }
338            }
339    
340            /**
341             * _more_
342             * 
343             * @return _more_
344             */
345            public List<StormInfo> getStormInfos() {
346                    return stormInfos;
347            }
348    
349            /**
350             * _more_
351             * 
352             * @param s
353             *            _more_
354             * 
355             * @return _more_
356             */
357            private double getDouble(String s) {
358                    if (s == null) {
359                            return Double.NaN;
360                    }
361                    if (s.length() == 0) {
362                            return Double.NaN;
363                    }
364                    return new Double(s).doubleValue();
365            }
366    
367            /**
368             * _more_
369             * 
370             * @throws VisADException
371             *             _more_
372             */
373            protected void initParams() throws VisADException {
374                    super.initParams();
375                    if (obsParams == null) {
376                            obsParams = new StormParam[] { PARAM_STORMCATEGORY,
377                                            PARAM_MINPRESSURE, PARAM_MAXWINDSPEED_KTS };
378    
379                    }
380            }
381    
382            /**
383             * _more_
384             * 
385             * @param stormInfo
386             *            _more_
387             * @param tracks
388             *            _more_
389             * @param trackFile
390             *            _more_
391             * @param waysToUse
392             *            _more_
393             * @param throwError
394             *            _more_
395             * 
396             * 
397             * @return _more_
398             * @throws Exception
399             *             _more_
400             */
401            private boolean readTracks(StormInfo stormInfo,
402                            StormTrackCollection tracks, String trackFile,
403                            Hashtable<String, Boolean> waysToUse, boolean throwError)
404                            throws Exception {
405    
406                    long t1 = System.currentTimeMillis();
407                    byte[] bytes = readFile(trackFile, true);
408                    long t2 = System.currentTimeMillis();
409                    // System.err.println("read time:" + (t2 - t1));
410                    boolean isZip = trackFile.endsWith(".gz");
411                    if ((bytes == null) && isZip) {
412                            String withoutGZ = trackFile.substring(0, trackFile.length() - 3);
413                            bytes = readFile(withoutGZ, true);
414                            isZip = false;
415                    }
416    
417                    if (bytes == null) {
418                            if (throwError) {
419                                    throw new BadDataException("Unable to read track file:"
420                                                    + trackFile);
421                            }
422                            return false;
423                    }
424    
425                    if (isZip) {
426                            GZIPInputStream zin = new GZIPInputStream(new ByteArrayInputStream(
427                                            bytes));
428                            bytes = IOUtil.readBytes(zin);
429                            zin.close();
430                    }
431                    GregorianCalendar convertCal = new GregorianCalendar(
432                                    DateUtil.TIMEZONE_GMT);
433                    convertCal.clear();
434    
435                    String trackData = new String(bytes);
436                    List lines = StringUtil.split(trackData, "\n", true, true);
437                    SimpleDateFormat fmt = new SimpleDateFormat("yyyyMMddHH");
438                    fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
439                    Hashtable trackMap = new Hashtable();
440                    Real altReal = new Real(RealType.Altitude, 0);
441                    // System.err.println("obs:" + lines.size());
442                    /*
443                     * Hashtable okWays = new Hashtable(); okWays.put(WAY_CARQ, "");
444                     * okWays.put(WAY_WRNG, ""); okWays.put(WAY_BEST, ""); okWays.put("ETA",
445                     * ""); okWays.put("NGX", ""); okWays.put("BAMS", "");
446                     */
447                    Hashtable seenDate = new Hashtable();
448                    initParams();
449                    int xcnt = 0;
450                    for (int i = 0; i < lines.size(); i++) {
451                            String line = (String) lines.get(i);
452                            if (i == 0) {
453                                    // System.err.println(line);
454                            }
455                            List toks = StringUtil.split(line, ",", true);
456                            /*
457                             * System.err.println(toks.size() + " " + BASEIDX);
458                             * if(toks.size()<BASEIDX-1) { System.err.println("bad line:" +
459                             * line); continue; } else { System.err.println("good line:" +
460                             * line); }
461                             */
462    
463                            // BASIN,CY,YYYYMMDDHH,TECHNUM,TECH,TAU,LatN/S,LonE/W,VMAX,MSLP,TY,RAD,WINDCODE,RAD1,RAD2,RAD3,RAD4,RADP,RRP,MRD,GUSTS,EYE,SUBREGION,MAXSEAS,INITIALS,DIR,SPEED,STORMNAME,DEPTH,SEAS,SEASCODE,SEAS1,SEAS2,SEAS3,SEAS4
464                            // AL, 01, 2007050612, , BEST, 0, 355N, 740W, 35, 1012, EX, 34, NEQ,
465                            // 0, 0, 0, 120,
466                            // AL, 01, 2007050812, 01, CARQ, -24, 316N, 723W, 55, 0, DB, 34,
467                            // AAA, 0, 0, 0, 0,
468    
469                            String dateString = (String) toks.get(IDX_YYYYMMDDHH);
470                            String wayString = (String) toks.get(IDX_TECH);
471                            // if (okWays.get(wayString) == null) {
472                            // continue;
473                            // }
474                            boolean isBest = wayString.equals(WAY_BEST);
475                            boolean isWarning = wayString.equals(WAY_WRNG);
476                            boolean isCarq = wayString.equals(WAY_CARQ);
477    
478                            int category = ((IDX_TY < toks.size()) ? getCategory((String) toks
479                                            .get(IDX_TY)) : CATEGORY_XX);
480                            if (category != CATEGORY_XX) {
481                                    // System.err.println("cat:" + category);
482                            }
483    
484                            String fhour = (String) toks.get(IDX_TAU);
485                            int forecastHour = new Integer(fhour).intValue();
486                            // A hack - we've seen some atfc files that have a 5 character
487                            // forecast hour
488                            // right padded with "00", eg., 01200
489                            if ((fhour.length() == 5) && (forecastHour > 100)) {
490                                    forecastHour = forecastHour / 100;
491                            }
492    
493                            if (isWarning || isCarq) {
494                                    forecastHour = -forecastHour;
495                            }
496    
497                            // Check for unique dates for this way
498                            String dttmkey = wayString + "_" + dateString + "_" + forecastHour;
499                            if (seenDate.get(dttmkey) != null) {
500                                    continue;
501                            }
502                            seenDate.put(dttmkey, dttmkey);
503    
504                            Date dttm = fmt.parse(dateString);
505                            convertCal.setTime(dttm);
506                            String key;
507                            Way way = getWay(wayString, null);
508                            if (!isBest && (waysToUse != null) && (waysToUse.size() > 0)
509                                            && (waysToUse.get(wayString) == null)) {
510                                    continue;
511                            }
512    
513                            if (isBest) {
514                                    key = wayString;
515                            } else {
516                                    key = wayString + "_" + dateString;
517                                    convertCal.add(Calendar.HOUR_OF_DAY, forecastHour);
518                            }
519                            dttm = convertCal.getTime();
520                            StormTrack track = (StormTrack) trackMap.get(key);
521                            if (track == null) {
522                                    way = (isBest ? Way.OBSERVATION : way);
523                                    track = new StormTrack(stormInfo, addWay(way), new DateTime(
524                                                    dttm), obsParams);
525                                    trackMap.put(key, track);
526                                    tracks.addTrack(track);
527                            }
528                            String latString = (String) toks.get(IDX_LAT);
529                            String lonString = (String) toks.get(IDX_LON);
530                            String t = latString + " " + lonString;
531    
532                            boolean south = latString.endsWith("S");
533                            boolean west = lonString.endsWith("W");
534                            double latitude = Double.parseDouble(latString.substring(0,
535                                            latString.length() - 1)) / 10.0;
536                            double longitude = Double.parseDouble(lonString.substring(0,
537                                            lonString.length() - 1)) / 10.0;
538                            if (south) {
539                                    latitude = -latitude;
540                            }
541                            if (west) {
542                                    longitude = -longitude;
543                            }
544    
545                            EarthLocation elt = new EarthLocationLite(new Real(
546                                            RealType.Latitude, latitude), new Real(RealType.Longitude,
547                                            longitude), altReal);
548    
549                            List<Real> attributes = new ArrayList<Real>();
550    
551                            double windspeed = ((IDX_VMAX < toks.size()) ? getDouble((String) toks
552                                            .get(IDX_VMAX))
553                                            : Double.NaN);
554                            double pressure = ((IDX_MSLP < toks.size()) ? getDouble((String) toks
555                                            .get(IDX_MSLP))
556                                            : Double.NaN);
557                            attributes.add(PARAM_STORMCATEGORY.getReal((double) category));
558                            attributes.add(PARAM_MINPRESSURE.getReal(pressure));
559                            attributes.add(PARAM_MAXWINDSPEED_KTS.getReal(windspeed));
560    
561                            StormTrackPoint stp = new StormTrackPoint(elt, new DateTime(dttm),
562                                            forecastHour, attributes);
563    
564                            track.addPoint(stp);
565                    }
566                    return true;
567            }
568    
569            /**
570             * _more_
571             * 
572             * @return _more_
573             */
574            public String getWayName() {
575                    return "Tech";
576            }
577    
578            /**
579             * _more_
580             * 
581             * @param stormInfo
582             *            _more_
583             * @param waysToUse
584             *            _more_
585             * @param observationWay
586             *            _more_
587             * 
588             * @return _more_
589             * 
590             * @throws Exception
591             *             _more_
592             */
593            public StormTrackCollection getTrackCollectionInner(StormInfo stormInfo,
594                            Hashtable<String, Boolean> waysToUse, Way observationWay)
595                            throws Exception {
596                    if (localTracks != null) {
597                            return localTracks;
598                    }
599    
600                    long t1 = System.currentTimeMillis();
601                    StormTrackCollection tracks = new StormTrackCollection();
602    
603                    String trackFile;
604                    boolean justObs = (waysToUse != null) && (waysToUse.size() == 1)
605                                    && (waysToUse.get(Way.OBSERVATION.toString()) != null);
606                    int nowYear = new GregorianCalendar(DateUtil.TIMEZONE_GMT)
607                                    .get(Calendar.YEAR);
608                    int stormYear = getYear(stormInfo.getStartTime());
609                    // If its the current year then its in the aid_public dir
610                    String aSubDir = ((stormYear == nowYear) ? "aid_public"
611                                    : ("archive/" + stormYear));
612                    String bSubDir = ((stormYear == nowYear) ? "btk"
613                                    : ("archive/" + stormYear));
614                    if (!justObs) {
615                            trackFile = getFullPath(aSubDir + "/" + PREFIX_ANALYSIS
616                                            + stormInfo.getBasin().toLowerCase()
617                                            + stormInfo.getNumber() + stormYear + ".dat.gz");
618                            // What we think might be in the archive might actually be the last
619                            // year
620                            // and they haven't moved it into the archive
621                            try {
622                                    readTracks(stormInfo, tracks, trackFile, waysToUse, true);
623                            } catch (BadDataException bde) {
624                                    if (!aSubDir.equals("aid_public")) {
625                                            try {
626                                                    trackFile = getFullPath("aid_public/" + PREFIX_ANALYSIS
627                                                                    + stormInfo.getBasin().toLowerCase()
628                                                                    + stormInfo.getNumber() + stormYear + ".dat.gz");
629                                                    readTracks(stormInfo, tracks, trackFile, waysToUse,
630                                                                    true);
631                                            } catch (BadDataException bde2) {
632                                                    System.err.println("Failed reading 'A' file for storm:"
633                                                                    + stormInfo + " file:" + trackFile);
634                                            }
635                                    }
636                                    // System.err.println("Failed reading 'A' file for storm:" +
637                                    // stormInfo+" file:" + trackFile);
638                            }
639                    }
640                    // Now read the b"est file
641                    trackFile = getFullPath(bSubDir + "/" + PREFIX_BEST
642                                    + stormInfo.getBasin().toLowerCase() + stormInfo.getNumber()
643                                    + stormYear + ".dat.gz");
644                    try {
645                            readTracks(stormInfo, tracks, trackFile, null, true);
646                    } catch (BadDataException bde) {
647                            if (!bSubDir.equals("btk")) {
648                                    try {
649                                            trackFile = getFullPath("btk/" + PREFIX_BEST
650                                                            + stormInfo.getBasin().toLowerCase()
651                                                            + stormInfo.getNumber() + stormYear + ".dat.gz");
652                                            readTracks(stormInfo, tracks, trackFile, null, true);
653                                    } catch (BadDataException bde2) {
654                                            System.err.println("Failed reading 'B' file for storm:"
655                                                            + stormInfo + " file:" + trackFile);
656                                    }
657    
658                            }
659                            // System.err.println("Failed reading 'B' file for storm:" +
660                            // stormInfo+" file:" + trackFile);
661                    }
662                    long t2 = System.currentTimeMillis();
663                    // System.err.println("time: " + (t2 - t1));
664    
665                    return tracks;
666            }
667    
668            /**
669             * Set the Directory property.
670             * 
671             * @param value
672             *            The new value for Directory
673             */
674            public void setPath(String value) {
675                    path = value;
676            }
677    
678            /**
679             * Get the Directory property.
680             * 
681             * @return The Directory
682             */
683            public String getPath() {
684                    return path;
685            }
686    
687            /**
688             * _more_
689             * 
690             * @param file
691             *            _more_
692             * @param ignoreErrors
693             *            _more_
694             * 
695             * @return _more_
696             * 
697             * @throws Exception
698             *             _more_
699             */
700            private byte[] readFile(String file, boolean ignoreErrors) throws Exception {
701                    if (new File(file).exists()) {
702                            return IOUtil.readBytes(IOUtil.getInputStream(file, getClass()));
703                    }
704                    if (!file.startsWith("ftp:")) {
705                            if (ignoreErrors) {
706                                    return null;
707                            }
708                            throw new FileNotFoundException("Could not read file: " + file);
709                    }
710    
711                    URL url = new URL(file);
712                    FTPClient ftp = new FTPClient();
713                    try {
714                            ftp.connect(url.getHost());
715                            ftp.login("anonymous", "password");
716                            ftp.setFileType(FTP.IMAGE_FILE_TYPE);
717                            ftp.enterLocalPassiveMode();
718                            ByteArrayOutputStream bos = new ByteArrayOutputStream();
719                            if (ftp.retrieveFile(url.getPath(), bos)) {
720                                    return bos.toByteArray();
721                            } else {
722                                    throw new IOException("Unable to retrieve file:" + url);
723                            }
724                    } catch (org.apache.commons.net.ftp.FTPConnectionClosedException fcce) {
725                            System.err.println("ftp error:" + fcce);
726                            System.err.println(ftp.getReplyString());
727                            if (!ignoreErrors) {
728                                    throw fcce;
729                            }
730                            return null;
731                    } catch (Exception exc) {
732                            if (!ignoreErrors) {
733                                    throw exc;
734                            }
735                            return null;
736                    } finally {
737                            try {
738                                    ftp.logout();
739                            } catch (Exception exc) {
740                            }
741                            try {
742                                    ftp.disconnect();
743                            } catch (Exception exc) {
744                            }
745    
746                    }
747            }
748    
749    }