package com.revolsys.swing.map.overlay; import java.awt.Color; import java.awt.Cursor; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.util.ArrayList; import java.util.List; import javax.swing.SwingUtilities; import com.revolsys.awt.WebColors; import com.revolsys.geometry.model.BoundingBox; import com.revolsys.geometry.model.Geometry; import com.revolsys.geometry.model.GeometryFactory; import com.revolsys.geometry.model.LineString; import com.revolsys.geometry.model.Point; import com.revolsys.geometry.model.Polygon; import com.revolsys.geometry.model.vertex.Vertex; import com.revolsys.io.BaseCloseable; import com.revolsys.raster.BufferedGeoreferencedImage; import com.revolsys.raster.GeoreferencedImage; import com.revolsys.raster.MappedLocation; import com.revolsys.swing.Icons; import com.revolsys.swing.SwingUtil; import com.revolsys.swing.map.ImageViewport; import com.revolsys.swing.map.MapPanel; import com.revolsys.swing.map.Viewport2D; import com.revolsys.swing.map.layer.raster.GeoreferencedImageLayer; import com.revolsys.swing.map.layer.raster.GeoreferencedImageLayerRenderer; import com.revolsys.swing.map.layer.record.renderer.GeometryStyleRenderer; import com.revolsys.swing.map.layer.record.renderer.MarkerStyleRenderer; import com.revolsys.swing.map.layer.record.style.GeometryStyle; import com.revolsys.swing.map.layer.record.style.MarkerStyle; import com.revolsys.swing.undo.ListAddUndo; import com.revolsys.swing.undo.SetObjectProperty; import com.revolsys.util.Booleans; public class EditGeoreferencedImageOverlay extends AbstractOverlay { public static final String ACTION_MOVE_IMAGE = "moveImage"; private static final String ACTION_MOVE_IMAGE_CORNER = "moveImageCorner"; private static final String ACTION_TIE_POINT_ADD = "Add Tie Point"; private static final String ACTION_TIE_POINT_MOVE_SOURCE = "moveSourceTiePoint"; private static final String ACTION_TIE_POINT_MOVE_TARGET = "moveTargetTiePoint"; private static final Color COLOR_OUTLINE = WebColors.Black; private static final Color COLOR_SELECT = WebColors.Cyan; private static final Cursor CURSOR_MOVE_IMAGE = Icons.getCursor("cursor_move", 8, 7); private static final Cursor CURSOR_SOURCE_PIXEL_ADD = Icons.getCursor("cursor_source_pixel_add", 5, 5); private static final long serialVersionUID = 1L; private static final MarkerStyle STYLE_BOX_CORNER = MarkerStyle.marker("cross", 11, WebColors.Black, 1, WebColors.Lime); private static final GeometryStyle STYLE_BOX_OUTLINE = GeometryStyle.line(Color.GREEN, 3); private static final GeometryStyle STYLE_IMAGE_LINE = GeometryStyle.line(COLOR_SELECT, 1); private static final MarkerStyle STYLE_VERTEX_FIRST_POINT = MarkerStyle .marker(SelectedRecordsVertexRenderer.firstVertexShape(), 9, COLOR_OUTLINE, 1, COLOR_SELECT); private static final MarkerStyle STYLE_VERTEX_LAST_POINT = MarkerStyle .marker(SelectedRecordsVertexRenderer.lastVertexShape(), 9, COLOR_OUTLINE, 1, COLOR_SELECT); static { STYLE_VERTEX_FIRST_POINT.setMarkerOrientationType("auto"); STYLE_VERTEX_FIRST_POINT.setMarkerPlacementType("vertex(0)"); STYLE_VERTEX_FIRST_POINT.setMarkerHorizontalAlignment("center"); STYLE_VERTEX_LAST_POINT.setMarkerOrientationType("auto"); STYLE_VERTEX_LAST_POINT.setMarkerPlacementType("vertex(n)"); STYLE_VERTEX_LAST_POINT.setMarkerHorizontalAlignment("right"); } private static final VertexStyleRenderer TIE_POINT_CLOSE_VERTEX_RENDERER = new VertexStyleRenderer( WebColors.RoyalBlue); private static final SelectedRecordsRenderer TIE_POINT_RENDERER = new SelectedRecordsRenderer( WebColors.Aqua, 127); private static final SelectedRecordsVertexRenderer TIE_POINT_VERTEX_RENDERER = new SelectedRecordsVertexRenderer( WebColors.Aqua, true); private Point addTiePointFirstPoint; private Point addTiePointMove; private GeoreferencedImage cachedImage; private List<Integer> closeSourcePixelIndexes = new ArrayList<>(); private List<Integer> closeTargetPointIndexes = new ArrayList<>(); private GeoreferencedImage image; private GeoreferencedImageLayer layer; private Point moveCornerOppositePoint; private Point moveCornerPoint; private BoundingBox moveImageBoundingBox; private Point moveImageFirstPoint; private java.awt.Point moveTiePointEventPoint; private int moveTiePointIndex = -1; private Point moveTiePointOpposite; private Point moveTiePointLocation; private boolean moveTiePointSource; private boolean moveTiePointStarted; public EditGeoreferencedImageOverlay(final MapPanel map) { super(map); addOverlayActionOverride( // SelectRecordsOverlay.ACTION_SELECT_RECORDS, // ACTION_MOVE_IMAGE, // ACTION_MOVE_IMAGE_CORNER); addOverlayAction( // ACTION_MOVE_IMAGE, // CURSOR_MOVE_IMAGE, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM, // ACTION_MOVE_IMAGE_CORNER // ); addOverlayActionOverride( // ACTION_MOVE_IMAGE_CORNER, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM, // ACTION_TIE_POINT_ADD // ); addOverlayAction( // ACTION_TIE_POINT_ADD, // CURSOR_NODE_ADD, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM // ); addOverlayAction( // ACTION_TIE_POINT_MOVE_SOURCE, // CURSOR_NODE_ADD, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM, ACTION_MOVE_IMAGE // ); addOverlayAction( // ACTION_TIE_POINT_MOVE_TARGET, // CURSOR_NODE_ADD, // ZoomOverlay.ACTION_PAN, // ZoomOverlay.ACTION_ZOOM, // ACTION_MOVE_IMAGE // ); } protected void addTiePointClear() { this.addTiePointFirstPoint = null; this.addTiePointMove = null; clearCachedImage(); clearSnapLocations(); setXorGeometry(null); clearOverlayAction(ACTION_TIE_POINT_ADD); } private boolean addTiePointFinish(final MouseEvent event) { if (event == null || event.getButton() == MouseEvent.BUTTON1) { if (this.addTiePointFirstPoint != null) { if (clearOverlayAction(ACTION_TIE_POINT_ADD)) { try { clearMapCursor(CURSOR_SOURCE_PIXEL_ADD); Point mapPoint = getEventPoint(); final Point snapPoint = getSnapPoint(); if (snapPoint != null) { mapPoint = snapPoint; } final Point sourcePoint = this.addTiePointFirstPoint; final Point sourcePixel = this.layer.targetPointToSourcePixel(sourcePoint); final GeometryFactory geometryFactory = getImageGeometryFactory(); final Point targetPoint = mapPoint.newGeometry(geometryFactory); final MappedLocation mappedLocation = new MappedLocation(sourcePixel, targetPoint); addUndo(new ListAddUndo(this.image.getTiePoints(), mappedLocation)); } finally { addTiePointClear(); } return true; } } } return false; } private boolean addTiePointMove(final MouseEvent event) { if (this.addTiePointFirstPoint != null) { if (hasSnapPoint()) { this.addTiePointMove = getSnapPoint(); } else { this.addTiePointMove = getEventPoint(); } final GeometryFactory geometryFactory = getGeometryFactory(); final Geometry xorGeometry = newXorLine(geometryFactory, this.addTiePointFirstPoint, this.addTiePointMove); setXorGeometry(xorGeometry); return true; } else if (SwingUtil.isAltDown(event)) { if (isInImage(event)) { setOverlayAction(ACTION_TIE_POINT_ADD); return true; } else { clearOverlayAction(ACTION_TIE_POINT_ADD); } } return false; } private boolean addTiePointStart(final MouseEvent event) { if (this.layer != null) { if (addTiePointFinish(event)) { } else if (SwingUtil.isLeftButtonAndAltDown(event)) { if (isInImage(event)) { if (setOverlayAction(ACTION_TIE_POINT_ADD)) { final Point mousePoint = getEventPoint(); this.addTiePointFirstPoint = mousePoint; event.consume(); return true; } } } } return false; } protected void adjustBoundingBoxAspectRatio() { if (this.moveImageBoundingBox != null && this.moveCornerPoint != null) { final double imageAspectRatio = this.image.getImageAspectRatio(); final BoundingBox boundingBox = this.moveImageBoundingBox; final double aspectRatio = boundingBox.getAspectRatio(); double minX = boundingBox.getMinX(); double maxX = boundingBox.getMaxX(); double minY = boundingBox.getMinY(); double maxY = boundingBox.getMaxY(); final double width = boundingBox.getWidth(); final double height = boundingBox.getHeight(); if (aspectRatio < imageAspectRatio) { if (minX == this.moveCornerOppositePoint.getX()) { maxX = minX + height * imageAspectRatio; } else { minX = maxX - height * imageAspectRatio; } } else if (aspectRatio > imageAspectRatio) { if (minY == this.moveCornerOppositePoint.getY()) { maxY = minY + width / imageAspectRatio; } else { minY = maxY - width / imageAspectRatio; } } final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory(); this.moveImageBoundingBox = viewportGeometryFactory.newBoundingBox(minX, minY, maxX, maxY); } } protected void appendTiePointLocations(final StringBuilder toolTip, final List<MappedLocation> tiePoints, final List<Integer> indices, final int startNumber, final boolean source) { if (!indices.isEmpty()) { int i = startNumber - 1; for (final Integer index : indices) { final MappedLocation tiePoint = tiePoints.get(index); toolTip.append("<div style=\"border-top: 1px solid #666666;padding: 1px;"); if (i == this.moveTiePointIndex) { toolTip.append("background-color: #0000ff;color: #ffffff"); } else { toolTip.append("background-color: #ffffff"); } toolTip.append("\">"); toolTip.append(i); toolTip.append(". "); final Point point; if (source) { point = tiePoint.getSourcePixel(); toolTip.append("Source: "); } else { point = tiePoint.getTargetPoint(); toolTip.append("Target: "); } appendPoint(toolTip, point); if (!source) { toolTip.append(" ("); toolTip.append(point.getCoordinateSystemId()); toolTip.append(")"); } toolTip.append("</div>"); i++; } i++; } } protected void cancel() { moveImageClear(); moveCornerClear(); moveTiePointClear(); addTiePointClear(); clearCachedImage(); repaint(0); } protected void clear() { this.image = null; this.layer = null; clearCachedImage(); clearUndoHistory(); cancel(); } protected void clearCachedImage() { this.cachedImage = null; System.gc(); } @Override public void focusLost(final FocusEvent e) { if (isEditing()) { cancel(); } } private GeoreferencedImage getCachedImage(BoundingBox boundingBox) { boundingBox = boundingBox.convert(getViewportGeometryFactory()); final Viewport2D viewport = getViewport(); final BoundingBox viewBoundingBox = viewport.getBoundingBox(); if (this.cachedImage == null || !this.cachedImage.getBoundingBox().equals(viewBoundingBox)) { try ( final ImageViewport imageViewport = new ImageViewport(viewport)) { final BufferedImage image = imageViewport.getImage(); final Graphics2D graphics = (Graphics2D)image.getGraphics(); this.image.drawImage(graphics, viewBoundingBox, viewport.getViewWidthPixels(), viewport.getViewHeightPixels(), !this.layer.isShowOriginalImage()); GeoreferencedImageLayerRenderer.render(imageViewport, graphics, this.image, !this.layer.isShowOriginalImage()); this.cachedImage = new BufferedGeoreferencedImage(imageViewport.getBoundingBox(), image); } } return this.cachedImage; } public BoundingBox getImageBoundingBox() { if (this.image == null) { return BoundingBox.empty(); } else { return this.layer.getBoundingBox(); } } public GeometryFactory getImageGeometryFactory() { if (this.image == null) { return getGeometryFactory(); } else { return this.layer.getGeometryFactory(); } } public GeoreferencedImageLayer getLayer() { return this.layer; } protected BoundingBox getMoveBoundingBox(final MouseEvent event) { BoundingBox boundingBox = getImageBoundingBox(); final Point mousePoint = getEventPoint(); final GeometryFactory imageGeometryFactory = getImageGeometryFactory(); final Point imagePoint = mousePoint.convertGeometry(imageGeometryFactory); final double deltaX = imagePoint.getX() - this.moveImageFirstPoint.getX(); final double deltaY = imagePoint.getY() - this.moveImageFirstPoint.getY(); boundingBox = boundingBox.move(deltaX, deltaY); return boundingBox; } private MappedLocation getMoveTiePoint() { if (this.moveTiePointIndex > -1) { int tiePointIndex; final int targetSize = this.closeTargetPointIndexes.size(); if (this.moveTiePointIndex < targetSize) { tiePointIndex = this.closeTargetPointIndexes.get(this.moveTiePointIndex); this.moveTiePointSource = false; } else if (this.moveTiePointIndex - targetSize < this.closeSourcePixelIndexes.size()) { tiePointIndex = this.closeSourcePixelIndexes.get(this.moveTiePointIndex - targetSize); this.moveTiePointSource = true; } else { return null; } return this.image.getTiePoints().get(tiePointIndex); } return null; } protected boolean isApplicable(final MouseEvent event) { final BoundingBox imageBoundingBox = getImageBoundingBox(); final Point point = getPoint(event); final double distance = getDistance(event); return imageBoundingBox.distance(point) < distance * 2; } public boolean isEditing() { if (this.layer != null) { return this.layer.isEditable(); } return false; } private boolean isInImage() { final Point mousePoint = getEventPoint(); return isInImage(mousePoint); } private boolean isInImage(final MouseEvent event) { final Point mousePoint = getPoint(event); final boolean inImage = isInImage(mousePoint); return inImage; } private boolean isInImage(final Point mousePoint) { if (mousePoint == null) { return false; } else { final BoundingBox imageBoundingBox = getImageBoundingBox(); final boolean inImage = imageBoundingBox.covers(mousePoint); return inImage; } } @Override public void keyPressed(final KeyEvent event) { if (isEditing()) { final int keyCode = event.getKeyCode(); if (this.moveCornerPoint != null) { if (keyCode == KeyEvent.VK_SHIFT) { adjustBoundingBoxAspectRatio(); repaint(); } } else { if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_META) { if (isInImage()) { setOverlayAction(ACTION_MOVE_IMAGE); } } else if (keyCode == KeyEvent.VK_ALT) { if (isInImage()) { if (setOverlayAction(ACTION_TIE_POINT_ADD)) { event.consume(); } } } } } } @Override public void keyReleased(final KeyEvent event) { if (isEditing()) { final int keyCode = event.getKeyCode(); if (this.moveTiePointIndex > -1 || this.addTiePointFirstPoint != null || this.moveTiePointStarted) { final char keyChar = event.getKeyChar(); if (keyChar >= '0' && keyChar <= '9') { event.consume(); } } if (keyCode == KeyEvent.VK_ESCAPE) { cancel(); repaint(); } else if (keyCode == KeyEvent.VK_CONTROL || keyCode == KeyEvent.VK_META) { if (this.moveImageFirstPoint == null) { moveImageClear(); } } else if (keyCode == KeyEvent.VK_ALT) { if (this.addTiePointFirstPoint == null) { addTiePointClear(); } } } } @Override public void keyTyped(final KeyEvent e) { if (isEditing()) { final char keyChar = e.getKeyChar(); if (keyChar >= '0' && keyChar <= '9') { final int index = keyChar - '0'; if (!this.moveTiePointStarted && this.moveTiePointIndex > -1) { if (index >= 0 && index < this.closeSourcePixelIndexes.size() + this.closeTargetPointIndexes.size()) { this.moveTiePointIndex = index; setMoveTiePointToolTip(); } } else if (index < getSnapPointLocationMap().size()) { setSnapPointIndex(index); setSnapLocations(getSnapPointLocationMap()); } } } } @Override public void mouseClicked(final MouseEvent event) { if (isEditing()) { if (isApplicable(event)) { if (addTiePointFinish(event)) { } else if (addTiePointStart(event)) { } } } } @Override public void mouseDragged(final MouseEvent event) { if (isEditing()) { if (addTiePointMove(event)) { } else if (moveTiePointDrag(event)) { } else if (moveCornerDrag(event)) { } else if (moveImageDrag(event)) { } } } @Override public void mouseMoved(final MouseEvent event) { if (isEditing()) { if (addTiePointMove(event)) { } else if (moveCornerMove(event)) { } else if (moveImageMove(event)) { } else if (moveTiePointMove(event)) { } } } @Override public void mousePressed(final MouseEvent event) { if (isEditing()) { if (moveImageStart(event)) { } else if (moveTiePointStart(event)) { } else if (isInImage() && SwingUtil.isLeftButtonAndAltDown(event)) { event.consume(); } else if (moveCornerStart(event)) { } } } @Override public void mouseReleased(final MouseEvent event) { if (isEditing()) { if (event.getButton() == MouseEvent.BUTTON1) { if (moveTiePointFinish(event)) { } else if (moveCornerFinish(event)) { } else if (moveCornerFinish(event)) { } else if (moveImageFinish(event)) { } } } } protected void moveCornerClear() { clearOverlayAction(ACTION_MOVE_IMAGE_CORNER); this.moveImageBoundingBox = null; this.moveCornerOppositePoint = null; this.moveCornerPoint = null; } private boolean moveCornerDrag(final MouseEvent event) { if (isOverlayAction(ACTION_MOVE_IMAGE_CORNER) && this.moveCornerOppositePoint != null) { final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory(); final Point mousePoint = getEventPoint(); this.moveImageBoundingBox = viewportGeometryFactory.newBoundingBox(mousePoint.getX(), mousePoint.getY(), this.moveCornerOppositePoint.getX(), this.moveCornerOppositePoint.getY()); if (SwingUtil.isShiftDown(event)) { adjustBoundingBoxAspectRatio(); } repaint(); return true; } return false; } private boolean moveCornerFinish(final MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1) { if (clearOverlayAction(ACTION_MOVE_IMAGE_CORNER)) { try { if (this.moveImageBoundingBox != null) { final SetObjectProperty setBBox = new SetObjectProperty(this, "imageBoundingBox", getImageBoundingBox(), this.moveImageBoundingBox); addUndo(setBBox); event.consume(); } } finally { moveCornerClear(); } repaint(); return true; } } return false; } private boolean moveCornerMove(final MouseEvent event) { Cursor moveCornerCursor = null; final Point oldPoint = this.moveCornerPoint; this.moveCornerPoint = null; this.moveCornerOppositePoint = null; final int modifiers = event.getModifiersEx(); if (modifiers == 0 || modifiers == InputEvent.SHIFT_DOWN_MASK) { if (this.layer != null) { final Point mousePoint = getEventPoint(); final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory(); Point closestPoint = null; final double maxDistance = getDistance(event); double closestDistance = Double.MAX_VALUE; if (oldPoint != null) { final double distance = oldPoint.distancePoint(mousePoint); if (distance < maxDistance) { closestPoint = oldPoint; closestDistance = distance; } } int closestIndex = -1; BoundingBox imageBoundingBox = getImageBoundingBox(); if (!imageBoundingBox.isEmpty()) { imageBoundingBox = imageBoundingBox.convert(viewportGeometryFactory); for (int i = 0; i < 4; i++) { final Point point = imageBoundingBox.getCornerPoint(i); final Point mapPoint = point.convertPoint2d(viewportGeometryFactory); final double distance = mapPoint.distancePoint(mousePoint); if (distance < maxDistance && distance < closestDistance) { closestPoint = point; closestDistance = distance; closestIndex = i; } } if (closestPoint == oldPoint) { if (oldPoint != null) { return true; } } else { this.moveCornerPoint = closestPoint; if (closestIndex == -1) { this.moveCornerOppositePoint = null; } else { this.moveCornerOppositePoint = imageBoundingBox.getCornerPoint(closestIndex + 2) .convertGeometry(viewportGeometryFactory); } switch (closestIndex) { case 0: moveCornerCursor = Cursor.getPredefinedCursor(Cursor.SE_RESIZE_CURSOR); break; case 1: moveCornerCursor = Cursor.getPredefinedCursor(Cursor.SW_RESIZE_CURSOR); break; case 2: moveCornerCursor = Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR); break; case 3: moveCornerCursor = Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR); break; } } } } } if (this.moveCornerPoint == null) { moveCornerClear(); return false; } else { if (setOverlayAction(ACTION_MOVE_IMAGE_CORNER)) { setMapCursor(moveCornerCursor); } return true; } } private boolean moveCornerStart(final MouseEvent event) { if (this.layer != null) { if (event.getButton() == MouseEvent.BUTTON1 && !event.isAltDown()) { if (this.moveCornerPoint != null) { if (isOverlayAction(ACTION_MOVE_IMAGE_CORNER)) { event.consume(); return true; } } } } return false; } protected void moveImageClear() { this.moveImageFirstPoint = null; this.moveImageBoundingBox = null; clearOverlayAction(ACTION_MOVE_IMAGE); } private boolean moveImageDrag(final MouseEvent event) { if (isOverlayAction(ACTION_MOVE_IMAGE) && this.moveImageFirstPoint != null) { this.moveImageBoundingBox = getMoveBoundingBox(event); repaint(0); return true; } return false; } private boolean moveImageFinish(final MouseEvent event) { if (event.getButton() == MouseEvent.BUTTON1) { if (clearOverlayAction(ACTION_MOVE_IMAGE)) { final BoundingBox boundingBox = getMoveBoundingBox(event); final SetObjectProperty setBBox = new SetObjectProperty(this, "imageBoundingBox", getImageBoundingBox(), boundingBox); addUndo(setBBox); moveImageClear(); event.consume(); repaint(); return true; } } return false; } private boolean moveImageMove(final MouseEvent event) { if (isInImage(event)) { if (SwingUtil.isControlOrMetaDown(event)) { if (setOverlayAction(ACTION_MOVE_IMAGE)) { event.consume(); return true; } } } moveImageClear(); return false; } private boolean moveImageStart(final MouseEvent event) { if (this.layer != null) { if (event.getButton() == MouseEvent.BUTTON1 && SwingUtil.isControlOrMetaDown(event)) { if (isOverlayAction(ACTION_MOVE_IMAGE)) { final Point mousePoint = getEventPoint(); final GeometryFactory imageGeometryFactory = getImageGeometryFactory(); final Point imagePoint = mousePoint.convertGeometry(imageGeometryFactory); this.moveImageFirstPoint = imagePoint; event.consume(); return true; } } } return false; } private void moveTiePointClear() { this.closeSourcePixelIndexes.clear(); this.closeTargetPointIndexes.clear(); this.moveTiePointEventPoint = null; this.moveTiePointIndex = -1; this.moveTiePointLocation = null; this.moveTiePointStarted = false; setXorGeometry(null); clearOverlayAction(ACTION_TIE_POINT_MOVE_SOURCE); clearOverlayAction(ACTION_TIE_POINT_MOVE_TARGET); } private boolean moveTiePointDrag(final MouseEvent event) { if (SwingUtil.isLeftButtonOnly(event)) { if (this.moveTiePointStarted) { if (this.moveTiePointSource) { this.moveTiePointLocation = getEventPoint(); } else { if (hasSnapPoint()) { this.moveTiePointLocation = getSnapPoint(); } else { this.moveTiePointLocation = getEventPoint(); } } event.consume(); final GeometryFactory geometryFactory = getGeometryFactory(); final Geometry xorGeometry = newXorLine(geometryFactory, this.moveTiePointOpposite, this.moveTiePointLocation); setXorGeometry(xorGeometry); repaint(); return true; } } return false; } private boolean moveTiePointFinish(final MouseEvent event) { if (this.moveTiePointStarted) { final MappedLocation tiePoint = getMoveTiePoint(); if (tiePoint != null) { Point point = getPoint(event); String action; if (this.moveTiePointSource) { action = ACTION_TIE_POINT_MOVE_SOURCE; } else { action = ACTION_TIE_POINT_MOVE_TARGET; } if (clearOverlayAction(action)) { if (this.moveTiePointSource) { final Point sourcePixel = this.layer.targetPointToSourcePixel(point); final SetObjectProperty setSourcePixel = new SetObjectProperty(tiePoint, "sourcePixel", tiePoint.getSourcePixel(), sourcePixel); addUndo(setSourcePixel); } else { final Point snapPoint = getSnapPoint(); if (snapPoint != null) { point = snapPoint; } final GeometryFactory imageGeometryFactory = getImageGeometryFactory(); point = point.newGeometry(imageGeometryFactory); tiePoint.setTargetPoint(point); final SetObjectProperty setTargetPoint = new SetObjectProperty(tiePoint, "targetPoint", tiePoint.getTargetPoint(), point); addUndo(setTargetPoint); } this.closeSourcePixelIndexes.clear(); this.closeTargetPointIndexes.clear(); moveTiePointClear(); clearCachedImage(); clearMapCursor(); clearSnapLocations(); if (event != null) { event.consume(); } repaint(); return true; } } } return false; } private boolean moveTiePointMove(final MouseEvent event) { boolean hasMove = false; if (this.image != null) { final List<MappedLocation> tiePoints = this.image.getTiePoints(); if (!tiePoints.isEmpty()) { final List<Integer> closeSourcePixelIndexes = new ArrayList<>(); final List<Integer> closeTargetPointIndexes = new ArrayList<>(); final BoundingBox hotSpot = getHotspotBoundingBox(); int i = 0; boolean hasSource = false; for (final MappedLocation tiePoint : tiePoints) { final Point sourcePoint = this.layer.sourcePixelToTargetPoint(tiePoint); if (hotSpot.covers(sourcePoint)) { closeSourcePixelIndexes.add(i); hasMove = true; hasSource = true; } final Point targetPoint = tiePoint.getTargetPoint(); if (hotSpot.covers(targetPoint)) { closeTargetPointIndexes.add(i); hasMove = true; } i++; } final boolean changed = !closeSourcePixelIndexes.equals(this.closeSourcePixelIndexes) || !closeTargetPointIndexes.equals(this.closeTargetPointIndexes); this.closeSourcePixelIndexes = closeSourcePixelIndexes; this.closeTargetPointIndexes = closeTargetPointIndexes; if (changed && hasMove) { this.moveTiePointIndex = 0; this.moveTiePointEventPoint = event.getPoint(); } final boolean tooltipSet = setMoveTiePointToolTip(); if (changed && hasMove) { if (tooltipSet) { if (hasSource) { setOverlayAction(ACTION_TIE_POINT_MOVE_SOURCE); } else { setOverlayAction(ACTION_TIE_POINT_MOVE_TARGET); } this.moveTiePointSource = hasSource; } } } } if (hasMove) { return true; } else { moveTiePointClear(); return false; } } private boolean moveTiePointStart(final MouseEvent event) { if (this.moveTiePointIndex > -1) { if (SwingUtilities.isLeftMouseButton(event)) { final MappedLocation tiePoint = getMoveTiePoint(); String action; if (this.moveTiePointSource) { action = ACTION_TIE_POINT_MOVE_SOURCE; this.moveTiePointOpposite = tiePoint.getTargetPoint(); } else { action = ACTION_TIE_POINT_MOVE_TARGET; final GeoreferencedImage image = this.image; final boolean showOriginalImage = this.layer.isShowOriginalImage(); final BoundingBox boundingBox = getImageBoundingBox(); this.moveTiePointOpposite = tiePoint.getSourcePoint(image, boundingBox, !showOriginalImage); } if (setOverlayAction(action)) { this.moveTiePointStarted = true; setMapCursor(CURSOR_NODE_ADD); getMap().clearToolTipText(); event.consume(); repaint(); return true; } } } return false; } @Override protected void paintComponent(final Viewport2D viewport, final Graphics2D graphics) { super.paintComponent(viewport, graphics); final GeoreferencedImageLayer layer = this.layer; final GeoreferencedImage image = this.image; final BoundingBox moveImageBoundingBox = this.moveImageBoundingBox; if (layer != null && layer.isVisible() && layer.isExists() && image != null) { try ( BaseCloseable transformCloseable = viewport.setUseModelCoordinates(graphics, true)) { final GeometryFactory viewportGeometryFactory = viewport.getGeometryFactory(); final boolean showOriginalImage = layer.isShowOriginalImage(); final BoundingBox imageBoundingBox = getImageBoundingBox(); BoundingBox boundingBox = imageBoundingBox; BoundingBox outlineBoundingBox = boundingBox; if (moveImageBoundingBox != null) { if (showOriginalImage) { boundingBox = moveImageBoundingBox; } outlineBoundingBox = moveImageBoundingBox; } final GeoreferencedImage cachedImage = getCachedImage(boundingBox); GeoreferencedImageLayerRenderer.renderAlpha(viewport, graphics, cachedImage, false, layer.getOpacity() / 255.0); GeoreferencedImageLayerRenderer.renderDifferentCoordinateSystem(viewport, graphics, imageBoundingBox); if (outlineBoundingBox != null && !outlineBoundingBox.isEmpty()) { final Polygon imageBoundary = outlineBoundingBox.convert(getViewportGeometryFactory()) .toPolygon(1); graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); viewport.drawGeometryOutline(imageBoundary, STYLE_BOX_OUTLINE); MarkerStyleRenderer.renderMarkerVertices(viewport, graphics, imageBoundary, STYLE_BOX_CORNER); final List<MappedLocation> tiePoints = image.getTiePoints(); final int tiePointCount = tiePoints.size(); final GeometryFactory viewGeometryFactory = getGeometryFactory(); if (tiePointCount > 0) { for (final MappedLocation mappedLocation : tiePoints) { LineString line = mappedLocation.getSourceToTargetLine(image, boundingBox, !showOriginalImage); if (line != null) { line = line.convertGeometry(viewportGeometryFactory); try ( BaseCloseable transformCloseable1 = viewport.setUseModelCoordinates(graphics, true)) { TIE_POINT_RENDERER.paintSelected(viewport, graphics, viewportGeometryFactory, line); } } } for (final MappedLocation mappedLocation : tiePoints) { LineString line = mappedLocation.getSourceToTargetLine(image, boundingBox, !showOriginalImage); if (line != null) { line = line.convertGeometry(viewportGeometryFactory); TIE_POINT_VERTEX_RENDERER.paintSelected(viewport, graphics, viewportGeometryFactory, line); } } } if (!showOriginalImage) { final double width = image.getImageWidth() - 1; final double height = image.getImageHeight() - 1; final double[] targetCoordinates = MappedLocation.toModelCoordinates(image, boundingBox, true, 0, height, width, height, width, 0, 0, 0, 0, height); final LineString line = viewGeometryFactory.lineString(2, targetCoordinates); GeometryStyleRenderer.renderLineString(viewport, graphics, line, STYLE_IMAGE_LINE); } } final MappedLocation moveTiePoint = getMoveTiePoint(); if (moveTiePoint != null) { final LineString line = moveTiePoint.getSourceToTargetLine(image, boundingBox, !showOriginalImage); Vertex vertex; if (this.moveTiePointSource) { vertex = line.getVertex(0); } else { vertex = line.getVertex(-1); } TIE_POINT_CLOSE_VERTEX_RENDERER.paintSelected(viewport, graphics, viewportGeometryFactory, vertex); } } } drawXorGeometry(graphics); } @Override public void propertyChange(final PropertyChangeEvent event) { super.propertyChange(event); final Object source = event.getSource(); final String propertyName = event.getPropertyName(); if (source instanceof GeoreferencedImageLayer) { final GeoreferencedImageLayer layer = (GeoreferencedImageLayer)source; if ("editable".equals(propertyName)) { if (!Booleans.getBoolean(event.getNewValue())) { if (this.layer == layer) { setLayer(null); } } else { setLayer(layer); } } else if (this.layer == layer) { clearCachedImage(); if ("boundingBox".equals(propertyName)) { final BoundingBox boundingBox = layer.getBoundingBox(); setImageBoundingBox(boundingBox); } else if ("hasChanges".equals(propertyName)) { clear(); setLayer(layer); } else if ("deleted".equals(propertyName)) { clear(); } } } else if (source == this.image) { clearCachedImage(); } else if ("scale".equals(propertyName)) { clearCachedImage(); } } public void setImageBoundingBox(BoundingBox boundingBox) { if (boundingBox == null) { final GeometryFactory viewportGeometryFactory = getViewportGeometryFactory(); boundingBox = viewportGeometryFactory.newBoundingBoxEmpty(); } setGeometryFactory(boundingBox.getGeometryFactory()); if (this.image != null) { this.image.setBoundingBox(boundingBox); } clearCachedImage(); } public void setLayer(final GeoreferencedImageLayer layer) { final GeoreferencedImageLayer oldLayer = this.layer; if (oldLayer != layer) { clear(); this.layer = layer; final Viewport2D viewport = getViewport(); setGeometryFactory(viewport.getGeometryFactory()); setEnabled(layer != null); if (layer != null) { this.image = layer.getImage(); setImageBoundingBox(layer.getBoundingBox()); } if (oldLayer != null) { oldLayer.setEditable(false); } } firePropertyChange("layer", oldLayer, layer); } protected boolean setMoveTiePointToolTip() { this.moveTiePointStarted = false; if (!this.closeSourcePixelIndexes.isEmpty() || !this.closeTargetPointIndexes.isEmpty()) { final List<MappedLocation> tiePoints = this.image.getTiePoints(); final StringBuilder toolTip = new StringBuilder(); toolTip.append("<html>"); appendTiePointLocations(toolTip, tiePoints, this.closeTargetPointIndexes, 1, false); appendTiePointLocations(toolTip, tiePoints, this.closeSourcePixelIndexes, this.closeTargetPointIndexes.size() + 1, true); toolTip.append("</html>"); getMap().setToolTipText(this.moveTiePointEventPoint, toolTip); return true; } return false; } }