From 55f34dd95c732dd95d41ef84a40084e6911e1cdb Mon Sep 17 00:00:00 2001 From: Taylor Smock Date: Thu, 19 Sep 2019 20:54:05 -0600 Subject: [PATCH] Add data to data layer (left on RapiD layer for now) Signed-off-by: Taylor Smock --- .../josm/plugins/rapid/RapiDPlugin.java | 7 + .../rapid/backend/RapiDAddCommand.java | 179 ++++++++++++++++++ .../plugins/rapid/backend/RapiDDataUtils.java | 50 +++-- .../rapid/backend/RapiDMoveAction.java | 46 +++++ .../plugins/rapid/RapiDAddComandTest.java | 70 +++++++ .../plugins/rapid/RapiDDataUtilsTest.java | 27 +++ 6 files changed, 359 insertions(+), 20 deletions(-) create mode 100644 src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDAddCommand.java create mode 100644 src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDMoveAction.java create mode 100644 test/unit/org/openstreetmap/josm/plugins/rapid/RapiDAddComandTest.java create mode 100644 test/unit/org/openstreetmap/josm/plugins/rapid/RapiDDataUtilsTest.java diff --git a/src/main/java/org/openstreetmap/josm/plugins/rapid/RapiDPlugin.java b/src/main/java/org/openstreetmap/josm/plugins/rapid/RapiDPlugin.java index 717534f..4f629ee 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/rapid/RapiDPlugin.java +++ b/src/main/java/org/openstreetmap/josm/plugins/rapid/RapiDPlugin.java @@ -1,10 +1,13 @@ // License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.rapid; +import javax.swing.AbstractAction; + import org.openstreetmap.josm.gui.MainApplication; import org.openstreetmap.josm.plugins.Plugin; import org.openstreetmap.josm.plugins.PluginInformation; import org.openstreetmap.josm.plugins.rapid.backend.RapiDAction; +import org.openstreetmap.josm.plugins.rapid.backend.RapiDMoveAction; public final class RapiDPlugin extends Plugin { /** The name of the plugin */ @@ -14,7 +17,11 @@ public final class RapiDPlugin extends Plugin { super(info); RapiDAction action = new RapiDAction(); + AbstractAction add = new RapiDMoveAction(); + MainApplication.getMenu().dataMenu.add(action); + MainApplication.getMenu().dataMenu.add(add); MainApplication.getMenu().fileMenu.add(action); + MainApplication.getMenu().fileMenu.add(add); } } diff --git a/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDAddCommand.java b/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDAddCommand.java new file mode 100644 index 0000000..f1dcc05 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDAddCommand.java @@ -0,0 +1,179 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.rapid.backend; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.TreeSet; +import java.util.stream.Collectors; + +import org.openstreetmap.josm.command.AddPrimitivesCommand; +import org.openstreetmap.josm.command.Command; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.OsmPrimitiveType; +import org.openstreetmap.josm.data.osm.PrimitiveData; +import org.openstreetmap.josm.data.osm.Relation; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.data.osm.visitor.MergeSourceBuildingVisitor; +import org.openstreetmap.josm.plugins.rapid.RapiDPlugin; +import org.openstreetmap.josm.tools.Logging; +import org.openstreetmap.josm.tools.Utils; + +public class RapiDAddCommand extends Command { + DataSet editable; + DataSet rapid; + Collection primitives; + AddPrimitivesCommand addPrimitivesCommand; + Collection modifiedPrimitives; + + /** + * Add primitives from RapiD to the OSM data layer + * + * @param rapid The rapid dataset + * @param editable The OSM dataset + * @param selection The primitives to add from RapiD + */ + public RapiDAddCommand(DataSet rapid, DataSet editable, Collection selection) { + super(rapid); + this.rapid = rapid; + this.editable = editable; + this.primitives = selection; + modifiedPrimitives = null; + } + + @Override + public boolean executeCommand() { + if (rapid.equals(editable)) { + Logging.error("{0}: DataSet rapid ({1}) should not be the same as DataSet editable ({2})", RapiDPlugin.NAME, + rapid, editable); + throw new IllegalArgumentException(); + } + primitives = new HashSet<>(primitives); + addPrimitivesToCollection(/* collection= */ primitives, /* primitives= */ primitives); + synchronized (this) { + rapid.unlock(); + Collection newPrimitives = new TreeSet<>(moveCollection(rapid, editable, primitives)); + createConnections(editable, newPrimitives); + rapid.lock(); + } + return true; + } + + /** + * Create connections based off of current RapiD syntax + * + * @param collection The primitives with connection information (currently only + * checks Nodes) + */ + public static void createConnections(DataSet dataSet, Collection collection) { + Collection nodes = Utils.filteredCollection(collection, Node.class); + for (Node node : nodes) { + if (node.hasKey("conn")) { + // Currently w,n,n + String[] connections = node.get("conn").split(","); + OsmPrimitive[] primitiveConnections = new OsmPrimitive[connections.length]; + for (int i = 0; i < connections.length; i++) { + String member = connections[i]; + long id = Long.parseLong(member.substring(1)); + char firstChar = member.charAt(0); + if (firstChar == 'w') { + primitiveConnections[i] = dataSet.getPrimitiveById(id, OsmPrimitiveType.WAY); + } else if (firstChar == 'n') { + primitiveConnections[i] = dataSet.getPrimitiveById(id, OsmPrimitiveType.NODE); + } else if (firstChar == 'r') { + primitiveConnections[i] = dataSet.getPrimitiveById(id, OsmPrimitiveType.RELATION); + } + } + for (int i = 0; i < primitiveConnections.length / 3; i++) { + if (primitiveConnections[i] instanceof Way && primitiveConnections[i + 1] instanceof Node + && primitiveConnections[i + 2] instanceof Node) { + addNodesToWay(node, (Way) primitiveConnections[i], (Node) primitiveConnections[i + 1], + (Node) primitiveConnections[i + 2]); + } else { + Logging.error("{0}: {1}, {2}: {3}, {4}: {5}", i, primitiveConnections[i].getClass(), i + 1, + primitiveConnections[i + 1].getClass(), i + 2, primitiveConnections[i + 2].getClass()); + } + } + Logging.error("RapiD: Removing conn from {0} in {1}", node, dataSet.getName()); + node.remove("conn"); + } + } + } + + /** + * Add a node to a way + * + * @param toAddNode The node to add + * @param way The way to add the node to + * @param first The first node in a waysegment (the node is between this and + * the second node) + * @param second The second node in a waysegemnt + * @param recursion The recursion (how many times this has called itself). Use 0 + * when calling. + */ + public static void addNodesToWay(Node toAddNode, Way way, Node first, Node second) { + int index = Math.min(way.getNodes().indexOf(first), way.getNodes().indexOf(second)); + way.addNode(index, toAddNode); + } + + /** + * Move primitives from one dataset to another + * + * @param to The receiving dataset + * @param from The sending dataset + * @param selection The primitives to move + * @return true if the primitives have moved datasets + */ + public Collection moveCollection(DataSet from, DataSet to, + Collection selection) { + if (from == null || to.isLocked() || from.isLocked()) { + Logging.error("RapiD: Cannot move primitives from {0} to {1}", from, to); + return Collections.emptySet(); + } + Collection originalSelection = from.getSelected(); + from.setSelected(selection); + MergeSourceBuildingVisitor mergeBuilder = new MergeSourceBuildingVisitor(from); + List primitiveDataList = mergeBuilder.build().allPrimitives().stream().map(OsmPrimitive::save) + .collect(Collectors.toList()); + from.setSelected(originalSelection); + addPrimitivesCommand = new AddPrimitivesCommand(primitiveDataList, primitiveDataList, to); + addPrimitivesCommand.executeCommand(); + return addPrimitivesCommand.getParticipatingPrimitives(); + } + + /** + * Add primitives and their children to a collection + * + * @param collection A collection to add the primitives to + * @param primitives The primitives to add to the collection + */ + public static void addPrimitivesToCollection(Collection collection, + Collection primitives) { + for (OsmPrimitive primitive : primitives) { + if (primitive instanceof Way) { + collection.addAll(((Way) primitive).getNodes()); + } else if (primitive instanceof Relation) { + addPrimitivesToCollection(collection, ((Relation) primitive).getMemberPrimitives()); + } + collection.add(primitive); + } + } + + @Override + public String getDescriptionText() { + return tr("Add object from RapiD"); + } + + @Override + public void fillModifiedData(Collection modified, Collection deleted, + Collection added) { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDDataUtils.java b/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDDataUtils.java index b225e04..abffd91 100644 --- a/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDDataUtils.java +++ b/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDDataUtils.java @@ -3,9 +3,9 @@ package org.openstreetmap.josm.plugins.rapid.backend; import java.io.IOException; import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; +import java.util.HashSet; +import java.util.Set; import org.openstreetmap.josm.data.osm.BBox; import org.openstreetmap.josm.data.osm.DataSet; @@ -20,6 +20,14 @@ import org.openstreetmap.josm.tools.Logging; */ public final class RapiDDataUtils { private static final String RAPID_API_TOKEN = "ASZUVdYpCkd3M6ZrzjXdQzHulqRMnxdlkeBJWEKOeTUoY_Gwm9fuEd2YObLrClgDB_xfavizBsh0oDfTWTF7Zb4C"; + private static Set API_LIST = new HashSet<>(); + static { + addRapidApi(new StringBuilder().append("https://www.facebook.com/maps/ml_roads?").append("conflate_with_osm=") + .append(true).append("&").append("theme=") + .append("ml_road_vector").append("&").append("collaborator=").append("fbid").append("&") + .append("token=").append(RAPID_API_TOKEN).append("&").append("hash=").append("ASYM8LPNy8k1XoJiI7A") + .append("&").append("bbox={bbox}").toString()); + } private RapiDDataUtils() { // Hide the constructor @@ -30,27 +38,29 @@ public final class RapiDDataUtils { Logging.setLogLevel(Logging.LEVEL_DEBUG); InputStream inputStream = null; DataSet dataSet = new DataSet(); - try { - final String query = new StringBuilder().append("conflate_with_osm=").append(true).append("theme=") - .append("ml_road_vector").append("&").append("collaborator=").append("fbid").append("&") - .append("token=").append(RAPID_API_TOKEN).append("&").append("hash=").append("ASYM8LPNy8k1XoJiI7A") - .append("&").append("bbox=").append(bbox.toString()).toString(); - final URI uri = new URI("https", null, "www.facebook.com", 80, "/maps/ml_roads", query, null); - final URL url = uri.toURL(); - inputStream = url.openStream(); - Logging.info("{0}: Getting {1}", RapiDPlugin.NAME, uri.toASCIIString()); - dataSet = OsmReader.parseDataSet(inputStream, null); - } catch (URISyntaxException | UnsupportedOperationException | IllegalDataException | IOException e) { - Logging.debug(e); - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - Logging.debug(e); + for (String urlString : API_LIST) { + try { + final URL url = new URL(urlString.replace("{bbox}", bbox.toStringCSV(","))); + Logging.error("{0}: Getting {1}", RapiDPlugin.NAME, url.toString()); + + inputStream = url.openStream(); + dataSet.mergeFrom(OsmReader.parseDataSet(inputStream, null)); + } catch (UnsupportedOperationException | IllegalDataException | IOException e) { + Logging.debug(e); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + Logging.debug(e); + } } } } return dataSet; } + + public static void addRapidApi(String url) { + API_LIST.add(url); + } } diff --git a/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDMoveAction.java b/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDMoveAction.java new file mode 100644 index 0000000..0abe567 --- /dev/null +++ b/src/main/java/org/openstreetmap/josm/plugins/rapid/backend/RapiDMoveAction.java @@ -0,0 +1,46 @@ +package org.openstreetmap.josm.plugins.rapid.backend; + +import static org.openstreetmap.josm.tools.I18n.tr; + +import java.awt.event.ActionEvent; +import java.util.Collection; +import java.util.List; + +import javax.swing.AbstractAction; + +import org.openstreetmap.josm.data.UndoRedoHandler; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.gui.MainApplication; +import org.openstreetmap.josm.gui.layer.OsmDataLayer; +import org.openstreetmap.josm.plugins.rapid.RapiDPlugin; + +public class RapiDMoveAction extends AbstractAction { + /** UID for abstract action */ + private static final long serialVersionUID = 319374598; + + public RapiDMoveAction() { + super(tr("Add from ".concat(RapiDPlugin.NAME))); + } + + @Override + public void actionPerformed(ActionEvent event) { + for (RapiDLayer layer : MainApplication.getLayerManager().getLayersOfType(RapiDLayer.class)) { + List osmLayers = MainApplication.getLayerManager().getLayersOfType(OsmDataLayer.class); + DataSet editData = null; + DataSet rapid = layer.getDataSet(); + Collection selected = rapid.getSelected(); + for (OsmDataLayer osmLayer : osmLayers) { + if (!osmLayer.isLocked() && osmLayer.isVisible() && osmLayer.isUploadable() + && osmLayer.getClass().equals(OsmDataLayer.class)) { + editData = osmLayer.getDataSet(); + break; + } + } + if (editData != null) { + RapiDAddCommand command = new RapiDAddCommand(rapid, editData, selected); + UndoRedoHandler.getInstance().add(command); + } + } + } +} diff --git a/test/unit/org/openstreetmap/josm/plugins/rapid/RapiDAddComandTest.java b/test/unit/org/openstreetmap/josm/plugins/rapid/RapiDAddComandTest.java new file mode 100644 index 0000000..22fa0b1 --- /dev/null +++ b/test/unit/org/openstreetmap/josm/plugins/rapid/RapiDAddComandTest.java @@ -0,0 +1,70 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.rapid; + +import java.util.Collection; +import java.util.Collections; +import java.util.TreeSet; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.openstreetmap.josm.TestUtils; +import org.openstreetmap.josm.data.coor.LatLon; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.data.osm.Node; +import org.openstreetmap.josm.data.osm.OsmPrimitive; +import org.openstreetmap.josm.data.osm.Way; +import org.openstreetmap.josm.plugins.rapid.backend.RapiDAddCommand; +import org.openstreetmap.josm.testutils.JOSMTestRules; + +public class RapiDAddComandTest { + @Rule + public JOSMTestRules test = new JOSMTestRules(); + + @Test + public void testMoveCollection() { + DataSet ds1 = new DataSet(); + DataSet ds2 = new DataSet(); + Way way1 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), + new Node(new LatLon(0, 0.1))); + for (Node node : way1.getNodes()) { + ds1.addPrimitive(node); + } + ds1.addPrimitive(way1); + ds1.lock(); + RapiDAddCommand command = new RapiDAddCommand(ds1, ds2, Collections.singleton(way1)); + command.executeCommand(); + Assert.assertTrue(ds2.containsWay(way1)); + Assert.assertTrue(ds2.containsNode(way1.firstNode())); + Assert.assertTrue(ds2.containsNode(way1.lastNode())); + // Assert.assertFalse(ds1.containsWay(way1)); // Only if we delete ways from + // rapid dataset + } + + @Test + public void testAddPrimitivesToCollection() { + Way way1 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(0, 0.1))); + Collection collection = new TreeSet<>(); + Assert.assertEquals(0, collection.size()); + RapiDAddCommand.addPrimitivesToCollection(collection, Collections.singletonList(way1)); + Assert.assertEquals(3, collection.size()); + } + + @Test + public void testCreateConnections() { + DataSet ds1 = new DataSet(); + Way way1 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0)), new Node(new LatLon(0, 0.1))); + Way way2 = TestUtils.newWay("highway=residential", new Node(new LatLon(0, 0.05)), + new Node(new LatLon(0.05, 0.2))); + way2.firstNode().put("conn", + "w".concat(Long.toString(way1.getUniqueId())).concat(",n") + .concat(Long.toString(way1.firstNode().getUniqueId())).concat(",n") + .concat(Long.toString(way1.lastNode().getUniqueId()))); + way1.getNodes().forEach(node -> ds1.addPrimitive(node)); + way2.getNodes().forEach(node -> ds1.addPrimitive(node)); + ds1.addPrimitive(way2); + ds1.addPrimitive(way1); + RapiDAddCommand.createConnections(ds1, Collections.singletonList(way2.firstNode())); + Assert.assertEquals(3, way1.getNodesCount()); + } +} diff --git a/test/unit/org/openstreetmap/josm/plugins/rapid/RapiDDataUtilsTest.java b/test/unit/org/openstreetmap/josm/plugins/rapid/RapiDDataUtilsTest.java new file mode 100644 index 0000000..38ccc7b --- /dev/null +++ b/test/unit/org/openstreetmap/josm/plugins/rapid/RapiDDataUtilsTest.java @@ -0,0 +1,27 @@ +// License: GPL. For details, see LICENSE file. +package org.openstreetmap.josm.plugins.rapid; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.openstreetmap.josm.data.coor.LatLon; +import org.openstreetmap.josm.data.osm.BBox; +import org.openstreetmap.josm.data.osm.DataSet; +import org.openstreetmap.josm.plugins.rapid.backend.RapiDDataUtils; +import org.openstreetmap.josm.testutils.JOSMTestRules; + +public class RapiDDataUtilsTest { + @Rule + public JOSMTestRules test = new JOSMTestRules(); + + /** + * This gets data from RapiD. This test may fail if someone adds the data to OSM. + */ + @Test + public void testGetData() { + BBox testBBox = new BBox(); + testBBox.add(new LatLon(39.066058, -108.5683808)); + testBBox.add(new LatLon(39.0667526, -108.5681757)); + DataSet ds = new DataSet(RapiDDataUtils.getData(testBBox)); + Assert.assertEquals(1, ds.getWays().size()); + } +}