eu.udig.imagegeoreferencing.WarpImageTool.java Source code

Java tutorial

Introduction

Here is the source code for eu.udig.imagegeoreferencing.WarpImageTool.java

Source

/*
 *    uDig - User Friendly Desktop Internet GIS client
 *    http://udig.refractions.net
 *    (C) 2004, Refractions Research Inc.
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License as published by the Free Software Foundation;
 *    version 2.1 of the License.
 *
 *    This library 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
 *    Lesser General Public License for more details.
 */
package eu.udig.imagegeoreferencing;

import java.awt.Image;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.processing.Operations;
import org.geotools.gce.geotiff.GeoTiffWriter;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.crs.DefaultDerivedCRS;
import org.geotools.referencing.cs.DefaultCartesianCS;
import org.geotools.referencing.operation.transform.WarpTransform2D;
import org.opengis.coverage.Coverage;
import org.opengis.referencing.crs.CoordinateReferenceSystem;

import com.vividsolutions.jts.geom.Coordinate;

import eu.udig.imagegeoreferencing.i18n.Messages;

import net.refractions.udig.catalog.CatalogPlugin;
import net.refractions.udig.catalog.IGeoResource;
import net.refractions.udig.catalog.IResolve;
import net.refractions.udig.catalog.IService;
import net.refractions.udig.catalog.IServiceFactory;
import net.refractions.udig.project.IMap;
import net.refractions.udig.project.ui.ApplicationGIS;
import net.refractions.udig.project.ui.render.displayAdapter.MapMouseEvent;
import net.refractions.udig.project.ui.tool.AbstractModalTool;
import net.refractions.udig.project.ui.tool.ModalTool;
import net.refractions.udig.project.ui.tool.Tool;
import net.refractions.udig.ui.PlatformGIS;

/**
 * Warp the selected image based on the placemarkers and load the result onto the map
 * 
 * @author GDavis, Refractions Research
 * @since 1.1.0
 */
public class WarpImageTool extends AbstractModalTool implements ModalTool {

    public static final String TOOLID = "net.refractions.udig.imagegeoreference.tools.warpImageTool"; //$NON-NLS-1$

    private List<PlaceMarker> imageMarkers;
    private List<PlaceMarker> basemapMarkers;
    private int warpValue = PlaceMarkersTool.WARP_MINDEGREE;

    public WarpImageTool() {
        this(MOUSE);
    }

    public WarpImageTool(int targets) {
        super(targets);
    }

    @Override
    public void setActive(boolean active) {
        super.setActive(active);
        if (active) {
            // warp the selected image
            IMap activeMap = ApplicationGIS.getActiveMap();
            warpImage(activeMap);
        }
    }

    private void loadMarkers(IMap map) {
        GeoReferenceMapGraphic mapGraphic = GeoReferenceUtils.getMapGraphic(map);
        if (mapGraphic != null) {
            imageMarkers = mapGraphic.getImageMarkers();
            basemapMarkers = mapGraphic.getBasemapMarkers();
            warpValue = mapGraphic.getWarpValue();
        }
    }

