Package net.sourceforge.gpstools

Source Code of net.sourceforge.gpstools.ExifLocator$PhotoWpt

package net.sourceforge.gpstools;

/* gpsdings
* Copyright (C) 2006-2007 Moritz Ringler
* $Id: ExifLocator.java 441 2010-12-13 20:04:20Z ringler $
*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import java.awt.Dimension;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.File;
import java.io.BufferedOutputStream;
import java.io.PrintStream;
import java.io.BufferedInputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.text.DateFormat;
import java.util.TimeZone;
import java.util.Date;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Locale;
import java.util.Set;
import java.util.Comparator;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import net.sourceforge.gpstools.exif.*;
import net.sourceforge.gpstools.xmp.*;
import net.sourceforge.gpstools.utils.*;
import net.sourceforge.gpstools.gpx.*;
import net.sourceforge.gpstools.kml.GpxKMLWriter;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.OptionBuilder;
import org.xml.sax.SAXException;

//ToDo: consider separating a) geo-locating jpegs b) producing output etc.
/**
* Geo-references jpeg files based on their exif information. ExifLocator can
* use GPS Exif data if present or the OriginalDateTime Exif tag in conjunction
* with a time-to-location mapping established from one or more gps tracklogs in
* GPX format. This class can produce several forms of output from the
* geo-referenced jpegs, and it can write the gps information back to the jpeg
* files (either their exif headers or directly into the image). ExifLocator can
* be used via its command line interface or via the API methods documented
* below.
**/
public class ExifLocator extends GPSDings {
    private final TrackInterpolator interp = new TrackInterpolator();
    private GpsExifWriter exifWriter = null;
    private Dimension imageDimension;
    private JSTemplateHandler jsHandler;
    private CalibratedExifReader exifReader;
    private GpsPhotoStampFormat stampformat;
    private JpegTransformer transformer;
    private boolean addTrackView = false;
    private File targetDir;

    private final static Comparator<WptType> defaultComparator = new Comparator<WptType>() {
        @Override
        public int compare(WptType pt1, WptType pt2) {
            final Date d1 = pt1.getTime();
            final Date d2 = pt2.getTime();
            int result = 0;
            if (d1 == null) {
                result = (d2 == null) ? compareByHashCode(pt1, pt2) : -1;
            } else if (d2 == null) {
                result = 1;
            } else {
                result = d1.compareTo(d2);
                if (result == 0) {
                    result = compareByHashCode(pt1, pt2);
                }
            }
            return result;
        }

        private int compareByHashCode(WptType pt1, WptType pt2) {
            return pt1.hashCode() - pt2.hashCode();
        }
    };
    private Set<PhotoWpt> data = new TreeSet<PhotoWpt>(defaultComparator);
    private List<GpxType> gpxTrks = null;

    private synchronized boolean hasGPX() {
        return gpxTrks != null;
    }

    /** Constructs a new instance of this class. */
    public ExifLocator() {
        // sole constructor
    }

    /**
     * Adds the track data in <code>gpx</code> to the internal interpolator.
     * This data will be used in future calls to {@link #locate locate} to find
     * the geolocation of digital photographs from their time stamps. There is
     * currently no way to remove GPX data that you added. You must construct a
     * new ExifLocator.
     *
     * @param gpx
     *            GPX data with timed gps tracks to use for geo-locating digital
     *            photographs; can be read from a GPX file <code>f</code>
     *            through
     *            <code>(GpxType) org.exolab.castor.xml.Unmarshaller.unmarshal({@link  net.sourceforge.gpstools.gpx.Gpx Gpx.class},
        new {@link org.xml.sax.InputSource InputSource}(new {@link java.io.FileInputStream FileInputStream}(f)))</code>
     */
    public synchronized void addGPX(GpxType gpx) {
        if (gpx.getTrkCount() > 0) {
            if (gpxTrks == null) {
                gpxTrks = new ArrayList<GpxType>();
            }
            gpxTrks.add(gpx);
            interp.addAllTracks(gpx);
        }
    }

    /** Returns whether a track view is added to the images. */
    public boolean getAddTrackView() {
        return addTrackView;
    }

    /** Sets whether a track view is added to the images. */
    public void setAddTrackView(boolean b) {
        addTrackView = b;
    }

    /**
     * (Re-)sets the collection of geolocated jpegs of this locator from
     * <code>newdata</code>.
     *
     * @param newdata
     *            a map of jpeg files and their associated geolocations
     * @see #setData(Set) setData(Set&lt;PhotoWpt&gt; data)
     */
    public void setData(Map<File, Wpt> newdata) {
        newdata.getClass(); // if data is null we throw a NullPointerException
        data.clear();
        for (File jpeg : newdata.keySet()) {
            data.add(new PhotoWpt(jpeg, newdata.get(jpeg)));
        }
    }

