diff --git a/.gitignore b/.gitignore index b46f1df..335abcb 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,7 @@ bin/ ### Other ### osmjar/ + +*.env +### constants +./src/**/utils/Constants.java \ No newline at end of file diff --git a/README.md b/README.md index 2bbbbcb..134cf4b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ -# Josm Magic Wand +# DS-annotate plugin for Java OpenStreetMap Editor -Plugin created for the [JOSM](https://josm.openstreetmap.de/), allows you to select areas to label using a range of -colors, it is also possible to add areas and subtract selected areas. +The DS-annotate plugin is an extension for the Java OpenStreetMap Desktop Editor (JOSM). Built upon ds-annotate, this plugin utilizes the [Segment Anything Service](https://github.com/developmentseed/segment-anything-services), which leverages Generative AI. Additionally, it allows users to label areas using a spectrum of colors, similar to a magic wand tool, enabling faster and more accurate mapping. ![Peek 2022-11-09 16-53](https://user-images.githubusercontent.com/12978932/200950045-179c72d5-600c-4012-b2a4-3ceb1345ea25.gif) diff --git a/build.gradle.kts b/build.gradle.kts index f972974..394105c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } group = "org.openstreetmap.josm.plugins.devseed.JosmMagicWand" -version = "1.2.0" +version = "1.2.2" java { toolchain { @@ -16,17 +16,18 @@ java { } } +tasks.jar { + duplicatesStrategy = DuplicatesStrategy.INCLUDE // O DuplicatesStrategy.EXCLUDE si deseas excluir duplicados +} +// Repositories Configuration allprojects { repositories { mavenLocal() + mavenCentral() } } -configurations { create("externalLibs") } - -repositories { - mavenCentral() -} +// Source Sets Configuration sourceSets { create("libs") { java { @@ -42,18 +43,20 @@ sourceSets { } } } -val libsImplementation: Configuration by configurations.getting { - extendsFrom(configurations.implementation.get()) + +configurations { + create("externalLibs") } dependencies { + // Libraries to be packed into the JAR packIntoJar("org.locationtech.jts:jts-core:1.19.0") + packIntoJar("org.locationtech.jts.io:jts-io-common:1.19.0") packIntoJar("org.openpnp:opencv:4.7.0-0") - libsImplementation("org.locationtech.jts:jts-core:1.19.0") - libsImplementation("org.openpnp:opencv:4.7.0-0") + packIntoJar("com.fasterxml.jackson.core:jackson-databind:2.15.2") + packIntoJar("com.squareup.okhttp3:okhttp:4.10.0") } - josm { pluginName = "josm_magic_wand" debugPort = 1729 @@ -68,5 +71,4 @@ josm { website = URL("https://github.com/developmentseed/JosmMagicWand") minJavaVersion = 11 } - -} \ No newline at end of file +} diff --git a/images/cursor/modifier/magic-wand-sam.svg b/images/cursor/modifier/magic-wand-sam.svg new file mode 100644 index 0000000..b1f78d8 --- /dev/null +++ b/images/cursor/modifier/magic-wand-sam.svg @@ -0,0 +1,99 @@ + + + + diff --git a/images/dialogs/magic-wand-encode.svg b/images/dialogs/magic-wand-encode.svg new file mode 100644 index 0000000..bbff56b --- /dev/null +++ b/images/dialogs/magic-wand-encode.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/images/dialogs/magicwand-info.svg b/images/dialogs/magicwand-info.svg new file mode 100644 index 0000000..8d53d81 --- /dev/null +++ b/images/dialogs/magicwand-info.svg @@ -0,0 +1,113 @@ + + diff --git a/images/mapmode/magic-wand-sam.svg b/images/mapmode/magic-wand-sam.svg new file mode 100644 index 0000000..b1f78d8 --- /dev/null +++ b/images/mapmode/magic-wand-sam.svg @@ -0,0 +1,99 @@ + + + + diff --git a/images/mapmode/magic-wand-simplify.svg b/images/mapmode/magic-wand-simplify.svg new file mode 100644 index 0000000..70b86d0 --- /dev/null +++ b/images/mapmode/magic-wand-simplify.svg @@ -0,0 +1,162 @@ + + diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MagicWandAction.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/MagicWandAction.java similarity index 95% rename from src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MagicWandAction.java rename to src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/MagicWandAction.java index 57be4be..3435a13 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MagicWandAction.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/MagicWandAction.java @@ -1,4 +1,4 @@ -package org.openstreetmap.josm.plugins.devseed.JosmMagicWand; +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions; import org.locationtech.jts.geom.Geometry; import org.opencv.core.Mat; @@ -18,6 +18,7 @@ import org.openstreetmap.josm.gui.layer.MapViewPaintable; import org.openstreetmap.josm.gui.util.KeyPressReleaseListener; import org.openstreetmap.josm.gui.util.ModifierExListener; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.ToolSettings; import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.CommonUtils; import org.openstreetmap.josm.spi.preferences.Config; import org.openstreetmap.josm.spi.preferences.PreferenceChangedListener; @@ -66,7 +67,7 @@ private enum Mode { public MagicWandAction() { - super(tr("MAgic Wand action "), "magic-wand", tr("Magic wand add"), Shortcut.registerShortcut("mapmode:magicwandadd", tr("Mode: {0}", tr("Magic wand add")), KeyEvent.VK_1, Shortcut.CTRL), ImageProvider.getCursor("crosshair", null)); + super(tr("Magic Wand"), "magic-wand", tr("Magic wand"), Shortcut.registerShortcut("mapmode:magicwandadd", tr("Mode: {0}", tr("Magic wand add")), KeyEvent.VK_1, Shortcut.CTRL), ImageProvider.getCursor("crosshair", null)); } @@ -158,7 +159,7 @@ public void doKeyPressed(KeyEvent e) { } catch (Exception ex) { Logging.error(ex); cleanMasks(); - new Notification(tr(ex.getMessage())).setIcon(JOptionPane.WARNING_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + new Notification(tr("Error create ways.")).setIcon(JOptionPane.WARNING_MESSAGE).setDuration(Notification.TIME_SHORT).show(); } } } @@ -305,6 +306,7 @@ private void drawContours() throws Exception { DataSet ds = MainApplication.getLayerManager().getEditDataSet(); // simplify List geometriesSimplify = geometries.stream().map(CommonUtils::simplifySmoothGeometry).collect(Collectors.toList()); + if (geometriesSimplify.isEmpty()) return; String tagKey = "magic_wand"; String tagValue = "yes"; if (ToolSettings.getAutoTags()!= null && !ToolSettings.getAutoTags().isEmpty()){ diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MergeSelectAction.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/MergeSelectAction.java similarity index 96% rename from src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MergeSelectAction.java rename to src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/MergeSelectAction.java index fe87c0c..dfad192 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MergeSelectAction.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/MergeSelectAction.java @@ -1,4 +1,4 @@ -package org.openstreetmap.josm.plugins.devseed.JosmMagicWand; +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions; import org.locationtech.jts.geom.Geometry; import org.openstreetmap.josm.actions.JosmAction; @@ -12,6 +12,7 @@ import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.Notification; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.ToolSettings; import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.CommonUtils; import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.CustomPolygon; import org.openstreetmap.josm.tools.Logging; diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/SamDecodeAction.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/SamDecodeAction.java new file mode 100644 index 0000000..cd1da07 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/SamDecodeAction.java @@ -0,0 +1,173 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions; + +import org.locationtech.jts.geom.Geometry; +import org.openstreetmap.josm.actions.mapmode.MapMode; +import org.openstreetmap.josm.command.Command; +import org.openstreetmap.josm.command.SequenceCommand; +import org.openstreetmap.josm.data.UndoRedoHandler; +import org.openstreetmap.josm.data.coor.EastNorth; +import org.openstreetmap.josm.data.coor.LatLon; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.projection.Projection; +import org.openstreetmap.josm.data.projection.ProjectionRegistry; +import org.openstreetmap.josm.data.projection.Projections; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.MapFrame; +import org.openstreetmap.josm.gui.MapView; +import org.openstreetmap.josm.gui.Notification; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.CommonUtils; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.ImageSamPanelListener; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.SamImage; +import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Logging; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.openstreetmap.josm.tools.I18n.tr; + +public class SamDecodeAction extends MapMode implements MouseListener { + private static final Cursor CURSOR_CUSTOM = ImageProvider.getCursor("crosshair", "magic-wand-sam"); + + + private enum Mode { + None, Drawing + } + + private Mode mode = Mode.None; + private Mode nextMode = Mode.None; + private Point drawStartPos; + private Point mousePos; + private ImageSamPanelListener listener; + + public SamDecodeAction(ImageSamPanelListener listener) { + super(tr("Magic Wand SAM"), "magic-wand-sam", tr("Magic Wand SAM action"), null, ImageProvider.getCursor("crosshair", null)); + this.listener = listener; + } + + private Cursor getCursor() { + return CURSOR_CUSTOM; + } + + private void setCursor(final Cursor c) { + MainApplication.getMap().mapView.setNewCursor(c, this); + } + + private void updCursor() { + if (!MainApplication.isDisplayingMapView()) return; + setCursor(getCursor()); + } + + @Override + public void enterMode() { + super.enterMode(); + + MapFrame map = MainApplication.getMap(); + map.mapView.addMouseListener(this); + map.mapView.addMouseMotionListener(this); + + updCursor(); + + } + + @Override + public void exitMode() { + super.exitMode(); + MapFrame map = MainApplication.getMap(); + map.mapView.removeMouseListener(this); + map.mapView.removeMouseMotionListener(this); + if (mode != Mode.None) map.mapView.repaint(); + mode = Mode.None; + } + + + public final void cancelDrawing() { + mode = Mode.None; + MapFrame map = MainApplication.getMap(); + if (map == null || map.mapView == null) return; + map.statusLine.setHeading(-1); + map.statusLine.setAngle(-1); + map.mapView.repaint(); + updateStatusLine(); + } + + private void drawingStart(MouseEvent e) { + mousePos = e.getPoint(); + drawStartPos = mousePos; + mode = Mode.Drawing; + updateStatusLine(); + } + + @Override + public void mouseReleased(MouseEvent e) { + Logging.info("-------- mouseReleased -----------"); + if (e.getButton() != MouseEvent.BUTTON1) return; + if (!MainApplication.getMap().mapView.isActiveLayerDrawable()) return; + + Thread apiThread = new Thread(() -> { + try { + drawWays(e); + } catch (Exception ex) { + Logging.error(ex); + new Notification(tr("Error data generation.")).setIcon(JOptionPane.ERROR_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + } + SwingUtilities.invokeLater(() -> { + System.out.println("later"); + }); + }); + apiThread.start(); + } + + private boolean drawWays(MouseEvent e) throws Exception { + + MapView mapView = MainApplication.getMap().mapView; + DataSet ds = MainApplication.getLayerManager().getEditDataSet(); + + String tagKey = "magic_wand_sam"; + String tagValue = "yes"; + + LatLon latLon = mapView.getLatLon(e.getX(), e.getY()); + Projection projection = ProjectionRegistry.getProjection(); + EastNorth eastNorth = latLon.getEastNorth(projection); + + + SamImage samImage = listener.getSamImageIncludepoint(eastNorth.getX(), eastNorth.getY()); + if (samImage == null) { + new Notification(tr("Click inside of active AOI to enable Segment Anything Model.")).setIcon(JOptionPane.ERROR_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + return false; + } + + List geometrySamList = samImage.fetchDecodePoint(eastNorth.getX(), eastNorth.getY()); + if (geometrySamList.isEmpty()) { + new Notification(tr("Error fetch data.")).setIcon(JOptionPane.ERROR_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + return false; + } + + Projection projectionSam = Projections.getProjectionByCode("EPSG:4326"); + List geometriesMercator = new ArrayList<>(); + + for (Geometry samGeometry : geometrySamList) { + var nodesMercator = CommonUtils.coordinates2Nodes(Arrays.asList(samGeometry.getCoordinates()), projectionSam); + var coordMercator = CommonUtils.nodes2Coordinates(nodesMercator); + var geometry = CommonUtils.coordinates2Polygon(coordMercator); + var geometrySimPolygonHull = CommonUtils.simplifyPolygonHull(geometry.copy(), 0.95); + geometriesMercator.add(geometrySimPolygonHull); + } + + + Collection cmds = CommonUtils.geometry2WayCommands(ds, geometriesMercator, tagKey, tagValue); + + UndoRedoHandler.getInstance().add(new SequenceCommand(tr("generate sam ways"), cmds)); + return !cmds.isEmpty(); + + } +} + + + diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/SimplifySelectAction.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/SimplifySelectAction.java similarity index 90% rename from src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/SimplifySelectAction.java rename to src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/SimplifySelectAction.java index 9c9322d..474ec65 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/SimplifySelectAction.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Actions/SimplifySelectAction.java @@ -1,4 +1,4 @@ -package org.openstreetmap.josm.plugins.devseed.JosmMagicWand; +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; @@ -13,6 +13,7 @@ import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.gui.Notification; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.ToolSettings; import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.CommonUtils; import org.openstreetmap.josm.tools.Logging; import org.openstreetmap.josm.tools.Shortcut; @@ -31,7 +32,7 @@ public class SimplifySelectAction extends JosmAction implements DataSelectionLis public SimplifySelectAction() { - super(tr("Simplify way"), "mapmode/magic-wand-merge", tr("Simplify multiple geometries"), Shortcut.registerShortcut("data:magicwandsimplify", tr("Data: {0}", tr("Simplify multiple geometries")), KeyEvent.VK_4, Shortcut.CTRL), true); + super(tr("Simplify way"), "mapmode/magic-wand-simplify", tr("Simplify multiple geometries"), Shortcut.registerShortcut("data:magicwandsimplify", tr("Data: {0}", tr("Simplify multiple geometries")), KeyEvent.VK_4, Shortcut.CTRL), true); } @Override @@ -73,7 +74,7 @@ private List simplifyWays(Collection ways) throws Exception { List geometries = new ArrayList<>(); for (Way w : ways) { List coordsMercator = CommonUtils.nodes2Coordinates(w.getNodes()); - Geometry geometryMercator = CommonUtils.coordinates2Geometry(coordsMercator, true); + Geometry geometryMercator = CommonUtils.coordinates2Polygon(coordsMercator); Geometry geometrySimplify = CommonUtils.simplifySmoothGeometry(geometryMercator); geometries.add(geometrySimplify); } diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MagicWandDialog.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MagicWandDialog.java deleted file mode 100644 index e77cc8b..0000000 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MagicWandDialog.java +++ /dev/null @@ -1,199 +0,0 @@ -package org.openstreetmap.josm.plugins.devseed.JosmMagicWand; - -import org.openstreetmap.josm.gui.SideButton; -import org.openstreetmap.josm.gui.dialogs.ToggleDialog; - -import javax.swing.*; -import java.awt.*; -import java.util.List; - -import static org.openstreetmap.josm.tools.I18n.tr; - -public class MagicWandDialog extends ToggleDialog { - // variables - - public MagicWandDialog() { - super(tr("Magic Wand"), "magicwand.svg", tr("Open MagicWand windows"), null, 90); - - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - // tolerance - panel.add(buildTolerancePanel()); - // simplify - panel.add(buildPolygonHullPanel()); - panel.add(buildDouglaspPanel()); - panel.add(buildTopologyPreservingPanel()); - panel.add(buildChaikinAnglePanel()); - panel.add(buildAutoAddTag()); - - createLayout(panel, true, List.of(new SideButton[]{})); - } - - private JPanel buildTolerancePanel() { - JPanel jpanel = new JPanel(); - jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); - // - int initValue = 9; - JLabel toleranceJLabel = new JLabel(); - toleranceJLabel.setText("Tolerance: " + initValue); - ToolSettings.setTolerance(initValue); - jpanel.add(toleranceJLabel); - // - JSlider jSlider = new JSlider(1, 30, initValue); - jSlider.setPaintTrack(true); - jSlider.setPaintTicks(true); - jSlider.setPaintLabels(true); - jSlider.setMajorTickSpacing(5); - jSlider.setMinorTickSpacing(0); - - jpanel.add(jSlider); - jSlider.addChangeListener(changeEvent -> { - JSlider source = (JSlider) changeEvent.getSource(); - int value = source.getValue(); - toleranceJLabel.setText(tr("Tolerance: " + value)); - ToolSettings.setTolerance(value); - }); - jpanel.add(new JSeparator()); - - return jpanel; - } - - private JPanel buildPolygonHullPanel() { - JPanel jpanel = new JPanel(); - jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); - // - JLabel simplPolygonHullJLabel = new JLabel(); - double decimalPlaces = Math.pow(10, 3); - double min = 0.5; - int initValue = (int) (0.95 * decimalPlaces); - // - simplPolygonHullJLabel.setText("Exterior contour: " + initValue / decimalPlaces); - ToolSettings.setSimplPolygonHull(initValue / decimalPlaces); - jpanel.add(simplPolygonHullJLabel); - // - JSlider jSlider = new JSlider((int) (min * decimalPlaces), (int) decimalPlaces, initValue); - jSlider.setPaintTrack(true); - jSlider.setPaintTicks(true); - jSlider.setPaintLabels(true); - - jpanel.add(jSlider); - jSlider.addChangeListener(changeEvent -> { - JSlider source = (JSlider) changeEvent.getSource(); - double value = source.getValue(); - if (value <= (min * decimalPlaces)) { - value = 0.0; - } else { - value /= decimalPlaces; - } - - simplPolygonHullJLabel.setText(tr("Exterior contour: " + value)); - ToolSettings.setSimplPolygonHull(value); - }); - jpanel.add(new JSeparator()); - - return jpanel; - } - - private JPanel buildDouglaspPanel() { - JPanel jpanel = new JPanel(); - jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); - // - JLabel simplDouglaspJLabel = new JLabel(); - double decimalPlaces = Math.pow(10, 3); - int initValue = 1000; - // - simplDouglaspJLabel.setText("Vertices: " + initValue / decimalPlaces); - ToolSettings.setSimplifyDouglasP(initValue / decimalPlaces); - jpanel.add(simplDouglaspJLabel); - // - JSlider jSlider = new JSlider(0, (int) (5 * decimalPlaces), initValue); - jSlider.setPaintTrack(true); - jSlider.setPaintTicks(true); - jSlider.setPaintLabels(true); - - jpanel.add(jSlider); - jSlider.addChangeListener(changeEvent -> { - JSlider source = (JSlider) changeEvent.getSource(); - double value = source.getValue() / decimalPlaces; - simplDouglaspJLabel.setText(tr("Vertices: " + value)); - ToolSettings.setSimplifyDouglasP(value); - }); - jpanel.add(new JSeparator()); - - return jpanel; - } - - private JPanel buildTopologyPreservingPanel() { - JPanel jpanel = new JPanel(); - jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); - // - JLabel simplTopologyPreservingJLabel = new JLabel(); - double decimalPlaces = Math.pow(10, 3); - int initValue = 1000; - // - simplTopologyPreservingJLabel.setText("Topology: " + initValue / decimalPlaces); - ToolSettings.setSimplTopologyPreserving(initValue / decimalPlaces); - jpanel.add(simplTopologyPreservingJLabel); - // - JSlider jSlider = new JSlider(0, (int) (5 * decimalPlaces), initValue); - jSlider.setPaintTrack(true); - jSlider.setPaintTicks(true); - jSlider.setPaintLabels(true); - - jpanel.add(jSlider); - jSlider.addChangeListener(changeEvent -> { - JSlider source = (JSlider) changeEvent.getSource(); - double value = source.getValue() / decimalPlaces; - simplTopologyPreservingJLabel.setText(tr("Topology: " + value)); - ToolSettings.setSimplTopologyPreserving(value); - }); - jpanel.add(new JSeparator()); - - return jpanel; - } - - private JPanel buildChaikinAnglePanel() { - JPanel jpanel = new JPanel(); - jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); - // - int initValue = 110; - double minValue = 20.0; - JLabel chaikinSmootherAngleJLabel = new JLabel(); - chaikinSmootherAngleJLabel.setText("Smooth Angle: " + initValue); - ToolSettings.setChaikinSmooAngle(initValue); - jpanel.add(chaikinSmootherAngleJLabel); - // - JSlider jSlider = new JSlider((int) minValue, 170, initValue); - jSlider.setPaintTrack(true); - jSlider.setPaintTicks(true); - jSlider.setPaintLabels(true); - - jpanel.add(jSlider); - jSlider.addChangeListener(changeEvent -> { - JSlider source = (JSlider) changeEvent.getSource(); - double value = source.getValue(); - if (value <= minValue) { - value = 0.0; - } - chaikinSmootherAngleJLabel.setText(tr("Smooth Angle: " + value)); - ToolSettings.setChaikinSmooAngle(value); - }); - jpanel.add(new JSeparator()); - - return jpanel; - } - - private JPanel buildAutoAddTag() { - JPanel jpanel = new JPanel(); - jpanel.setLayout(new FlowLayout()); - JButton button = new JButton("add Tag"); - button.addActionListener(e -> { - TagsDialog tagsDialog = new TagsDialog(); - if (tagsDialog.getValue() != 1) return; - tagsDialog.saveSettings(); - }); - jpanel.add(button); - return jpanel; - } - -} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MainJosmMagicWandPlugin.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MainJosmMagicWandPlugin.java index 4515d8d..c331dda 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MainJosmMagicWandPlugin.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/MainJosmMagicWandPlugin.java @@ -7,6 +7,12 @@ import org.openstreetmap.josm.gui.MapFrame; import org.openstreetmap.josm.plugins.Plugin; import org.openstreetmap.josm.plugins.PluginInformation; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions.MagicWandAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions.MergeSelectAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions.SamDecodeAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Actions.SimplifySelectAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui.MagicWandDialog; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.CommonUtils; import org.openstreetmap.josm.tools.Logging; import javax.swing.*; @@ -30,15 +36,17 @@ public MainJosmMagicWandPlugin(PluginInformation info) { jToolmenu.addSeparator(); MainMenu.add(jToolmenu, new MergeSelectAction()); MainMenu.add(jToolmenu, new SimplifySelectAction()); - + // create a folder + CommonUtils.createCacheDir(); } @Override public void mapFrameInitialized(MapFrame oldFrame, MapFrame newFrame) { if (oldFrame == null && newFrame != null) { - newFrame.addToggleDialog( new MagicWandDialog()); + MagicWandDialog magicWandDialog = new MagicWandDialog(); + newFrame.addToggleDialog(magicWandDialog); MainApplication.getMap().addMapMode(new IconToggleButton(new MagicWandAction())); - + MainApplication.getMap().addMapMode(new IconToggleButton(new SamDecodeAction(magicWandDialog))); } } } diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/ToolSettings.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/ToolSettings.java index b36e2e5..d216684 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/ToolSettings.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/ToolSettings.java @@ -1,5 +1,10 @@ package org.openstreetmap.josm.plugins.devseed.JosmMagicWand; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.SamImage; + +import java.util.ArrayList; +import java.util.List; + public final class ToolSettings { private ToolSettings() { } @@ -16,8 +21,25 @@ private ToolSettings() { //smooth private static double chaikinSmooDistance; private static double chaikinSmooAngle; -// tags + // tags private static String autoTags; + // sam images + private static List samImagesList = new ArrayList<>(); + + public static List getSamImagesList() { + return samImagesList; + } + public static void setSamImagesList(List samImagesList) { + ToolSettings.samImagesList = samImagesList; + } + + public static void setSamImage(SamImage samImage) { + ToolSettings.samImagesList.add(samImage); + } + public static void clearSamImagesList() { + ToolSettings.samImagesList.clear(); + } + public static int getMaskOpen() { return maskOpen; } @@ -67,11 +89,11 @@ public static void setSimplTopologyPreserving(double simplTopologyPreserving) { ToolSettings.simplTopologyPreserving = simplTopologyPreserving; } - public static double getSimplifyDouglasP() { + public static double getSimplDouglasP() { return simplDouglasP; } - public static void setSimplifyDouglasP(double simplDouglasP) { + public static void setSimplDouglasP(double simplDouglasP) { ToolSettings.simplDouglasP = simplDouglasP; } diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/ButtonActions/AutoAddTagAction.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/ButtonActions/AutoAddTagAction.java new file mode 100644 index 0000000..2e755c6 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/ButtonActions/AutoAddTagAction.java @@ -0,0 +1,32 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui.ButtonActions; + +import org.openstreetmap.josm.actions.JosmAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui.TagsDialog; + +import java.awt.event.ActionEvent; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.openstreetmap.josm.tools.I18n.tr; + +public class AutoAddTagAction extends JosmAction { + AtomicBoolean isPerforming = new AtomicBoolean(false); + + public AutoAddTagAction() { + super(tr("Add tag"), "dialogs/add", tr("Add a new key/value tag to geometries"), + null, false); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (!/*successful*/isPerforming.compareAndSet(false, true)) { + return; + } + try { + TagsDialog tagsDialog = new TagsDialog(); + if (tagsDialog.getValue() != 1) return; + tagsDialog.saveSettings(); + } finally { + isPerforming.set(false); + } + } +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/ButtonActions/SamEncondeAction.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/ButtonActions/SamEncondeAction.java new file mode 100644 index 0000000..2e31aa2 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/ButtonActions/SamEncondeAction.java @@ -0,0 +1,99 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui.ButtonActions; + +import org.openstreetmap.josm.actions.JosmAction; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.MapView; +import org.openstreetmap.josm.gui.Notification; +import org.openstreetmap.josm.gui.layer.ImageryLayer; +import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.ImageSamPanelListener; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.LayerImageValues; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.SamImage; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.image.BufferedImage; +import java.util.List; + +import static org.openstreetmap.josm.tools.I18n.tr; + +public class SamEncondeAction extends JosmAction { + private ImageSamPanelListener listener; + + public SamEncondeAction(ImageSamPanelListener listener) { + super(tr("SAM AOI"), "dialogs/magic-wand-encode", tr("Add a new SAM AOI"), + null, false); + this.listener = listener; + } + + @Override + public void actionPerformed(ActionEvent e) { + MapView mapView = MainApplication.getMap().mapView; + // check layers + List targetLayer = MainApplication.getLayerManager().getLayers(); + boolean hasMapLayer = false; + for (Layer layer : targetLayer) { + if (layer instanceof ImageryLayer && layer.isVisible()) { + hasMapLayer = true; + break; + } + } + + if (hasMapLayer) { + LayerImageValues layerImageValues = getLayeredImage(mapView); + + SamImage samImage = new SamImage(mapView.getProjectionBounds(), layerImageValues.getBufferedImage(), layerImageValues.getLayerName()); + + // effect + setEnabled(false); + apiThread(samImage); + + } else { + new Notification(tr("An active layer is needed.")).setIcon(JOptionPane.ERROR_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + } + } + + private void apiThread(SamImage samImage) { + Thread apiThread = new Thread(() -> { + samImage.setEncodeImage(); + SwingUtilities.invokeLater(() -> { + addSamImage(samImage); + samImage.updateCacheImage(); + setEnabled(true); + }); + }); + apiThread.start(); + } + + private void addSamImage(SamImage samImage) { + if (samImage.isEncodeImage()) { + listener.addLayer(); + listener.addBboxLayer(samImage.getBboxWay()); + listener.onAddSamImage(samImage); + new Notification(tr("Added a sam image.")).setIcon(JOptionPane.INFORMATION_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + } else { + new Notification(tr("Error adding sam image.")).setIcon(JOptionPane.ERROR_MESSAGE).setDuration(Notification.TIME_SHORT).show(); + } + } + + private LayerImageValues getLayeredImage(MapView mapView) { + LayerImageValues layerImageValues = new LayerImageValues(); + BufferedImage bufImage = new BufferedImage(mapView.getWidth(), mapView.getHeight(), BufferedImage.TYPE_3BYTE_BGR); + Graphics2D imgGraphics = bufImage.createGraphics(); + imgGraphics.setClip(0, 0, mapView.getWidth(), mapView.getHeight()); + + for (Layer layer : mapView.getLayerManager().getVisibleLayersInZOrder()) { + if (layer.isVisible() && layer.isBackgroundLayer()) { + Composite translucent = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) layer.getOpacity()); + imgGraphics.setComposite(translucent); + mapView.paintLayer(layer, imgGraphics); + layerImageValues.setBufferedImage(bufImage); + layerImageValues.setLayerName(layer.getName()); + } + + } + + return layerImageValues; + } +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/MagicWandDialog.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/MagicWandDialog.java new file mode 100644 index 0000000..7a428c5 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/MagicWandDialog.java @@ -0,0 +1,297 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui; + +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.locationtech.jts.geom.Point; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.SideButton; +import org.openstreetmap.josm.gui.dialogs.ToggleDialog; +import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.ToolSettings; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui.ButtonActions.AutoAddTagAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui.ButtonActions.SamEncondeAction; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.ImageSamPanelListener; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.SamImage; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils.SamImageGrid; +import org.openstreetmap.josm.tools.Logging; + +import javax.swing.*; +import javax.swing.border.TitledBorder; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.openstreetmap.josm.tools.I18n.tr; + +public class MagicWandDialog extends ToggleDialog implements ImageSamPanelListener { + // variables + private SamImageGrid samImageGrid; + private JPanel mainJpanel; + private OsmDataLayer uneditableLayer = null; + private boolean canSamAoi = false; + + public MagicWandDialog() { + super(tr("Magic Wand"), "magicwand-info.svg", tr("Open MagicWand windows"), null, 200, false); + + mainJpanel = new JPanel(); + mainJpanel.setLayout(new BoxLayout(mainJpanel, BoxLayout.Y_AXIS)); + + JPanel optionPanel = new JPanel(); + optionPanel.setLayout(new GridLayout(5, 1, 3, 3)); + // tolerance + optionPanel.add(buildTolerancePanel()); + // simplify + optionPanel.add(buildPolygonHullPanel()); + optionPanel.add(buildDouglaspPanel()); + optionPanel.add(buildTopologyPreservingPanel()); + optionPanel.add(buildChaikinAnglePanel()); + // add mainJpanel + mainJpanel.add(optionPanel); + // sam image + + mainJpanel.add(buildSamImagesPanel()); + // layer + initLayer(); + // buttons + // add + SideButton addTagButton = new SideButton(new AutoAddTagAction()); + // sam + SideButton samButton = new SideButton(new SamEncondeAction(this)); + + createLayout(mainJpanel, true, Arrays.asList(addTagButton, samButton)); + } + + private JPanel buildTolerancePanel() { + JPanel jpanel = new JPanel(); + jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); + jpanel.setPreferredSize(new Dimension(0,25)); + int initValue = 9; + TitledBorder titledBorder = BorderFactory.createTitledBorder("Tolerance: " + initValue); + jpanel.setBorder(titledBorder); + ToolSettings.setTolerance(initValue); + // + JSlider jSlider = new JSlider(1, 30, initValue); + jSlider.setPaintTrack(true); + jSlider.setPaintTicks(true); + jSlider.setPaintLabels(true); + + jpanel.add(jSlider); + jSlider.addChangeListener(changeEvent -> { + JSlider source = (JSlider) changeEvent.getSource(); + int value = source.getValue(); + titledBorder.setTitle(tr("Tolerance: " + value)); + ToolSettings.setTolerance(value); + jpanel.repaint(); + }); + + return jpanel; + } + + private JPanel buildPolygonHullPanel() { + JPanel jpanel = new JPanel(); + jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); + jpanel.setPreferredSize(new Dimension(0,25)); + // + double decimalPlaces = Math.pow(10, 3); + double min = 0.5; + int initValue = (int) (0.95 * decimalPlaces); + // + TitledBorder titledBorder = BorderFactory.createTitledBorder("Exterior contour: " + initValue / decimalPlaces); + jpanel.setBorder(titledBorder); + + ToolSettings.setSimplPolygonHull(initValue / decimalPlaces); + // + JSlider jSlider = new JSlider((int) (min * decimalPlaces), (int) decimalPlaces, initValue); + jSlider.setPaintTrack(true); + jSlider.setPaintTicks(true); + jSlider.setPaintLabels(true); + + jpanel.add(jSlider); + jSlider.addChangeListener(changeEvent -> { + JSlider source = (JSlider) changeEvent.getSource(); + double value = source.getValue(); + if (value <= (min * decimalPlaces)) { + value = 0.0; + } else { + value /= decimalPlaces; + } + + titledBorder.setTitle(tr("Exterior contour: " + value)); + ToolSettings.setSimplPolygonHull(value); + jpanel.repaint(); + }); + + + return jpanel; + } + + private JPanel buildDouglaspPanel() { + JPanel jpanel = new JPanel(); + jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); + // + double decimalPlaces = Math.pow(10, 3); + int initValue = 1000; + // + TitledBorder titledBorder = BorderFactory.createTitledBorder("Vertices: " + initValue / decimalPlaces); + jpanel.setBorder(titledBorder); + ToolSettings.setSimplDouglasP(initValue / decimalPlaces); + // + JSlider jSlider = new JSlider(0, (int) (5 * decimalPlaces), initValue); + jSlider.setPaintTrack(true); + jSlider.setPaintTicks(true); + jSlider.setPaintLabels(true); + + jpanel.add(jSlider); + jSlider.addChangeListener(changeEvent -> { + JSlider source = (JSlider) changeEvent.getSource(); + double value = source.getValue() / decimalPlaces; + titledBorder.setTitle(tr("Vertices: " + value)); + ToolSettings.setSimplDouglasP(value); + jpanel.repaint(); + }); + + + return jpanel; + } + + private JPanel buildTopologyPreservingPanel() { + JPanel jpanel = new JPanel(); + jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); + // + double decimalPlaces = Math.pow(10, 3); + int initValue = 1000; + // + TitledBorder titledBorder = BorderFactory.createTitledBorder("Topology: " + initValue / decimalPlaces); + jpanel.setBorder(titledBorder); + ToolSettings.setSimplTopologyPreserving(initValue / decimalPlaces); + // + JSlider jSlider = new JSlider(0, (int) (5 * decimalPlaces), initValue); + jSlider.setPaintTrack(true); + jSlider.setPaintTicks(true); + jSlider.setPaintLabels(true); + + jpanel.add(jSlider); + jSlider.addChangeListener(changeEvent -> { + JSlider source = (JSlider) changeEvent.getSource(); + double value = source.getValue() / decimalPlaces; + titledBorder.setTitle(tr("Topology: " + value)); + ToolSettings.setSimplTopologyPreserving(value); + jpanel.repaint(); + }); + + + return jpanel; + } + + private JPanel buildChaikinAnglePanel() { + JPanel jpanel = new JPanel(); + jpanel.setLayout(new BoxLayout(jpanel, BoxLayout.Y_AXIS)); + // + int initValue = 110; + double minValue = 20.0; + // + TitledBorder titledBorder = BorderFactory.createTitledBorder("Smooth Angle: " + initValue); + jpanel.setBorder(titledBorder); + ToolSettings.setChaikinSmooAngle(initValue); + // + JSlider jSlider = new JSlider((int) minValue, 170, initValue); + jSlider.setPaintTrack(true); + jSlider.setPaintTicks(true); + jSlider.setPaintLabels(true); + + jpanel.add(jSlider); + jSlider.addChangeListener(changeEvent -> { + JSlider source = (JSlider) changeEvent.getSource(); + double value = source.getValue(); + if (value <= minValue) { + value = 0.0; + } + titledBorder.setTitle(tr("Smooth Angle: " + value)); + ToolSettings.setChaikinSmooAngle(value); + jpanel.repaint(); + }); + + + return jpanel; + } + + private JPanel buildSamImagesPanel() { + samImageGrid = new SamImageGrid(130); + samImageGrid.setLayout(new GridLayout(0, 2, 2, 3)); + + return samImageGrid; + } + + private void initLayer() { + if (uneditableLayer == null || !MainApplication.getLayerManager().containsLayer(uneditableLayer)) { + DataSet bboxDataset = new DataSet(); + uneditableLayer = new OsmDataLayer(bboxDataset, "Magic Wand Uneditable Layer", null); + uneditableLayer.setUploadDiscouraged(false); + } + } + + @Override + public void onAddSamImage(SamImage samImage) { + samImageGrid.addSamImage(samImage); + } + + @Override + public void onRemoveAll() { + samImageGrid.removeAllSamImage(); + } + + @Override + public ArrayList getSamImageList() { + return samImageGrid.getSamImageList(); + } + + @Override + public SamImage getSamImageIncludepoint(double x, double y) { + GeometryFactory geometryFactory = new GeometryFactory(); + Coordinate coordinate = new Coordinate(x, y); + Point point = geometryFactory.createPoint(coordinate); + + return samImageGrid.getSamImageIncludepoint(point); + } + + @Override + public void addLayer() { + try { + initLayer(); + + Layer activeLayer = MainApplication.getLayerManager().getActiveDataLayer(); + if (activeLayer == null) { + activeLayer = new OsmDataLayer(new DataSet(), "Data Layer ", null); + MainApplication.getLayerManager().addLayer(activeLayer); + } + int indexActiveLayer = MainApplication.getLayerManager().getLayers().indexOf(activeLayer); + + if (!MainApplication.getLayerManager().containsLayer(uneditableLayer)) { + MainApplication.getLayerManager().addLayer(uneditableLayer); + MainApplication.getLayerManager().setActiveLayer(activeLayer); + } + int indexUneditableLayer = MainApplication.getLayerManager().getLayers().indexOf(uneditableLayer); + + if (indexActiveLayer >= indexUneditableLayer) { + MainApplication.getLayerManager().moveLayer(uneditableLayer, indexActiveLayer); + } + } catch (Exception e) { + Logging.error(e); + } + } + + @Override + public void addBboxLayer(Way way) { + for (Node node : way.getNodes()) { + if (!uneditableLayer.getDataSet().containsNode(node)) { + uneditableLayer.getDataSet().addPrimitive(node); + } + } + uneditableLayer.getDataSet().addPrimitive(way); + } + +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/TagsDialog.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/TagsDialog.java similarity index 93% rename from src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/TagsDialog.java rename to src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/TagsDialog.java index 22c0dc4..50aeab0 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/TagsDialog.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/Ui/TagsDialog.java @@ -1,9 +1,10 @@ -package org.openstreetmap.josm.plugins.devseed.JosmMagicWand; +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.Ui; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.gui.Notification; import org.openstreetmap.josm.gui.widgets.HistoryComboBox; +import org.openstreetmap.josm.plugins.devseed.JosmMagicWand.ToolSettings; import org.openstreetmap.josm.tools.GBC; import javax.swing.*; diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CommonUtils.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CommonUtils.java index 3997cc6..810b79c 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CommonUtils.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CommonUtils.java @@ -1,15 +1,13 @@ package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; import org.locationtech.jts.algorithm.Angle; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.PrecisionModel; +import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.util.GeometryFixer; import org.locationtech.jts.precision.GeometryPrecisionReducer; import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import org.locationtech.jts.simplify.PolygonHullSimplifier; import org.locationtech.jts.simplify.TopologyPreservingSimplifier; +import org.opencv.core.Point; import org.opencv.core.*; import org.opencv.imgproc.Imgproc; import org.opencv.photo.Photo; @@ -21,6 +19,7 @@ import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.TagMap; import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.data.preferences.JosmBaseDirectories; import org.openstreetmap.josm.data.projection.Projection; import org.openstreetmap.josm.data.projection.ProjectionRegistry; import org.openstreetmap.josm.gui.MapView; @@ -31,13 +30,15 @@ import java.awt.image.BufferedImage; import java.awt.image.ColorConvertOp; import java.awt.image.DataBufferByte; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; import java.util.stream.Collectors; +import static org.openstreetmap.josm.tools.I18n.tr; public class CommonUtils { // IMAGE public static Mat bufferedImage2Mat(BufferedImage bi) { @@ -56,14 +57,18 @@ public static BufferedImage mat2BufferedImageGray(Mat mat) { return image; } - public static void bufferedImageSaveFile(BufferedImage image, String filePath) throws Exception { - File oupFile = new File(filePath); - int index = filePath.lastIndexOf('.'); - String extension = "jpg"; - if (index > 0) { - extension = filePath.substring(index + 1); + public static void bufferedImageSaveFile(BufferedImage image, String filePath) { + try { + File oupFile = new File(filePath); + int index = filePath.lastIndexOf('.'); + String extension = "jpg"; + if (index > 0) { + extension = filePath.substring(index + 1); + } + ImageIO.write(image, extension, oupFile); + } catch (Exception e) { + Logging.error("Exception " + e + " save image"); } - ImageIO.write(image, extension, oupFile); } public static BufferedImage convertColorspace(BufferedImage image, int newType) { @@ -221,19 +226,28 @@ public static Geometry reduceGeometry(Geometry g, int scale) { } // GEOMETRY UTILS - public static Geometry coordinates2Geometry(List coordinates, boolean close) throws Exception { + public static Polygon coordinates2Polygon(List coordinates) throws Exception { + if (coordinates == null || coordinates.size() < 3) { + throw new Exception("The coordinate list must have at least 3 points to form a valid polygon."); + } GeometryFactory gf = new GeometryFactory(); - if (close) { - if (!coordinates.get(coordinates.size() - 1).equals(coordinates.get(0))) - coordinates.add(coordinates.get(0)); - var tmpPolygon = gf.createPolygon(coordinates.toArray(new Coordinate[]{})); - if (!tmpPolygon.isValid()) { - return GeometryFixer.fix(tmpPolygon); + if (!coordinates.get(coordinates.size() - 1).equals(coordinates.get(0))) { + coordinates.add(coordinates.get(0)); + } + + Polygon tmpPolygon = gf.createPolygon(coordinates.toArray(new Coordinate[]{})); + if (!tmpPolygon.isValid()) { + Geometry fixedGeometry = GeometryFixer.fix(tmpPolygon); + if (fixedGeometry instanceof Polygon) { + return gf.createPolygon(((Polygon) fixedGeometry).getExteriorRing().getCoordinates()); + } else if (fixedGeometry instanceof MultiPolygon) { + return gf.createPolygon(((Polygon) fixedGeometry.getGeometryN(0)).getExteriorRing().getCoordinates()); + } else { + throw new Exception("La corrección de la geometría no produjo un polígono válido."); } - return tmpPolygon; } - return gf.createLineString(coordinates.toArray(new Coordinate[]{})); + return tmpPolygon; } public static List nodes2Coordinates(List nodes) { @@ -255,9 +269,22 @@ public static List coordinates2Nodes(List coordinates, Project public static Collection geometry2WayCommands(DataSet ds, List geometries, String tagMapKey, String tagMapValue) throws Exception { Collection cmds = new LinkedList<>(); Projection projection = ProjectionRegistry.getProjection(); + List geometriesSplit = new ArrayList<>(); for (Geometry geometry : geometries) { + if (geometry.getNumGeometries() > 1) { + for (int j = 0; j < geometry.getNumGeometries(); j++) { + geometriesSplit.add(geometry.union()); + System.out.println("geom interno"); + } + } else { + geometriesSplit.add(geometry.union()); + System.out.println("geom externo"); + } + } + + for (Geometry geometry : geometriesSplit) { Way w = new Way(); - List nodes = coordinates2Nodes(List.of(geometry.getCoordinates()), projection); + List nodes = coordinates2Nodes(List.of(geometry.union().getCoordinates()), projection); int index = 0; for (Node n : nodes) { if (index == (nodes.size() - 1)) { @@ -314,7 +341,7 @@ public static Geometry chaikinAlgotihm(Geometry geometry, double maxAngle) { } Logging.info("-- chaikinAlgotihm -- Ang: " + maxAngle + " Orig: " + coordinates.length + " new : " + tmpCoords.size()); - return simplifyPolygonHull(coordinates2Geometry(tmpCoords, true), 0.999); + return simplifyPolygonHull(coordinates2Polygon(tmpCoords), 0.999); } catch (Exception e) { Logging.error(e); return geometry; @@ -339,13 +366,14 @@ public static Mat processImage(Mat mat_image, Mat mat_mask, boolean ctrl_, boole mat_flood.create(new Size(mat_image.cols() + 2, mat_image.rows() + 2), CvType.CV_8UC1); mat_flood.setTo(new Scalar(0)); floodFillFacade.setTolerance(ToolSettings.getTolerance()); - // + // improve colors Mat mat_blur = blur(mat_image, 7); floodFillFacade.fill(mat_blur, mat_flood, x, y); + Mat mat_open = open(mat_flood, 5); - Mat mat_close = close(mat_open, 9); - Mat mat_erode = erode(mat_close, 3); - Mat mat_dilate = dilate(mat_erode, 5); + Mat mat_close = close(mat_open, 5); +// Mat mat_erode = erode(mat_close, 3); + Mat mat_dilate = dilate(mat_close, 1); if (mat_mask != null) { @@ -364,7 +392,7 @@ public static Mat processImage(Mat mat_image, Mat mat_mask, boolean ctrl_, boole mat_tmp.create(new Size(mat_image.cols() + 2, mat_image.rows() + 2), CvType.CV_8UC1); mat_tmp.setTo(new Scalar(0)); Core.subtract(mat_mask.clone(), mat_dilate.clone(), mat_tmp); - Mat mat_tmp_open = open(mat_tmp, 5); + Mat mat_tmp_open = open(mat_tmp, 9); return mat_tmp_open.clone(); } } @@ -415,7 +443,7 @@ public static List contourn2Geometry(List contours, MapVie tmpCoords.add(c); } // create multi - Geometry geometryTmp = coordinates2Geometry(tmpCoords, true); + Geometry geometryTmp = coordinates2Polygon(tmpCoords); geometries.add(geometryTmp.copy()); } catch (Exception ex) { Logging.error(ex); @@ -426,8 +454,8 @@ public static List contourn2Geometry(List contours, MapVie } public static Geometry simplifySmoothGeometry(Geometry geometry) { - if (ToolSettings.getSimplifyDouglasP() > 0) { - geometry = CommonUtils.simplifyDouglasP(geometry.copy(), ToolSettings.getSimplifyDouglasP()); + if (ToolSettings.getSimplDouglasP() > 0) { + geometry = CommonUtils.simplifyDouglasP(geometry.copy(), ToolSettings.getSimplDouglasP()); } if (ToolSettings.getSimplPolygonHull() > 0) { geometry = CommonUtils.simplifyPolygonHull(geometry.copy(), ToolSettings.getSimplPolygonHull()); @@ -440,4 +468,91 @@ public static Geometry simplifySmoothGeometry(Geometry geometry) { } return geometry.copy(); } + + public static String encodeImageToBase64(BufferedImage image) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(image, "jpg", outputStream); + byte[] imageBytes = outputStream.toByteArray(); + return Base64.getEncoder().encodeToString(imageBytes); + } catch (Exception e) { + Logging.error(e); + return ""; + } + + } + + public static BufferedImage decodeBase64ToImage(String base64Image) { + try { + byte[] imageBytes = Base64.getDecoder().decode(base64Image); + ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes); + return ImageIO.read(inputStream); + } catch (Exception e) { + Logging.error(e); + return null; + } + } + + public static String magicWandCacheDirPath() { + String josmCacheDataDir = JosmBaseDirectories.getInstance().getCacheDirectory(true).toString(); + + return josmCacheDataDir + File.separator + "magicwand"; + } + + public static boolean existCacheDir() { + return new File(magicWandCacheDirPath()).exists(); + } + + public static void createCacheDir() { + String magicWandDirPath = magicWandCacheDirPath(); + File magicWandDir = new File(magicWandDirPath); + if (!magicWandDir.exists()) { + boolean wasDirectoryCreated = magicWandDir.mkdir(); + + if (wasDirectoryCreated) { + Logging.info(tr("magicwand " + "Folder 'magicwand' successfully created at: " + magicWandDirPath)); + } else { + Logging.info(tr("magicwand " + "Failed to create the 'magicwand' folder at: " + magicWandDirPath)); + } + } else { + Logging.info(tr("magicwand " + "The 'magicwand' folder already exists at: " + magicWandDirPath)); + } + + } + + public static String getDateTime() { + LocalDateTime now = LocalDateTime.now(); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy_MM_dd__HH_mm_ss"); + return now.format(formatter); + } + + public static String getMapLayerName(String layerName) { + + if (layerName.length() >= 50) { + String normalized = layerName.trim().substring(0, 50).replaceAll("[^a-zA-Z0-9\\s-]", "").replaceAll("\\s+", " "); + return normalized.replaceAll("\\s", "-").toLowerCase(); + } else if (!layerName.isEmpty()) { + String normalized = layerName.trim().replaceAll("[^a-zA-Z0-9\\s-]", "").replaceAll("\\s+", " "); + return normalized.replaceAll("\\s", "-").toLowerCase(); + } else { + return "no_layer_name"; + } + } + + public static List extractPolygons(Geometry geometry) { + List polygons = new ArrayList<>(); + + if (geometry instanceof MultiPolygon) { + for (int i = 0; i < geometry.getNumGeometries(); i++) { + Geometry subGeometry = geometry.getGeometryN(i); + polygons.addAll(extractPolygons(subGeometry)); + } + } else if (geometry instanceof Polygon) { + if (geometry.getNumPoints() > 5 && geometry.isValid()) { + polygons.add((Polygon) geometry); + } + } + + return polygons; + } } diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/Constants.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/Constants.java new file mode 100644 index 0000000..3773f3f --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/Constants.java @@ -0,0 +1,6 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +public class Constants { + public static final String ENCODE_URL = ""; + public static final String DECODE_URL = ""; +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CustomPolygon.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CustomPolygon.java index e086fc3..540c8a9 100644 --- a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CustomPolygon.java +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/CustomPolygon.java @@ -24,7 +24,7 @@ public CustomPolygon() { public void fromWay(Way w) { this.way = w; try { - this.pol = (Polygon) CommonUtils.coordinates2Geometry(CommonUtils.nodes2Coordinates(w.getNodes()), true); + this.pol = CommonUtils.coordinates2Polygon(CommonUtils.nodes2Coordinates(w.getNodes())); } catch (Exception ex) { Logging.error(ex); } diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/DecondeRequestBody.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/DecondeRequestBody.java new file mode 100644 index 0000000..abbe5fc --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/DecondeRequestBody.java @@ -0,0 +1,80 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +import java.util.List; + +public class DecondeRequestBody { + private List bbox; + private String crs; + private String image_embeddings; + private List image_shape; + private int input_label; + private List input_point; + private double zoom; + + public DecondeRequestBody(List bbox, String image_embeddings, List image_shape, List input_point) { + this.bbox = bbox; + this.crs="EPSG:3857"; + this.image_embeddings = image_embeddings; + this.image_shape = image_shape; + this.input_label = 1; + this.input_point = input_point; + this.zoom = 15; + } + + + public List getBbox() { + return bbox; + } + + public void setBbox(List bbox) { + this.bbox = bbox; + } + + public String getCrs() { + return crs; + } + + public void setCrs(String crs) { + this.crs = crs; + } + + public String getImage_embeddings() { + return image_embeddings; + } + + public void setImage_embeddings(String image_embeddings) { + this.image_embeddings = image_embeddings; + } + + public List getImage_shape() { + return image_shape; + } + + public void setImage_shape(List image_shape) { + this.image_shape = image_shape; + } + + public int getInput_label() { + return input_label; + } + + public void setInput_label(int input_label) { + this.input_label = input_label; + } + + public List getInput_point() { + return input_point; + } + + public void setInput_point(List input_point) { + this.input_point = input_point; + } + + public double getZoom() { + return zoom; + } + + public void setZoom(double zoom) { + this.zoom = zoom; + } +} \ No newline at end of file diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/EncondeRequestBody.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/EncondeRequestBody.java new file mode 100644 index 0000000..8a9c9d4 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/EncondeRequestBody.java @@ -0,0 +1,19 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +public class EncondeRequestBody { + private String encoded_image; + + public EncondeRequestBody(String encodedImage) { + encoded_image = encodedImage; + } + + public String getEncoded_image() { + return encoded_image; + } + + public void setEncoded_image(String encoded_image) { + this.encoded_image = encoded_image; + } + + +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImagePanel.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImagePanel.java new file mode 100644 index 0000000..e6b2d2b --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImagePanel.java @@ -0,0 +1,141 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +import org.openstreetmap.josm.data.ProjectionBounds; +import org.openstreetmap.josm.data.imagery.ImageryInfo; +import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; +import org.openstreetmap.josm.gui.MapView; +import org.openstreetmap.josm.gui.layer.ImageryLayer; +import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.tools.ImageProvider; +import org.openstreetmap.josm.tools.Logging; + +import javax.swing.*; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.List; +import java.util.stream.Collectors; + +import static org.openstreetmap.josm.gui.MainApplication.getMap; + + +public class ImagePanel extends JPanel { + + private BufferedImage image; + private JButton zoomJButton; + private JButton deleteJButton; + private int maxHeight; + private int maxWidth; + + private SamImage samImage; + + private JLabel imageLabel; + private ImagePanelListener listener; + + public ImagePanel(SamImage samImage, int maxWidth, ImagePanelListener listener) { + setLayout(null); + this.samImage = samImage; + this.image = samImage.getLayerImage(); + this.maxWidth = maxWidth; + + this.maxHeight = getMaxHeight(samImage.getLayerImage(), maxWidth); + this.listener = listener; + setupButtons(); + // image + Graphics2D g2d = this.image.createGraphics(); + g2d.dispose(); + ImageIcon icono = new ImageIcon(getScaledImage(this.image, maxWidth, this.maxHeight)); + imageLabel = new JLabel(icono); + add(imageLabel); + imageLabel.setBounds(0, 0, this.maxWidth, this.maxHeight); + setPreferredSize(new Dimension(this.maxWidth, this.maxHeight)); + // setup actions + + } + + private Image getScaledImage(BufferedImage originalImage, int maxWidth, int maxHeight) { + int width = originalImage.getWidth(); + + if (width <= maxWidth) { + return originalImage; + } + + return originalImage.getScaledInstance(maxWidth, maxHeight, Image.SCALE_SMOOTH); + } + + private int getMaxHeight(BufferedImage originalImage, int maxWidth) { + int width = originalImage.getWidth(); + int height = originalImage.getHeight(); + + double radio = (double) width / height; + + if (width <= maxWidth) { + return height; + } + return (int) (maxWidth / radio); + + } + + private void setupButtons() { + // Creamos los botones + ImageIcon zoomIco = new ImageProvider("dialogs", "zoom-best-fit").get(); + ImageIcon deleteIco = new ImageProvider("dialogs", "delete").get(); + + zoomJButton = new JButton(zoomIco); + + zoomJButton.setBounds(20, this.maxHeight - 32, zoomIco.getIconWidth(), zoomIco.getIconHeight()); + zoomJButton.setContentAreaFilled(false); + zoomJButton.addActionListener(e -> zoomAction()); + add(zoomJButton); + + deleteJButton = new JButton(deleteIco); + deleteJButton.setBounds(70, this.maxHeight - 32, deleteIco.getIconWidth(), deleteIco.getIconHeight()); + deleteJButton.setContentAreaFilled(false); + deleteJButton.addActionListener(e -> deleteAction()); + + add(deleteJButton); + + } + + private void zoomAction() { + MapView mapView = getMap().mapView; + + try { + // add layer + List activeLayers = mapView.getLayerManager() + .getVisibleLayersInZOrder() + .stream() + .filter(layer -> (layer instanceof ImageryLayer && layer.isVisible() && layer.getName().equals(samImage.getLayerName()))) + .collect(Collectors.toList()); + + if (activeLayers.isEmpty()) { + List imagerySources = ImageryLayerInfo + .instance + .getLayers() + .stream() + .filter(layer -> (layer.getName().equals(samImage.getLayerName()))) + .collect(Collectors.toList()); + + if (!imagerySources.isEmpty()) { + ImageryInfo imageryInfo = imagerySources.get(0); + mapView.getLayerManager().addLayer(ImageryLayer.create(imageryInfo)); + } + } + } catch (Exception e) { + Logging.error(e); + } + + ProjectionBounds projectionBounds = samImage.getProjectionBounds(); + mapView.zoomTo(projectionBounds); + mapView.zoomIn(); + mapView.repaint(); + } + + private void deleteAction() { + try { + samImage.removeCacheImge(); + listener.removeSamImage(samImage); + } catch (Exception e) { + Logging.error(e); + } + } +} \ No newline at end of file diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImagePanelListener.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImagePanelListener.java new file mode 100644 index 0000000..a91f9d0 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImagePanelListener.java @@ -0,0 +1,6 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +public interface ImagePanelListener { + void removeSamImage(SamImage samImage); + +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImageSamPanelListener.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImageSamPanelListener.java new file mode 100644 index 0000000..9770be4 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/ImageSamPanelListener.java @@ -0,0 +1,21 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + + +import org.openstreetmap.josm.data.osm.Way; + +import java.util.ArrayList; + +public interface ImageSamPanelListener { + void onAddSamImage(SamImage samImage); + + void onRemoveAll(); + + ArrayList getSamImageList(); + + SamImage getSamImageIncludepoint(double x, double y); + + void addLayer(); + + void addBboxLayer(Way way); + +} \ No newline at end of file diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/LayerImageValues.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/LayerImageValues.java new file mode 100644 index 0000000..0134c70 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/LayerImageValues.java @@ -0,0 +1,28 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +import java.awt.image.BufferedImage; + +public class LayerImageValues { + BufferedImage bufferedImage; + String layerName; + + public BufferedImage getBufferedImage() { + return bufferedImage; + } + + public void setBufferedImage(BufferedImage bufferedImage) { + this.bufferedImage = bufferedImage; + } + + public String getLayerName() { + return layerName; + } + + public void setLayerName(String layerName) { + this.layerName = layerName; + } + + public LayerImageValues() { + } + +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/SamImage.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/SamImage.java new file mode 100644 index 0000000..d88a8f0 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/SamImage.java @@ -0,0 +1,381 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; +import com.fasterxml.jackson.databind.ObjectMapper; +import okhttp3.*; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.io.geojson.GeoJsonReader; +import org.openstreetmap.josm.data.ProjectionBounds; +import org.openstreetmap.josm.data.coor.EastNorth; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.tools.Logging; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.*; +import java.util.concurrent.TimeUnit; + +@JsonRootName(value = "SamImage") +public class SamImage { + + @JsonIgnore + private final ObjectMapper objectMapper = new ObjectMapper(); + // fields + + @JsonIgnore + private BufferedImage layerImage; + @JsonIgnore + private ProjectionBounds projectionBounds; + // encode + @JsonProperty("base64Image") + private String base64Image; + + @JsonIgnore + private EastNorth center; + // enconde fields + + @JsonProperty("isEncodeImage") + private boolean isEncodeImage = false; + @JsonProperty("imageShape") + private List imageShape; + @JsonProperty("crs") + private String crs = "EPSG:3857"; + @JsonProperty("bbox") + private List bbox; + @JsonProperty("imageEmbedding") + private String imageEmbedding; + @JsonIgnore + private Polygon bboxPolygon; + @JsonIgnore + private Way bboxWay; + @JsonProperty("nameObject") + private String nameObject; + @JsonProperty("layerName") + private String layerName; + + + public SamImage(ProjectionBounds projectionBounds, BufferedImage layerImage, String layerName) { + // image + this.setLayerImage(layerImage); + this.setBase64Image(CommonUtils.encodeImageToBase64(layerImage)); + // create imageShape + this.setImageShape(new ArrayList<>(Arrays.asList( + layerImage.getHeight(), layerImage.getWidth() + ))); + + // projectionBounds + this.setCenter(projectionBounds.getCenter()); + this.setProjectionBounds(projectionBounds); + // create bbox + this.setBbox(new ArrayList<>(Arrays.asList( + projectionBounds.getMin().getX(), + projectionBounds.getMin().getY(), + projectionBounds.getMax().getX(), + projectionBounds.getMax().getY()))); + this.setBboxPolygon(createPolygonFromDoubles(this.getBbox())); + this.setBboxWay(createBboxWay()); + this.setNameObject(CommonUtils.getDateTime() + "__" + CommonUtils.getMapLayerName(layerName) + ".json"); + this.setLayerName(layerName); + saveCache(this.nameObject); + } + + public SamImage( + @JsonProperty("nameObject") String nameObject, + @JsonProperty("base64Image") String base64Image, + @JsonProperty("isEncodeImage") boolean isEncodeImage, + @JsonProperty("imageShape") List imageShape, + @JsonProperty("crs") String crs, + @JsonProperty("bbox") List bbox, + @JsonProperty("imageEmbedding") String imageEmbedding, + @JsonProperty("layerName") String layerName + + ) { + this.setNameObject(nameObject); + this.setBase64Image(base64Image); + this.setLayerImage(CommonUtils.decodeBase64ToImage(base64Image)); + this.setEncodeImage(isEncodeImage); + this.setImageShape(imageShape); + this.setCrs(crs); + this.setBbox(bbox); + ProjectionBounds projectionBoundsTmp = new ProjectionBounds(bbox.get(0), bbox.get(1), bbox.get(2), bbox.get(3)); + this.setProjectionBounds(projectionBoundsTmp); + this.setBboxPolygon(createPolygonFromDoubles(this.getBbox())); + this.setBboxWay(createBboxWay()); + this.setImageEmbedding(imageEmbedding); + this.setLayerName(layerName); + + } + + private void saveCache(String fileName) { + if (!CommonUtils.existCacheDir()) { + CommonUtils.createCacheDir(); + } + String magicWandDirPath = CommonUtils.magicWandCacheDirPath(); + String filePath = magicWandDirPath + File.separator + fileName; + try { + File file = new File(filePath); + objectMapper.writeValue(file, this); + + Logging.info("Object save in : " + filePath); + } catch (Exception e) { + Logging.error(e); + e.printStackTrace(); + } + } + public void updateCacheImage(){ + saveCache(nameObject); + } + public void removeCacheImge(){ + try { + String magicWandDirPath = CommonUtils.magicWandCacheDirPath(); + String filePath = magicWandDirPath + File.separator + nameObject; + File file = new File(filePath); + file.delete(); + }catch (Exception e){ + Logging.error(e); + } + + } + public BufferedImage getLayerImage() { + return layerImage; + } + + public EastNorth getCenter() { + return center; + } + + public ProjectionBounds getProjectionBounds() { + return projectionBounds; + } + + public boolean isEncodeImage() { + return isEncodeImage; + } + + public void setEncodeImage() { + try { + // request body + EncondeRequestBody encodeRequestBody = new EncondeRequestBody(getBase64Image()); + String requestBodyJson = objectMapper.writeValueAsString(encodeRequestBody); + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + RequestBody requestBody = RequestBody.create(JSON, requestBodyJson); + + // client + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(5, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .build(); + + Request request = new Request.Builder() + .url(Constants.ENCODE_URL) + .post(requestBody) + .build(); + // response + Response response = client.newCall(request).execute(); + + String responseData = response.body().string(); + Map dataMap = objectMapper.readValue(responseData, Map.class); + // get fields + setImageEmbedding((String) dataMap.getOrDefault("image_embedding", "")); + setEncodeImage(true); + } catch (Exception e) { + Logging.error(e); + setEncodeImage(false); + } + + } + + public Polygon createPolygonFromDoubles(List coordinates) { + Coordinate[] vertices = { + new Coordinate(coordinates.get(0), coordinates.get(1)), + new Coordinate(coordinates.get(0), coordinates.get(3)), + new Coordinate(coordinates.get(2), coordinates.get(3)), + new Coordinate(coordinates.get(2), coordinates.get(1)), + new Coordinate(coordinates.get(0), coordinates.get(1)) + }; + + GeometryFactory geometryFactory = new GeometryFactory(); + return geometryFactory.createPolygon(vertices); + } + + public List fetchDecodePoint(double x, double y) { + List geometryList = new ArrayList<>(); + GeoJsonReader reader = new GeoJsonReader(); + + try { + + // request body + Coordinate clickCoordinate = mouseClick2Coordinate(x, y); + + DecondeRequestBody decodeRequestBody = new DecondeRequestBody(getBbox(), getImageEmbedding(), getImageShape(), Arrays.asList((int) clickCoordinate.x, (int) clickCoordinate.y)); + String requestBodyJson = objectMapper.writeValueAsString(decodeRequestBody); + MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + RequestBody requestBody = RequestBody.create(JSON, requestBodyJson); + // client + + OkHttpClient client = new OkHttpClient.Builder() + .connectTimeout(3, TimeUnit.SECONDS) + .readTimeout(3, TimeUnit.SECONDS) + .build(); + + + Request request = new Request.Builder() + .url(Constants.DECODE_URL) + .post(requestBody) + .build(); + + Response response = client.newCall(request).execute(); + + if (response.isSuccessful()) { + String responseData = response.body().string(); + Map dataMap = objectMapper.readValue(responseData, Map.class); + if (dataMap.getOrDefault("status", "").equals("success")) { + List geojsonsList = (List) dataMap.get("geojsons"); + List geojsonConfidence = (List) dataMap.get("confidence_scores"); + Double maxVal = Collections.max(geojsonConfidence); + Integer maxIdx = geojsonConfidence.indexOf(maxVal); + Geometry geometryJson = reader.read(geojsonsList.get(maxIdx)); + // validate geometry + geometryList = CommonUtils.extractPolygons(geometryJson); + } + } + + } catch (Exception e) { + Logging.error(e); + } + return CommonUtils.filterByArea(geometryList, 0.1); + } + + public Coordinate mouseClick2Coordinate(double x, double y) { + double minX = getBbox().get(0); + double minY = getBbox().get(1); + double maxX = getBbox().get(2); + double maxY = getBbox().get(3); + + // image size + int bboxHeight = getImageShape().get(0); + int bboxWidth = getImageShape().get(1); + + // factor (nx, ny) + double nx = (x - minX) / (maxX - minX); + double ny = (y - minY) / (maxY - minY); + // Y i invert + return new Coordinate(nx * bboxWidth, (1 - ny) * bboxHeight); + } + + + public boolean containsPoint(Point p) { + try { + return getBboxPolygon().contains(p); + } catch (Exception e) { + Logging.error(e); + } + return false; + } + + private Way createBboxWay() { + Node node1 = new Node(new EastNorth(getBbox().get(0), getBbox().get(1))); + Node node2 = new Node(new EastNorth(getBbox().get(0), getBbox().get(3))); + Node node3 = new Node(new EastNorth(getBbox().get(2), getBbox().get(3))); + Node node4 = new Node(new EastNorth(getBbox().get(2), getBbox().get(1))); + + Way way = new Way(); + way.addNode(node1); + way.addNode(node2); + way.addNode(node3); + way.addNode(node4); + way.addNode(node1); + return way; + } + + public Way getBboxWay() { + return bboxWay; + } + + public void setLayerImage(BufferedImage layerImage) { + this.layerImage = layerImage; + } + + public void setProjectionBounds(ProjectionBounds projectionBounds) { + this.projectionBounds = projectionBounds; + } + + public String getBase64Image() { + return base64Image; + } + + public void setBase64Image(String base64Image) { + this.base64Image = base64Image; + } + + public void setCenter(EastNorth center) { + this.center = center; + } + + public void setEncodeImage(boolean encodeImage) { + isEncodeImage = encodeImage; + } + + public List getImageShape() { + return imageShape; + } + + public void setImageShape(List imageShape) { + this.imageShape = imageShape; + } + + public String getCrs() { + return crs; + } + + public void setCrs(String crs) { + this.crs = crs; + } + + public List getBbox() { + return bbox; + } + + public void setBbox(List bbox) { + this.bbox = bbox; + } + + public String getImageEmbedding() { + return imageEmbedding; + } + + public void setImageEmbedding(String imageEmbedding) { + this.imageEmbedding = imageEmbedding; + } + + public Polygon getBboxPolygon() { + return bboxPolygon; + } + + public void setBboxPolygon(Polygon bboxPolygon) { + this.bboxPolygon = bboxPolygon; + } + + public void setBboxWay(Way bboxWay) { + this.bboxWay = bboxWay; + } + + public String getNameObject() { + return nameObject; + } + + public void setNameObject(String nameObject) { + this.nameObject = nameObject; + } + + public String getLayerName() { + return layerName; + } + + public void setLayerName(String layerName) { + this.layerName = layerName; + } +} diff --git a/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/SamImageGrid.java b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/SamImageGrid.java new file mode 100644 index 0000000..87058b0 --- /dev/null +++ b/src/org/openstreetmap/josm/plugins/devseed/JosmMagicWand/utils/SamImageGrid.java @@ -0,0 +1,80 @@ +package org.openstreetmap.josm.plugins.devseed.JosmMagicWand.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.locationtech.jts.geom.Point; +import org.openstreetmap.josm.tools.Logging; + +import javax.swing.*; +import java.awt.*; +import java.io.File; +import java.util.ArrayList; +public class SamImageGrid extends JPanel implements ImagePanelListener { + private ArrayList samImageList; + private int maxWidth; + ObjectMapper objectMapper = new ObjectMapper(); + + public SamImageGrid(int maxWidth) { + this.maxWidth = maxWidth; + samImageList = new ArrayList<>(); + setLayout(new GridLayout(0, 2, 1, 1)); + + // + File cacheDir = new File(CommonUtils.magicWandCacheDirPath()); + File[] cacheFiles = cacheDir.listFiles((dir, fileName) -> fileName.endsWith(".json")); + + for (File jsonFile : cacheFiles) { + try { + SamImage samImage = objectMapper.readValue(jsonFile, SamImage.class); + addSamImage(samImage); + } catch (Exception e) { + Logging.error(e); + } + } + } + + public void addSamImage(SamImage samImage) { + if (samImage.isEncodeImage()) { + samImageList.add(samImage); + } + updateJpanel(); + } + + public void removeAllSamImage() { + samImageList.clear(); + updateJpanel(); + } + + public ArrayList getSamImageList() { + return samImageList; + } + + public SamImage getSamImageIncludepoint(Point p) { + for (SamImage s : samImageList) { + if (s.containsPoint(p)) { + return s; + } + } + return null; + } + + private void updateJpanel() { + removeAll(); + for (SamImage samImage : samImageList) { + add(new ImagePanel(samImage, this.maxWidth,this)); + } + revalidate(); + repaint(); + } + + + @Override + public void removeSamImage(SamImage samImage) { + try { + samImageList.remove(samImage); + updateJpanel(); + } catch (Exception e) { + Logging.error(e); + + } + } +} \ No newline at end of file