    /**
     * Warp the currently selected image with the current markers
     * 
     * @param map
     */
    private void warpImage(IMap map) {
        // load the current markers and warp value
        loadMarkers(map);

        if (!validateMarkers()) {
            showErrorDialog();
            return;
        }

        // get the selected Image to warp
        GeoReferenceImage selectedGeoImage = null;
        GeoReferenceMapGraphic mapGraphic = GeoReferenceUtils.getMapGraphic(map);
        if (mapGraphic != null && mapGraphic.getImages() != null) {
            selectedGeoImage = GeoReferenceUtils.getSelectedGeoImage(mapGraphic.getImages(), map);
        }
        if (selectedGeoImage == null) {
            showErrorDialog();
            return;
        }

        // create a WarpTransform2D of the source and dest points
        Point2D[] dstCoords = getImagePoints(selectedGeoImage, imageMarkers);
        Point2D[] srcCoords = getBasemapPoints(map, basemapMarkers);
        // the best warping results seem to occur when the points are ordered from
        // left to right and up to down, so order them in that fashion as best as possible first.
        int[] order = getSortOrder(dstCoords);
        dstCoords = sortArray(dstCoords, order);
        srcCoords = sortArray(srcCoords, order);

        // warp the image within a progress dialog process
        WarpImageProcess warpProcess = new WarpImageProcess(map, selectedGeoImage, srcCoords, dstCoords, warpValue);
        PlatformGIS.runInProgressDialog("Warping Image", false, warpProcess, false); //$NON-NLS-1$
        GridCoverage2D warpedCoverage = warpProcess.getWarpedCoverage();

        // regardless of results, clear the points from the map and reset the point vars
        Tool placeMarkersTool = GeoReferenceUtils.findTool(PlaceMarkersTool.TOOLID);
        if (placeMarkersTool != null && placeMarkersTool instanceof PlaceMarkersTool) {
            ((PlaceMarkersTool) placeMarkersTool).clearPointVars();
        }

        // show dialog to confirm saving the displayed result or to cancel and restart
        if (warpedCoverage != null) {
            Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();

            FileDialog fd = new FileDialog(shell, SWT.SAVE);
            fd.setFilterExtensions(new String[] { "*.tif" }); //$NON-NLS-1$
            fd.setText(Messages.saveFile_title);
            String filename = fd.open();
            if (filename != null && filename != "") { //$NON-NLS-1$
                WriteTifAndLoadProcess writeTifAndLoadProcess = new WriteTifAndLoadProcess(map, warpedCoverage,
                        filename);
                PlatformGIS.runInProgressDialog("Saving and Loading TIF", false, writeTifAndLoadProcess, false); //$NON-NLS-1$

                // now remove the selected geoImage from the mapgraphic layer
                HashMap<IMap, HashMap<String, GeoReferenceImage>> mapimages = mapGraphic.getImages();
                selectedGeoImage.setSelected(false);
                HashMap<String, GeoReferenceImage> images = mapimages.get(map);
                if (images != null)
                    images.remove(selectedGeoImage.getFilename());
            }

            // refresh the map
            if (mapGraphic != null)
                mapGraphic.getLayer(map).refresh(null);
        }
    }

    /**
     * Ensure at least the min amount of markers are set
     *
     * @return
     */
    private boolean validateMarkers() {
        if (imageMarkers == null || imageMarkers.size() < PlaceMarkersTool.MIN_PLACEMARKERS
                || basemapMarkers == null || basemapMarkers.size() < PlaceMarkersTool.MIN_PLACEMARKERS
                || imageMarkers.size() != basemapMarkers.size()) {
            return false;
        }
        // loop through markers and make sure they are set
        for (int i = 0; i < imageMarkers.size(); i++) {
            PlaceMarker iMarker = imageMarkers.get(i);
            PlaceMarker bMarker = basemapMarkers.get(i);
            if (iMarker.getPoint() == null || bMarker.getPoint() == null) {
                return false;
            }
        }
        return true;
    }

    /**
     * Show a dialog with the error explaining why the warp would not process
     */
    private void showErrorDialog() {
        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        final Dialog dialog = new Dialog(shell) {
            @Override
            protected Control createDialogArea(Composite parent) {
                Composite container = (Composite) super.createDialogArea(parent);
                GridLayout layout = new GridLayout(1, true);
                container.setLayout(layout);
                GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
                layoutData.widthHint = 450;
                container.setLayoutData(layoutData);

                Label descLabel = new Label(container, SWT.WRAP);
                descLabel
                        .setText(MessageFormat.format(Messages.WarpDialog_desc, PlaceMarkersTool.MIN_PLACEMARKERS));
                layoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
                layoutData.verticalSpan = 1;
                layoutData.horizontalSpan = 1;
                descLabel.setLayoutData(layoutData);

                return container;
            }

            @Override
            protected void configureShell(Shell newShell) {
                super.configureShell(newShell);
                newShell.setText(Messages.WarpDialog_title);
                GridLayout layout = new GridLayout();
                layout.numColumns = 1;
                newShell.setLayout(layout);
            }
        };
        dialog.setBlockOnOpen(true);
        dialog.open();
    }