    /**
     * (Re-)sets the collection of geolocated jpegs of this locator from
     * <code>newdata</code>.
     *
     * @param newdata
     *            a set of geo-located photographs
     * @see #setData(Map) setData(Map&lt;File, Wpt&gt; data)
     * @see #locate locate
     * @see #transformJpegs transformJpegs
     */
    public void setData(Set<PhotoWpt> newdata) {
        data.clear();
        data.addAll(newdata);
    }

    /**
     * Sets the preferred image dimension of this locator to the specified width
     * and height. The specified width and height are used in javascript output
     * and for scaling down jpegs in kmz mode or when a non-null target
     * directory has been specified. option is specified on the command line.
     *
     * @param w
     *            the new preferred image width
     * @param h
     *            the new preferred image height
     * @see ExifLocator#print(Output)
     * @see #transformJpegs transformJpegs
     * @see #maybeTransformJpeg maybeTransformJpeg
     */
    public void setImageDimension(int w, int h) {
        if (w <= 0 || h <= 0) {
            throw new IllegalArgumentException(
                    "Both image width and height must be larger than zero.");
        }
        imageDimension = new Dimension(w, h);
    }

    /**
     * Tries to geolocate the specified jpeg file. If <code>readExifTag</code>
     * is <code>true</code> this exif locator will first look for EXIF GPS
     * information and use it if present. If such information is not available
     * or if <code>readExifTag</code> is <code>false</code> this exif locator
     * will try to find the geo-location of the jpeg by reading the
     * OriginalDateTime from the jpeg's EXIF tag, converting it to UTC and using
     * the UTC-to-geolocation mapping established from the gps tracks added with
     * {@link #addGPX addGPX} to find the corresponding geolocation. The
     * returned PhotoWpt is <em>not</em> automatically added to this exif
     * locator's collection of geo-located pictures, use {@link #setData(Set)
     * setData}.
     *
     * @param jpeg
     *            the jpeg file to locate. Must have an exif header with at
     *            least an OriginalDateTime tag.
     * @param readExifTag
     *            whether to read EXIF GPS information if present
     * @return the geo-located jpeg or <code>null</code> if the jpeg couldn't be
     *         geo-located.
     */
    public PhotoWpt locate(File jpeg, boolean readExifTag) {
        message("Locating " + jpeg);

        /* read exif or xmp gps tag if it exists */
        Wpt wpt = null;
        if (readExifTag) {
            try {
                wpt = getExifReader().readGPSTag(jpeg);
            } catch (Exception ex) {
                handleException(null, ex);
            }
        }

        /* try to locate the picture using its timestamp and the gpx tracks */
        if ((wpt == null) && hasGPX()) {
            try {
                wpt = interp.getWpt(getExifReader().readOriginalUTCTime(jpeg));
            } catch (org.apache.commons.math.FunctionEvaluationException fee) {
                if (fee.getMessage().indexOf("Argument outside domain") >= 0) {
                    String d = null;
                    try {
                        d = (new SimpleDateFormat(ISO_DATE + 'Z'))
                                .format(getExifReader().readOriginalUTCTime(
                                        jpeg));
                    } catch (Exception wonthappen) {
                        throw new Error(wonthappen);
                    }
                    message("The point in time this picture was taken ("
                            + d
                            + ") does not fall into the period covered by the current gps track data.");
                }
            } catch (Exception ex) {
                handleException(null, ex);
            }
        }

        /* return the result */
        return (wpt == null) ? null : new PhotoWpt(jpeg, wpt);
    }

    /**
     * Generates output.
     *
     * @throws IOException
     *             if an I/O error occurs.
     */
    @Override
    public void print() throws IOException {
        if (output.size() == 0) {
            print(Output.Text);
        } else {
            super.print();
        }
    }

    /**
     * Generates output of the specified type.
     *
     * @param out
     *            the type of output to produce.
     * @throws IOException
     *             if an I/O error occurs.
     */
    @Override
    public void print(Output out) throws IOException {
        if (data.isEmpty()) {
            message("The list of geo-located pictures is empty. No output is generated.");
            return;
        }
        File f = output.get(out);
        OutputStream os = (f == null) ? System.out : new BufferedOutputStream(
                new FileOutputStream(f));
        try {
            switch (out) {
            case XML:
                printGPX(os);
                break;
            case Text:
                printPlainText(os);
                break;
            case MapJS:
                printJS(os);
                break;
            case KMZ:
                createKMZ(os);
                break;
            default:
                printKML(os);
            }
        } finally {
            if (os != System.out) {
                os.close();
            }
        }
    }

    private void printKML(OutputStream out) throws IOException {
        message("Writing KML");
        try {
            Gpx gpx = new Gpx();
            for (PhotoWpt wpt : data) {
                Wpt photoPoint = GPXUtils.getInstance().copyWpt(wpt);
                String photo = wpt.getJpeg().getAbsolutePath();
                photoPoint.setDesc("<p>" + photo + "</p><p><a href=\"" + photo
                        + "\">" + "<img src=\"" + photo
                        + "\" width=\"200\" height=\"150\">"
                        + "<br>Show image in web browser</a></p>");
                gpx.addWpt(photoPoint);
            }
            /* GpxKMLWriter automatically uses UTF-8 encoding */
            GpxKMLWriter kw = new GpxKMLWriter(out, "GPSDings Pictures", false);
            kw.writeGpx(gpx);
            kw.endDocument();
        } catch (SAXException ex) {
            throw new IOException("SAXException: " + ex.getMessage());
        }
    }

