// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.ImportImagePlugin; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Graphics2D; import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.media.jai.PlanarImage; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.Icon; import javax.swing.ImageIcon; import org.apache.log4j.Logger; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.geometry.Envelope2D; import org.geotools.image.ImageWorker; import org.geotools.referencing.CRS; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.RenameLayerAction; import org.openstreetmap.josm.data.Bounds; import org.openstreetmap.josm.data.ProjectionBounds; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.dialogs.LayerListDialog; import org.openstreetmap.josm.gui.dialogs.LayerListPopup; import org.openstreetmap.josm.gui.layer.Layer; /** * Layer which contains spatial referenced image data. * * @author Christoph Beekmans, Fabian Kowitz, Anna Robaszkiewicz, Oliver Kuhn, Martin Ulitzny * */ public class ImageLayer extends Layer { private Logger logger = Logger.getLogger(ImageLayer.class); private File imageFile; private BufferedImage image = null; // coordinates of upper left corner private EastNorth upperLeft; // Angle of rotation of the image private double angle = 0.0; // current bbox private Envelope2D bbox; // Layer icon private Icon layericon = null; // reference system of the oringinal image private CoordinateReferenceSystem sourceRefSys; /** * Constructor */ public ImageLayer(File file) throws IOException { super(file.getName()); this.imageFile = file; this.image = (BufferedImage) createImage(); layericon = new ImageIcon(ImportImagePlugin.pluginClassLoader.getResource("images/layericon.png")); } /** * create spatial referenced image. */ private Image createImage() throws IOException { // geotools type for images and value coverages GridCoverage2D coverage = null; try { // create a grid coverage from the image coverage = PluginOperations.createGridFromFile(imageFile, null, true); this.sourceRefSys = coverage.getCoordinateReferenceSystem(); // now reproject grid coverage coverage = PluginOperations.reprojectCoverage(coverage, CRS.decode(Main.getProjection().toCode())); } catch (FactoryException e) { logger.error("Error while creating GridCoverage:", e); throw new IOException(e.getMessage()); } catch (Exception e) { if (e.getMessage().contains("No projection file found")) { ExtendedDialog ex = new ExtendedDialog(Main.parent, tr("Warning"), new String[] {tr("Default image projection"), tr("JOSM''s current projection"), tr("Cancel")}); // CHECKSTYLE.OFF: LineLength ex.setContent(tr("No projection file (.prj) found.<br>" + "You can choose the default image projection ({0}) or JOSM''s current editor projection ({1}) as original image projection.<br>" + "(It can be changed later from the right click menu of the image layer.)", ImportImagePlugin.pluginProps.getProperty("default_crs_srid"), Main.getProjection().toCode())); // CHECKSTYLE.ON: LineLength ex.showDialog(); int val = ex.getValue(); if (val == 3) { logger.debug("No projection and user declined un-projected use"); throw new LayerCreationCanceledException(); } CoordinateReferenceSystem src = null; try { if (val == 1) { src = PluginOperations.defaultSourceCRS; } else { logger.debug("Passing through image un-projected."); src = CRS.decode(Main.getProjection().toCode()); } // create a grid coverage from the image coverage = PluginOperations.createGridFromFile(imageFile, src, false); this.sourceRefSys = coverage.getCoordinateReferenceSystem(); if (val == 1) { coverage = PluginOperations.reprojectCoverage(coverage, CRS.decode(Main.getProjection().toCode())); } } catch (Exception e1) { logger.error("Error while creating GridCoverage:", e1); throw new IOException(e1); } } else { logger.error("Error while creating GridCoverage:", e); throw new IOException(e); } } logger.debug("Coverage created: " + coverage); // TODO upperLeft = new EastNorth(coverage.getEnvelope2D().x, coverage.getEnvelope2D().y + coverage.getEnvelope2D().height); angle = 0; bbox = coverage.getEnvelope2D(); // Refresh // Main.map.mapView.repaint(); // PlanarImage image = (PlanarImage) coverage.getRenderedImage(); // logger.info("Color Model: " + coverage.getRenderedImage().getColorModel()); ImageWorker worker = new ImageWorker(coverage.getRenderedImage()); return worker.getBufferedImage(); } @Override public void paint(Graphics2D g2, MapView mv, Bounds bounds) { if (image != null && g2 != null) { // Position image at the right graphical place EastNorth center = mv.getCenter(); EastNorth leftop = mv.getEastNorth(0, 0); double pixel_per_east_unit = (mv.getWidth() / 2.0) / (center.east() - leftop.east()); double pixel_per_north_unit = (mv.getHeight() / 2.0) / (leftop.north() - center.north()); // This is now the offset in screen pixels double pic_offset_x = ((upperLeft.east() - leftop.east()) * pixel_per_east_unit); double pic_offset_y = ((leftop.north() - upperLeft.north()) * pixel_per_north_unit); Graphics2D g = (Graphics2D) g2.create(); // Move picture by offset from upper left corner g.translate(pic_offset_x, pic_offset_y); // Rotate image by angle g.rotate(angle * Math.PI / 180.0); // Determine scale to fit JOSM extents ProjectionBounds projbounds = mv.getProjectionBounds(); double width = projbounds.maxEast - projbounds.minEast; double height = projbounds.maxNorth - projbounds.minNorth; double ratio_x = (this.bbox.getMaxX() - this.bbox.getMinX()) / width; double ratio_y = (this.bbox.getMaxY() - this.bbox.getMinY()) / height; double pixels4bbox_width = ratio_x * mv.getWidth(); double pixels4bbox_height = ratio_y * mv.getHeight(); // Scale image to JOSM extents double scalex = pixels4bbox_width / image.getWidth(); double scaley = pixels4bbox_height / image.getHeight(); if ((scalex > 10) || (scaley > 10)) { logger.warn("Not drawing image - scale too big"); return; } g.scale(scalex, scaley); // Draw picture try { g.drawImage(image, 0, 0, null); } catch (ArrayIndexOutOfBoundsException e) { // TODO: prevents this to happen when displaying GeoTIFF images (see #7902) e.printStackTrace(); } } else { logger.error("Error while dawing image: image == null or Graphics == null"); } } public Envelope2D getBbox() { return bbox; } @Override public Icon getIcon() { // TODO Auto-generated method stub return this.layericon; } @Override public Object getInfoComponent() { // TODO Auto-generated method stub return null; } @Override public Action[] getMenuEntries() { return new Action[]{ LayerListDialog.getInstance().createActivateLayerAction(this), LayerListDialog.getInstance().createShowHideLayerAction(), LayerListDialog.getInstance().createDeleteLayerAction(), SeparatorLayerAction.INSTANCE, new RenameLayerAction(getAssociatedFile(), this), new LayerPropertiesAction(this), SeparatorLayerAction.INSTANCE, new LayerListPopup.InfoAction(this)}; } @Override public boolean isMergable(Layer arg0) { return false; } @Override public void mergeFrom(Layer arg0) { throw new UnsupportedOperationException(); } @Override public void visitBoundingBox(BoundingXYVisitor visitor) { EastNorth min = new EastNorth(getBbox().getMinX(), getBbox().getMinY()); EastNorth max = new EastNorth(getBbox().getMaxX(), getBbox().getMaxY()); visitor.visit(min); visitor.visit(max); } @Override public String getToolTipText() { return this.getName(); } public File getImageFile() { return imageFile; } public BufferedImage getImage() { return image; } /** * loads the image and reprojects it using a transformation * calculated by the new reference system. */ void resample(CoordinateReferenceSystem refSys) throws IOException, NoSuchAuthorityCodeException, FactoryException { logger.debug("resample"); GridCoverage2D coverage = PluginOperations.createGridFromFile(this.imageFile, refSys, true); coverage = PluginOperations.reprojectCoverage(coverage, CRS.decode(Main.getProjection().toCode())); this.bbox = coverage.getEnvelope2D(); this.image = ((PlanarImage) coverage.getRenderedImage()).getAsBufferedImage(); upperLeft = new EastNorth(coverage.getEnvelope2D().x, coverage .getEnvelope2D().y + coverage.getEnvelope2D().height); angle = 0; // repaint and zoom to new bbox BoundingXYVisitor boundingXYVisitor = new BoundingXYVisitor(); visitBoundingBox(boundingXYVisitor); Main.map.mapView.zoomTo(boundingXYVisitor); } /** * Action that creates a dialog GUI element with properties of a layer. * */ public class LayerPropertiesAction extends AbstractAction { public ImageLayer imageLayer; public LayerPropertiesAction(ImageLayer imageLayer) { super(tr("Layer Properties")); this.imageLayer = imageLayer; } public void actionPerformed(ActionEvent arg0) { LayerPropertiesDialog layerProps = new LayerPropertiesDialog(imageLayer, PluginOperations.crsDescriptions); layerProps.setLocation(Main.parent.getWidth() / 4, Main.parent.getHeight() / 4); layerProps.setVisible(true); } } /** * Exception which represents that the layer creation has been canceled by the user. */ class LayerCreationCanceledException extends IOException{ } public CoordinateReferenceSystem getSourceRefSys() { return sourceRefSys; } public void setSourceRefSys(CoordinateReferenceSystem sourceRefSys) { this.sourceRefSys = sourceRefSys; } }