    /**
     * Sort the given point array in the order of the provided index array
     * 
     * @param ar
     * @param order array
     */
    private Point2D[] sortArray(Point2D[] ar, int[] order) {
        Point2D[] ordered = new Point2D[ar.length];
        for (int i = 0; i < ar.length; i++) {
            ordered[i] = ar[order[i]];
        }
        return ordered;
    }

    /**
     * Determine the order the array should be sorted in
     * 
     * @param ar
     * @return new order array
     */
    private int[] getSortOrder(Point2D[] ar) {
        Point2D[] sorted = sortPointsX(ar);
        sorted = sortPointsY(sorted);

        // determine the new sort order
        int[] order = new int[ar.length];
        for (int i = 0; i < sorted.length; i++) {
            Point2D point = sorted[i];
            for (int y = 0; y < ar.length; y++) {
                if (point.equals(ar[y])) {
                    order[i] = y;
                    break;
                }
            }
        }
        return order;
    }

    /**
     * Sort the point array from "left" to "right" as that
     * seems to give better results.
     * 
     * @param ar
     * @return sorted array
     */
    private Point2D[] sortPointsX(Point2D[] ar) {
        Point2D[] sorted = new Point2D[ar.length];
        for (int i = 0; i < ar.length; i++) {
            Point2D point = ar[i];
            for (int y = 0; y < sorted.length; y++) {
                Point2D point2 = sorted[y];
                if (point2 == null) {
                    sorted[y] = point;
                    break;
                } else {
                    if (point2.getX() < point.getX()) {
                        for (int z = sorted.length - 1; z > y; z--) {
                            sorted[z] = sorted[z - 1];
                        }
                        sorted[y] = point;
                        break;
                    }
                }
            }
        }
        return sorted;
    }

    /**
     * Sort the point array from "top" to "bottom" as that
     * seems to give better results.
     * 
     * @param ar
     * @return sorted array
     */
    private Point2D[] sortPointsY(Point2D[] ar) {
        Point2D[] sorted = new Point2D[ar.length];
        for (int i = 0; i < ar.length; i++) {
            Point2D point = ar[i];
            for (int y = 0; y < sorted.length; y++) {
                Point2D point2 = sorted[y];
                if (point2 == null) {
                    sorted[y] = point;
                    break;
                } else {
                    if (point2.getY() < point.getY()) {
                        for (int z = sorted.length - 1; z > y; z--) {
                            sorted[z] = sorted[z - 1];
                        }
                        sorted[y] = point;
                        break;
                    }
                }
            }
        }
        return sorted;
    }

    /**
     * Convert the placemarkers into an array of Point2D points relative to the image's
     * top-left corner.
     * 
     * @param selectedGeoImage
     * @param markers
     * @return
     */
    private Point2D[] getImagePoints(GeoReferenceImage selectedGeoImage, List<PlaceMarker> markers) {
        int topleft_x = selectedGeoImage.getPosX();
        int topleft_y = selectedGeoImage.getPosY();
        Point2D[] points = new Point2D[markers.size()];
        int index = 0;
        for (PlaceMarker marker : markers) {
            java.awt.Point point = marker.getPoint();
            // flip the y coordinate
            int y = point.y - topleft_y;
            y = selectedGeoImage.getScaledHeight() - y;
            java.awt.Point scaledPoint = new java.awt.Point(point.x - topleft_x, y);
            // the above point is for the scaled image, so adjust it for the full size image
            double xScale = (double) scaledPoint.x / (double) selectedGeoImage.getScaledWidth();
            double yScale = (double) scaledPoint.y / (double) selectedGeoImage.getScaledHeight();
            Image image = selectedGeoImage.getAWTImage();
            java.awt.Point fullPoint = new java.awt.Point((int) (image.getWidth(null) * xScale),
                    (int) (image.getHeight(null) * yScale));
            points[index] = fullPoint;
            index++;
        }
        return points;
    }