    private static void addJpeg(InputStream in, String entry, byte[] buffer,
            ZipOutputStream zippo) throws IOException {
        final int bufflength = buffer.length;
        try {
            zippo.putNextEntry(new ZipEntry(entry));
            int count = 0;
            while ((count = in.read(buffer, 0, bufflength)) != -1) {
                zippo.write(buffer, 0, count);
            }
        } finally {
            try {
                zippo.closeEntry();
            } finally {
                in.close();
            }
        }
    }

    private synchronized JpegTransformer getJpegTransformer() {
        if (transformer == null) {
            transformer = new JpegTransformer();
        }
        return transformer;
    }

    /**
     * Sets the stamp format that will be used to write text into geo-located
     * jpegs.
     *
     * @param pattern
     *            the stamp pattern to use. If <code>pattern</code> is
     *            <code>null</code> text stamping will be de-activated.
     * @param loc
     *            the locale to use for formatting numeric variables in the
     *            pattern. <code>null</code> is equivalent to
     *            {@link java.util.Locale#getDefault}.
     * @see net.sourceforge.gpstools.utils.GpsPhotoStampFormat
     *      GpsPhotoStampFormat
     * @see #transformJpegs transformJpegs
     * @see #maybeTransformJpeg maybeTransformJpeg
     **/
    public synchronized void setStampFormat(String pattern, Locale loc) {
        if (pattern == null) {
            stampformat = null;
        } else {
            stampformat = new GpsPhotoStampFormat(pattern, loc);
        }
    }

    /**
     * Sets the target directory where transformed jpeg files will be stored.
     *
     * @param f
     *            the directory to store processed jpegs in or <code>null</code>
     *            if jpegs should be processed in place.
     * @see #transformJpegs transformJpegs
     * @see #maybeTransformJpeg maybeTransformJpeg
     */
    public void setTargetDir(File f) {
        targetDir = f;
    }

    private synchronized String getStampText(Wpt wpt) {
        return (stampformat == null) ? null : stampformat.format(wpt);
    }

    private void createKMZ(OutputStream out) throws IOException {
        message("Writing KMZ");
        Map<String, String> resourceMap = new TreeMap<String, String>();
        ZipOutputStream zippo = new ZipOutputStream(out);
        byte[] buffer = new byte[65536];
        try {
            Gpx gpx = new Gpx();
            JpegTransformer imgTransformer = getJpegTransformer();
            for (PhotoWpt wpt : data) {
                Wpt photoPoint = GPXUtils.getInstance().copyWpt(wpt);
                String resourceBase = "photos/" + wpt.getJpeg().getName();
                String resource = resourceBase;
                String path = wpt.getJpeg().getAbsolutePath();
                for (int count = 1; resourceMap.get(resource) != null
                        && !resourceMap.get(resource).equals(path); count++) {
                    resource = resourceBase + "-" + count;
                }
                if (resourceMap.get(resource) == null) {
                    message("Adding " + path + " as " + resource);
                    InputStream image = imgTransformer.transform(wpt.getJpeg(),
                            getStampText(wpt), imageDimension);
                    addJpeg(image, resource, buffer, zippo);
                    resourceMap.put(resource, path);
                }
                photoPoint.setDesc("<p><img src=\"" + resource + "\">");
                gpx.addWpt(photoPoint);
            }
            if (imgTransformer != null) {
                imgTransformer.releaseResources();
            }

            /* GpxKMLWriter automatically uses UTF-8 encoding */
            zippo.putNextEntry(new ZipEntry("photo.kml"));
            BufferedOutputStream bos = new BufferedOutputStream(zippo);
            GpxKMLWriter kw = new GpxKMLWriter(bos, "GPSDings Pictures", false);
            kw.writeGpx(gpx);
            kw.endDocument();
            bos.flush();
            zippo.closeEntry();
            zippo.close();
        } catch (SAXException ex) {
            throw new IOException("SAXException: " + ex.getMessage());
        }
    }

    private void printGPX(OutputStream out) throws IOException {
        message("Writing GPX");
        Gpx gpx = new Gpx();
        gpx.setVersion("1.1");
        gpx.setCreator("net.sourceforge.gpstools.ExifLocator");
        GPXUtils gutil = GPXUtils.getInstance();
        for (Wpt pt : data) {
            // We must use copyWpt here to have an object of
            // class Wpt.class, otherwise XML marshalling will not work
            // since the marshalling descriptor is not inherited. Maybe we could
            // also add an entry for PhotoWpt in .castor.cdr
            // instead
            gpx.addWpt(Wpt.class.equals(pt.getClass()) ? pt : gutil.copyWpt(pt));
        }
        GPSDings.writeGPX(gpx, out);
    }

    private void printPlainText(OutputStream out) throws IOException {
        message("Writing plain text");
        PrintStream ps = new PrintStream(out, true);
        for (Wpt pt : data) {
            interp.printWpt(ps, pt);
        }
    }

    private synchronized void printJS(OutputStream out) throws IOException {
        message("Writing Google Maps Javascript");
        if (jsHandler == null) {
            jsHandler = new JSTemplateHandler();
        }
        getTemplateProcessor().readWriteTemplate(
                "/net/sourceforge/gpstools/res/ExiflocPhoto.js.template", out,
                jsHandler);
    }

    /**
     * Adds GPS EXIF information to the headers of the geo-located jpegs if
     * there is a non-null GpsExifWriter.
     *
     * @see #setData(Map) setData
     * @see #setData(Set) setData
     * @see #setGpsExifWriter setGpsExifWriter
     */
    public void maybeUpdateExif() {
        if (exifWriter != null) {
            File jpeg;
            long time = System.nanoTime();
            int n = data.size();
            String type = (exifWriter instanceof XMPWriter) ? "XMP" : "Exif";
            String msg = "Writing " + type + " GPS info to ";
            for (PhotoWpt wpt : data) {
                jpeg = wpt.getJpeg();
                message(msg + jpeg);
                if (!exifWriter.writeExifGpsInfo(jpeg, wpt, true)) {
                    message(msg + jpeg + " failed.");
                    n--;
                }
            }
            if (n != 0) {
                System.err.println(type + " gps info written to " + n
                        + " images");
                System.err.println("Average time per image "
                        + (System.nanoTime() - time) / 1e9 / n + " s");
            }
        }
    }

    /**
     * Copies, resizes, and/or text-stamps the geo-located jpegs.
     *
     * @see #setData(Map) setData
     * @see #setData(Set) setData
     * @see #setTargetDir setTargetDir
     * @see #setImageDimension setImageDimension
     * @see #setStampFormat setStampFormat
     * @see #setTextCorner(String) setTextCorner
     * @see #setTextCorner(int) setTextCorner
     * @see #setTextAlpha setTextAlpha
     * @return the set of geo-located jpegs after applying the current
     *         transformations. If the exif locator has a non-null
     *         {@link #setTargetDir targetDir directory} the file paths of the
     *         transformed jpegs will differ from those of the input files.
     */
    public Set<PhotoWpt> transformJpegs() throws IOException {
        Set<PhotoWpt> newpts = new TreeSet<PhotoWpt>(defaultComparator);
        if (targetDir == null) {
            newpts.addAll(data);
        } else {
            JpegTransformer t = getJpegTransformer();
            GPXPath tracks = null;
            if (hasGPX() && getAddTrackView()) {
                tracks = new GPXPath(gpxTrks);
            }
            for (PhotoWpt wpt : data) {
                PhotoWpt pt = maybeTransformJpeg(wpt, t, tracks);
                if (pt != null) {
                    newpts.add(pt);
                }
            }
            t.releaseResources();
        }
        return newpts;
    }

    /**
     * Copies, resizes, and/or text-stamps a geo-located jpeg using the
     * specified transformer.
     *
     * @param pt
     *            the geo-located jpeg to transform
     * @param t
     *            the transformer to use
     * @see #setTargetDir setTargetDir
     * @see #setImageDimension setImageDimension
     * @see #setStampFormat setStampFormat
     * @return the geo-located jpeg after applying the current transformations.
     *         If the exif locator has a non-null {@link #setTargetDir target
     *         directory} the file path of the transformed jpeg will differ from
     *         that of the input file.
     */
    public PhotoWpt maybeTransformJpeg(PhotoWpt pt, JpegTransformer t,
            GPXPath tracks) throws IOException {
        PhotoWpt result = pt;
        if (targetDir != null) {
            File f = pt.getJpeg();
            String name = f.getName();
            targetDir.mkdirs();
            File target = new File(targetDir, name);
            InputStream in = (tracks != null) ? t.transform(f,
                    getStampText(pt), imageDimension, tracks,
                    tracks.project(pt)) : t.transform(f, getStampText(pt),
                    imageDimension);
            result = null;
            try {
                OutputStream out = new FileOutputStream(target);
                byte[] buffer = new byte[4096];
                int count;
                while ((count = in.read(buffer, 0, 4096)) != -1) {
                    out.write(buffer, 0, count);
                }
                out.close();
                result = new PhotoWpt(target, GPXUtils.getInstance()
                        .copyWpt(pt));
            } finally {
                in.close();
            }
        }
        return result;
    }

    /**
     * Sets the exif writer to use for writing gps information to the exif
     * headers of geo-located jpegs.
     *
     * @param writer
     *            the exif writer or <code>null</code> to disable exif
     *            modification
     * @see #maybeUpdateExif maybeUpdateExif
     */
    public void setGpsExifWriter(GpsExifWriter writer) {
        exifWriter = writer;
    }