    /**
     * Convert the placemarkers into an array of Point2D points in the basemap's CRS
     * 
     * @param map
     * @param markers
     * @return
     */
    private Point2D[] getBasemapPoints(IMap map, List<PlaceMarker> markers) {
        Point2D[] points = new Point2D[markers.size()];
        int index = 0;
        for (PlaceMarker marker : markers) {
            java.awt.Point point = marker.getPoint();
            Coordinate coord = map.getViewportModel().pixelToWorld(point.x, point.y);
            points[index] = new Point2D.Double(coord.x, coord.y);
            index++;
        }
        return points;
    }

    /**
     * @see net.refractions.udig.project.ui.tool.AbstractTool#mousePressed(net.refractions.udig.project.render.displayAdapter.MapMouseEvent)
     */
    public void mousePressed(MapMouseEvent e) {
        // validate the mouse click
        if (!validModifierButtonCombo(e)) {
            return;
        }
    }

    /**
     * Checks if the user clicked the right mouse button, and if they did then
     * rotate the selected image referencing tool.
     * 
     * Otherwise it returns true if the combination of buttons and modifiers 
     * are legal with a left-mouse-click.
     * 
     * @param e
     * @return
     */
    protected boolean validModifierButtonCombo(MapMouseEvent e) {
        if (e.buttons == MapMouseEvent.BUTTON3) {
            // rotate to the next tool after the move markers tool
            GeoReferenceUtils.rotateToNextTool(MoveMarkersTool.TOOLID);
            return false;
        }
        return e.buttons == MapMouseEvent.BUTTON1 && !(e.modifiersDown());
    }

}

/**
 * Warps the given geoImage.
 * 
 * @author GDavis
 *
 */
final class WarpImageProcess implements IRunnableWithProgress {

    private IMap map;
    private GeoReferenceImage geoImage;
    private Point2D[] srcCoords;
    private Point2D[] dstCoords;
    private int warpValue;
    private GridCoverage2D warpedCoverage;

    public WarpImageProcess(IMap map, GeoReferenceImage geoImage, Point2D[] srcCoords, Point2D[] dstCoords,
            int warpValue) {
        this.map = map;
        this.geoImage = geoImage;
        this.srcCoords = srcCoords;
        this.dstCoords = dstCoords;
        this.warpValue = warpValue;
    }

    public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
        WarpTransform2D warpTransform = new WarpTransform2D(srcCoords, dstCoords, warpValue);

        // create a DefaultDerivedCRS for the CRS of the image
        CoordinateReferenceSystem crs = map.getViewportModel().getCRS();
        DefaultDerivedCRS derivedCRS = new DefaultDerivedCRS("imageCRS", crs, warpTransform, //$NON-NLS-1$
                DefaultCartesianCS.GENERIC_2D);

        // now create a gridcoverage for the new warped image
        BufferedImage image = (BufferedImage) geoImage.getAWTImage();
        GridCoverageFactory factory = new GridCoverageFactory();
        ReferencedEnvelope ref = new ReferencedEnvelope(0, image.getWidth(null), 0, image.getHeight(null),
                derivedCRS);
        GridCoverage2D coverage = (GridCoverage2D) factory.create("GridCoverage", image, ref); //$NON-NLS-1$