    /**
     * Command line interface.
     *
     * @param argv
     *            the command line arguments
     * @see <a href="http://gpstools.sf.net/exifloc.html">GPSdings exifloc
     *      online documentation</a>
     */
    public static void main(String[] argv) throws Exception {
        // java.util.Locale.setDefault(java.util.Locale.US);
        (new ExiflocCommandLine(argv)).execute();
    }

    private class JSTemplateHandler implements TemplateHandler {
        private final GpsFormat format = GpsFormat.getInstance();

        public JSTemplateHandler() {
            // sole constructor
        }

        @Override
        public void handleTemplateEvent(TemplateEvent e) throws IOException {
            Appendable app = e.getDest();
            String var = e.getVariable();
            if ("photowpts".equals(var)) {
                addPhotoWpts(app);
            } else {
                message("Skipping unknown variable " + var);
            }
        }

        private void addPhotoWpts(Appendable photojs) throws IOException {
            Dimension jpegSize = null;
            boolean comma = false;
            for (PhotoWpt wpt : data) {
                File jpeg = wpt.getJpeg();
                try {
                    // message("Getting dimensions for " + jpeg);
                    jpegSize = getJPEGDimension(jpeg);
                    // message(jpegSize);
                } catch (Exception ex) {
                    message(ex.getMessage());
                    message("Cannot retrieve height and width for file " + jpeg);
                    continue;
                }
                if (comma) {
                    photojs.append(",\n");
                } else {
                    comma = true;
                }
                String name = "'" + wpt.getName().replace('\'', ' ').trim()
                        + "'";
                photojs.append("{lat:");
                photojs.append(format.asLatLon(wpt.getLat()));
                photojs.append(",lon:").append(format.asLatLon(wpt.getLon()));
                photojs.append(",desc:").append(name);
                photojs.append(",img: { src: ").append(name);
                photojs.append(",width:")
                        .append(String.valueOf(jpegSize.width));
                photojs.append(",height:").append(
                        String.valueOf(jpegSize.height));
                photojs.append("}}");
            }
        }
    }

    /**
     * Returns the dimension of the specified jpeg image. This method will first
     * try to read the image dimension from the jpeg header; if this fails it
     * will decode the jpeg and determine the dimension of the decoded image.
     *
     * @param f
     *            a jpeg file
     * @return the size of the image stored in <code>f</code>
     * @throws IOException
     *             if an I/O error occurs or if <code>f</code> is no jpeg
     * @see #getExifReader getExifReader
     */
    public Dimension getJPEGDimension(File f) throws IOException {
        // if(imageDimension != null){
        // return imageDimension;
        // }
        // message("Getting dimensions for " + f);
        Dimension result = null;
        try {
            result = getExifReader().readJPEGDimension(f);
        } catch (Exception ignore) {
            // handled below.
        }

        if (result == null) {
            result = JpegTransformer.getJPEGDimensionByDecoding(f);
        }
        if (imageDimension != null) {
            float scale = Math.min(1.0f * imageDimension.width / result.width,
                    1.0f * imageDimension.height / result.width);
            if (scale < 1) {
                result.setSize(Math.round(result.width * scale),
                        Math.round(result.height * scale));
            }
        }
        // message(result);
        return result;
    }

    /**
     * Returns the exif reader used by this exif locator to read Exif
     * information from digital photographs.
     *
     * @return the exif reader used by this exif locator
     */
    public synchronized CalibratedExifReader getExifReader() {
        if (exifReader == null) {
            exifReader = new CalibratedExifReader();
        }
        return exifReader;
    }

    public synchronized void setExifReader(CalibratedExifReader reader) {
        exifReader = reader;
    }

    private static class ExiflocCommandLine extends
            AbstractCommandLine<ExifLocator> {
        private boolean readGpsTag = false;
        private boolean guessUTCOffset = false;
        private GpxType xgpx = null;
        public static final String OPT_GPX = "gpx";
        public static final String OPT_DATE_FORMAT = "date-format";
        public static final String OPT_CALIBRATE = "calibrate";
        public static final String OPT_CALIBRATE_EXIF = "Calibrate";
        public static final String OPT_UTC_OFFSET = "utc-offset";
        public static final String OPT_TEXT = "text";
        public static final String OPT_XML = "GPX";
        public static final String OPT_EXIV2 = "exif";
        public static final String OPT_EXIF = "Exif";
        public static final String OPT_XMP = "XMP";
        public static final String OPT_READ_XMP = "xmp";
        public static final String OPT_JS = "javascript";
        public static final String OPT_IMAGE_SIZE = "image-size";
        public static final String OPT_KML = "kml";
        public static final String OPT_KMZ = "kmz";
        public static final String OPT_READ_GPS = "read-gps-tag";
        public static final String OPT_COPY_TO = "copy-to";
        public static final String OPT_STAMP = "stamp";
        public static final String OPT_TRACK = "Track";

        public ExiflocCommandLine(String[] argv) {
            super(new ExifLocator(), argv);
        }

        @SuppressWarnings("static-access")
        @Override
        protected Options createOptions() {
            Options options = super.createOptions();

            options.addOption(makeOption(OPT_GPX, File.class, 1, "gpxfile"));

            String longOption = OPT_DATE_FORMAT;
            options.addOption(OptionBuilder.withLongOpt(longOption)
                    .isRequired(false).hasArg(true).withType(String.class)
                    // .withDescription(desc.getString(opt) + " " + ISO_DATE)
                    .create(longOption.charAt(0)));

            OptionGroup calib = new OptionGroup();
            calib.addOption(makeOption(OPT_CALIBRATE, Date.class, 1,
                    "dateTimeList"));
            calib.addOption(makeOption(OPT_CALIBRATE_EXIF, Date.class, 1,
                    "jpegDateTimeList"));
            calib.addOption(makeOption(OPT_UTC_OFFSET, Double.class, 1, "secs"));
            options.addOptionGroup(calib);

            OptionGroup ogExif = new OptionGroup();
            ogExif.addOption(OptionBuilder.withLongOpt(OPT_EXIV2)
                    .isRequired(false).hasOptionalArg().withType(String.class)
                    // .withDescription(desc.getString(opt) + " " + ISO_DATE)
                    .create(OPT_EXIV2.charAt(0)));
            // addOption(makeOption(OPT_EXIV2, String.class, 1, "file"));
            ogExif.addOption(makeOption(OPT_EXIF, Boolean.class, 0, null));
            ogExif.addOption(makeOption(OPT_XMP, Boolean.class, 0, null));
            options.addOptionGroup(ogExif);

            options.addOption(makeOption(OPT_READ_XMP, Boolean.class, 0, null));
            options.addOption(makeOption(OPT_TEXT, File.class, 1, "file"));
            options.addOption(makeOption(OPT_XML, File.class, 1, "file"));
            options.addOption(makeOption(OPT_KML, File.class, 1, "file"));
            options.addOption(makeOption('z', OPT_KMZ, File.class, 1, "file"));
            options.addOption(makeOption('y', OPT_COPY_TO, File.class, 1,
                    "file"));
            options.addOption(makeOption(OPT_STAMP, String.class, 3,
                    "pattern position alpha"));
            options.addOption(makeOption('T', OPT_TRACK, String.class, 5,
                    "position size bg track marker"));
            options.addOption(makeOption(OPT_JS, File.class, 1, "file"));
            options.addOption(makeOption(OPT_READ_GPS, Boolean.class, 0, null));
            options.addOption(makeOption(OPT_IMAGE_SIZE, Integer.class, 2,
                    "width height"));

            return options;
        }

        @Override
        protected CommandLine processOptions(Options options)
                throws org.apache.commons.cli.ParseException, IOException,
                java.text.ParseException {
            CommandLine cl = super.processOptions(options);
            char c;

            /* readGpsTag option */
            opt = OPT_READ_GPS;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                readGpsTag = true;
            }

            // OPT_READ_XMP must be processed before OPT_CALIBRATE_EXIF
            /* xmp option */
            opt = OPT_READ_XMP;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                app.setExifReader(new CalibratedExifReader(new XMPReader()));
            }