        // resample the new image with the world CRS
        warpedCoverage = null;
        // RenderedImage rImage = null;
        try {
            Operations ops = new Operations(null);
            Coverage resample = ops.resample(coverage, crs);
            warpedCoverage = (GridCoverage2D) resample;

            // write out a jpg version for testing
            // rImage = warpedCoverage.getRenderedImage();
            // ImageIO.write(rImage, "jpg", new File("blahblah.jpg"));

            // // add the result as a layer to the map by first saving it as a temp tiff file
            // // and then loading that file as a layer and deleting the temp file
            //          File tempfile = new File("warptemp.tif"); //$NON-NLS-1$
            // GeoTiffWriter writer = new GeoTiffWriter(tempfile);
            // writer.write(warpedCoverage.geophysics(true), null);
            // IGeoResource tiffResource = getTiffResource(tempfile.toURL());
            // if (tiffResource != null) {
            // ApplicationGIS.addLayersToMap(map, Collections.singletonList(tiffResource), -1);
            // }
            // delete temp file
            // tempfile.delete();

            // IGeoResource resource =
            // CatalogPlugin.getDefault().getLocalCatalog().createTemporaryResource(tempfile);
            // ApplicationGIS.addLayersToMap(map, Collections.singletonList(resource), -1);

        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        // // create a bufferedImage of the result using the type from the current image
        // Image image2 = geoImage.getAWTImage();
        // int type = 0;
        // if (image2 instanceof BufferedImage) {
        // type = ((BufferedImage)image2).getType();
        // }
        // if (type == 0) {
        // type = BufferedImage.TYPE_INT_RGB;
        // }
        // BufferedImage buffImage = new BufferedImage(rImage.getWidth(),
        // rImage.getHeight(), type);
        // Graphics2D graphics = buffImage.createGraphics();
        // graphics.drawRenderedImage(rImage, new AffineTransform());
        // try {
        // ImageIO.write(buffImage, "jpg", new File("testtest.jpg"));
        // } catch (Exception e) {
        // e.printStackTrace();
        // }
        // rImage = null;
        // //selectedGeoImage.setScaledAWTImage(buffImage);
    }

    public IGeoResource getTiffResource(URL url) {
        IServiceFactory factory = CatalogPlugin.getDefault().getServiceFactory();
        List<IService> services = factory.createService(url);

        if (services.isEmpty()) {
            return null;
        }
        for (IService service : services) {
            IResolve resolve;
            IGeoResource geoR;
            try {
                resolve = service.members(null).get(0);
                geoR = resolve.resolve(IGeoResource.class, new NullProgressMonitor());
                return geoR;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public GridCoverage2D getWarpedCoverage() {
        return warpedCoverage;
    }
}

/**
 * Writes the given coverage to file as a tif and then loads it on the map.
 * 
 * @author GDavis
 *
 */
final class WriteTifAndLoadProcess implements IRunnableWithProgress {

    private IMap map;
    private GridCoverage2D warpedCoverage;
    private String filename;

    public WriteTifAndLoadProcess(IMap map, GridCoverage2D warpedCoverage, String filename) {
        this.map = map;
        this.warpedCoverage = warpedCoverage;
        this.filename = filename;
    }

    @SuppressWarnings("deprecation")
    public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
        if (warpedCoverage == null || filename == null || filename == "") { //$NON-NLS-1$
            monitor.done();
            return;
        }

        monitor.beginTask("Writing Warped Image to TIF file...", IProgressMonitor.UNKNOWN); //$NON-NLS-1$
        GeoTiffWriter writer;
        if (!filename.contains(".tif")) { //$NON-NLS-1$
            filename += ".tif"; //$NON-NLS-1$
        }
        try {
            writer = new GeoTiffWriter(new File(filename));
            writer.write(warpedCoverage.geophysics(true), null);
        } catch (IOException e) {
            e.printStackTrace();
        }

        monitor.setTaskName("Loading TIF file into uDig..."); //$NON-NLS-1$

        // add the result as a layer to the map by
        // loading that file as a layer
        File tempfile = new File(filename);
        IGeoResource tiffResource = null;
        try {
            writer = new GeoTiffWriter(tempfile);
            writer.write(warpedCoverage.geophysics(true), null);
            tiffResource = getTiffResource(tempfile.toURL());
        } catch (IOException e) {
            e.printStackTrace();
        }

        monitor.setTaskName("Updating Map..."); //$NON-NLS-1$

        if (tiffResource != null) {
            ApplicationGIS.addLayersToMap(map, Collections.singletonList(tiffResource), -1);
        }

    }

    private IGeoResource getTiffResource(URL url) {
        IServiceFactory factory = CatalogPlugin.getDefault().getServiceFactory();
        List<IService> services = factory.createService(url);

        if (services.isEmpty()) {
            return null;
        }
        for (IService service : services) {
            IResolve resolve;
            IGeoResource geoR;
            try {
                resolve = service.members(null).get(0);
                geoR = resolve.resolve(IGeoResource.class, new NullProgressMonitor());
                return geoR;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}