            /* gpx option */
            opt = OPT_GPX;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                // throw new
                // MissingOptionException("The -g, --gpx option is required.");
                File fgpx = (File) cl.getOptionObject(c);
                InputStream in = new BufferedInputStream(new FileInputStream(
                        fgpx));
                try {
                    xgpx = GPSDings.readGPX(in);
                } finally {
                    in.close();
                }
                app.addGPX(xgpx);
            } else if (!readGpsTag) {
                throw new MissingOptionException(
                        "You must use at least one of the -g and the -r options.");
            }

            /* output options */

            opt = OPT_XML;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                app.enableOutput(Output.XML, cl.getOptionValue(c));
            }

            opt = OPT_TEXT;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                app.enableOutput(Output.Text, cl.getOptionValue(c));
            }

            opt = OPT_JS;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                app.enableOutput(Output.MapJS, cl.getOptionValue(c));
            }

            opt = OPT_KML;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                app.enableOutput(Output.KML, cl.getOptionValue(c));
            }

            c = 'z';
            if (cl.hasOption(c)) {
                app.enableOutput(Output.KMZ, cl.getOptionValue(c));
            }

            opt = OPT_EXIV2;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                String s = cl.getOptionValue(c);
                WriterFactory factory = new WriterFactory();
                GpsExifWriter writer = null;
                try {
                    writer = (s == null) ? factory.getWriter(s,
                            WriterFactory.ExifWriter.Libexiv2) : factory
                            .getWriter(s);
                } catch (InstantiationException ex) {
                    System.err.println(ex);
                    System.err
                            .println("Cannot load the specified Exif writer. Consider using the -E option");
                    System.exit(1);
                }
                app.setGpsExifWriter(writer);
            }

            opt = OPT_XMP;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                WriterFactory factory = new WriterFactory();
                GpsExifWriter writer = null;
                try {
                    writer = factory.getWriter(null,
                            WriterFactory.ExifWriter.XMP);
                } catch (InstantiationException ex) {
                    System.err.println(ex);
                    System.err.println("Cannot load the XMP writer.");
                    System.exit(1);
                }
                app.setGpsExifWriter(writer);
            }

            opt = OPT_EXIF;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                try {
                    app.setGpsExifWriter((new WriterFactory()).getWriter(null,
                            WriterFactory.ExifWriter.Default));
                } catch (InstantiationException neverHappens) {
                    throw new Error(neverHappens);
                }
            }

            /* image-size option */
            opt = OPT_IMAGE_SIZE;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                String[] wh = cl.getOptionValues(c);
                app.setImageDimension(Integer.parseInt(wh[0]),
                        Integer.parseInt(wh[1]));
                if (!(cl.hasOption('y') || cl.hasOption('z') || cl
                        .hasOption('j'))) {
                    System.err
                            .println("Warning: -i option found, but neither -y nor -z.");
                    System.err.println("         Jpegs will not be resized.");
                }
            }

            /* track option */
            opt = OPT_TRACK;
            c = 'T';
            if (cl.hasOption(c)) {
                if (cl.hasOption('g')) {
                    if (cl.hasOption('y') || cl.hasOption('z')) {
                        app.setAddTrackView(true);
                        String[] possza = cl.getOptionValues(c);
                        JpegTransformer t = app.getJpegTransformer();
                        t.setTrackCorner(possza[0]);
                        t.setTrackSize(Float.parseFloat(possza[1]));
                        t.setTrackColor(JpegTransformer.C_BACKGROUND,
                                parseColor(possza[2]));
                        t.setTrackColor(JpegTransformer.C_TRACK,
                                parseColor(possza[3]));
                        t.setTrackColor(JpegTransformer.C_MARKER,
                                parseColor(possza[4]));
                    } else {
                        System.err
                                .println("Warning: -T option found, but neither -y nor -z.");
                        System.err
                                .println("         Jpegs will not be stamped.");
                    }
                } else {
                    System.err
                            .println("Warning: -T option without -g has no effect.");
                }
            }

            /* stamp option */
            opt = OPT_STAMP;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                String[] patternposition = cl.getOptionValues(c);
                String pattern = patternposition[0];
                if (pattern.charAt(0) == '@') {
                    BufferedReader in = new BufferedReader(new FileReader(
                            pattern.substring(1)));
                    StringBuilder sb = new StringBuilder();
                    for (String line = in.readLine(); line != null; line = in
                            .readLine()) {
                        sb.append(line);
                    }
                    pattern = sb.toString();
                }
                app.setStampFormat(pattern, null);
                app.getJpegTransformer().setTextCorner(patternposition[1]);
                app.getJpegTransformer().setTextAlpha(
                        Float.parseFloat(patternposition[2]));
                if (!(cl.hasOption('y') || cl.hasOption('z'))) {
                    System.err
                            .println("Warning: -s option found, but neither -y nor -z.");
                    System.err.println("         Jpegs will not be stamped.");
                }
            }

            /* copyto option */
            c = 'y';
            if (cl.hasOption(c)) {
                String path = cl.getOptionValue(c);
                File f = new File(path);
                if (f.exists() && !f.isDirectory()) {
                    throw new IllegalArgumentException(
                            "Illegal Argument for the -y/--copy-to option: "
                                    + path + "\nNot a directory.");
                }
                app.setTargetDir(f);
            }

            /* date-format option */
            opt = OPT_DATE_FORMAT;
            c = opt.charAt(0);
            String dfpattern = cl.getOptionValue(c, ISO_DATE);
            DateFormat df = new SimpleDateFormat(dfpattern);
            df.setTimeZone(UTC);

            // OPT_READ_XMP must be processed before OPT_CALIBRATE_EXIF
            /* calibrate options */
            opt = OPT_CALIBRATE;
            c = opt.charAt(0);
            if (cl.hasOption(c)) {
                app.getExifReader().calibrateFromDateMap(
                        (new DateMapParser(df)).parse(cl.getOptionValue(c)));
            } else if (cl.hasOption(OPT_CALIBRATE_EXIF.charAt(0))) {
                /* calibrateExif option */
                opt = OPT_CALIBRATE_EXIF;
                c = opt.charAt(0);
                app.getExifReader()
                        .calibrateFromDateMap(
                                (new FileDateMapParser(df)).parse(cl
                                        .getOptionValue(c)));
                /* utc-offset option */
            } else if (cl.hasOption(OPT_UTC_OFFSET.charAt(0))) {
                opt = OPT_UTC_OFFSET;
                c = opt.charAt(0);
                app.getExifReader()
                        .setUTCOffset(
                                Math.round(Double.parseDouble(cl
                                        .getOptionValue(c)) * 1000));
            } else {
                // by default use computer time zone
                guessUTCOffset = true;
            }

            return cl;
        }

        private TimeZone getGpxTimeZone() {
            TimeZone result = null;
            app.message("Trying to retrieve the local timezone from the geonames.org webservice.");
            try {
                result = GPXUtils.getInstance().getTimeZone(xgpx);
            } catch (GPXUtils.GeonamesException ex) {
                app.message(ex.toString());
                app.message("Caused by " + ex.getCause().toString());
            } catch (GPXUtils.TimeZoneBoundaryException ex) {
                app.message(ex.toString());
            }
            return result;
        }

        private TimeZone getJpegTimeZone(File[] jpegs) {
            TimeZone result = null;
            app.message("Trying to retrieve the local timezone from the geonames.org webservice.");
            GpsExifReader exr = app.getExifReader();
            try {
                for (File jpeg : jpegs) {
                    try {
                        WptType pt = exr.readGPSTag(jpeg);
                        if (pt != null) {
                            result = GPXUtils.getInstance().getTimeZone(pt);
                            break;
                        }
                    } catch (IOException ex) {
                        app.message(ex.toString());
                    }
                }
            } catch (GPXUtils.GeonamesException ex) {
                app.message(ex.toString());
                app.message("Caused by " + ex.getCause().toString());
            } catch (Exception ex) {
                app.message(ex.toString());
            }
            return result;
        }

        @Override
        public void execute() {
            processArguments();
            try {
                File[] jpegs = getInputFiles();
                if (jpegs == null) {
                    System.exit(1);
                }
                Set<PhotoWpt> points = new TreeSet<PhotoWpt>(defaultComparator);
                PhotoWpt pt;
                if (guessUTCOffset) {
                    TimeZone tz = null;
                    if (xgpx != null) {
                        tz = getGpxTimeZone();
                    } else if (readGpsTag) {
                        tz = getJpegTimeZone(jpegs);
                    }
                    if (tz == null) {
                        app.message("Using computer timezone.");
                        tz = TimeZone.getDefault();
                    }
                    for (File jpeg : jpegs) {
                        try {
                            Date d = app.getExifReader().readOriginalTime(jpeg);
                            if (d != null) {
                                long dl = d.getTime();
                                int offs = tz.getOffset(dl);
                                dl -= offs;
                                offs = tz.getOffset(dl);
                                d = new Date(dl);
                                app.getExifReader().setUTCOffset(offs);
                                app.message("Assuming that EXIF DateTimeOriginal tags are given in "
                                        + tz.getDisplayName(
                                                tz.inDaylightTime(d),
                                                TimeZone.LONG, Locale.US) + ":");
                                app.message("\tUTC offset at " + d + " was "
                                        + offs / 1000 + " s.");
                                break;
                            }
                        } catch (IOException ex) {
                            // try next picture
                        }
                    }
                }
                for (File jpeg : jpegs) {
                    pt = app.locate(jpeg, readGpsTag);
                    if (pt != null) {
                        boolean b = points.add(pt);
                        if (!b) {
                            app.message(jpeg.getName()
                                    + " is already contained in the ExifLocator's dataset.");
                        }
                    }
                }
                app.setData(points);
                app.setData(app.transformJpegs());
                app.maybeUpdateExif();
                app.print();
            } catch (Exception ex) {
                System.err.println(ex.getClass().getName() + ": "
                        + ex.getMessage());
                ex.printStackTrace();
                System.exit(1);
            }
        }

        @Override
        public void printHelp(PrintStream out) {
            try {
                super.printHelp(out);
                cat("exifloc.txt");
            } catch (Exception ex) {
                throw new Error(ex);
            }
        }

    }

    /**
     * A geo-located digital photograph. The jpeg file is accessible through
     * {@link #getJpeg getJpeg}, the geo-location through the diverse
     * {@link net.sourceforge.gpstools.gpx.WptType Wpt} methods.
     */
    public static class PhotoWpt extends Wpt {
        private static final long serialVersionUID = 4758326140499795677L;
        private final File jpeg;

        /**
         * Constructs a new PhotoWpt from the specified jpeg file and
         * geo-location.
         *
         * @param jpeg
         *            a digital photograph file
         * @param wpt
         *            the geo-information to associate with that file
         */
        public PhotoWpt(File jpeg, Wpt wpt) {
            this.jpeg = jpeg;
            GPXUtils.getInstance().copyProperties(wpt, this);
            String name = jpeg.getName();
            setName(name);
            setSym("Scenic Area");
            name = jpeg.getPath();
            setCmt(name);
            setDesc(name);
        }

        /**
         * Returns the jpeg file with the digital photograph.
         *
         * @return the jpeg file passed to the constructor
         */
        public File getJpeg() {
            return jpeg;
        }
    }
}
TOP

Related Classes of net.sourceforge.gpstools.ExifLocator$PhotoWpt

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.