kopia lustrzana https://github.com/JOSM/MapWithAI
Add new conflation command to merge address nodes onto buildings
Also some conflation commands can be undone without move the object back to the MapWithAI layer. Signed-off-by: Taylor Smock <taylor.smock@kaart.com>pull/1/head
rodzic
41105000c0
commit
d05eca89d8
|
@ -132,4 +132,10 @@ public abstract class AbstractConflationCommand extends Command {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the command should show as a separate command in the
|
||||
* undo/redo lists
|
||||
*/
|
||||
public abstract boolean allowUndo();
|
||||
}
|
||||
|
|
|
@ -94,4 +94,9 @@ public class ConnectedCommand extends AbstractConflationCommand {
|
|||
}
|
||||
return returnCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowUndo() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,4 +104,9 @@ public class DuplicateCommand extends AbstractConflationCommand {
|
|||
}
|
||||
return returnCommand;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowUndo() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
// License: GPL. For details, see LICENSE file.
|
||||
package org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation;
|
||||
|
||||
import static org.openstreetmap.josm.tools.I18n.tr;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.openstreetmap.josm.command.Command;
|
||||
import org.openstreetmap.josm.command.SequenceCommand;
|
||||
import org.openstreetmap.josm.data.osm.DataSet;
|
||||
|
@ -19,9 +23,10 @@ import org.openstreetmap.josm.data.osm.Way;
|
|||
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
|
||||
import org.openstreetmap.josm.plugins.utilsplugin2.replacegeometry.ReplaceGeometryUtils;
|
||||
import org.openstreetmap.josm.tools.Geometry;
|
||||
import org.openstreetmap.josm.tools.Logging;
|
||||
|
||||
public class MergeAddressBuildings extends AbstractConflationCommand {
|
||||
private static final String BUILDING_KEY = "building";
|
||||
public static final String KEY = "building";
|
||||
|
||||
public MergeAddressBuildings(DataSet data) {
|
||||
super(data);
|
||||
|
@ -39,7 +44,7 @@ public class MergeAddressBuildings extends AbstractConflationCommand {
|
|||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return BUILDING_KEY;
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -47,16 +52,18 @@ public class MergeAddressBuildings extends AbstractConflationCommand {
|
|||
List<Command> commands = new ArrayList<>();
|
||||
if (MapWithAIPreferenceHelper.isMergeBuildingAddress()) {
|
||||
possiblyAffectedPrimitives.stream().filter(Way.class::isInstance).map(Way.class::cast)
|
||||
.filter(way -> way.hasKey(BUILDING_KEY)).filter(Way::isClosed)
|
||||
.filter(way -> way.hasKey(KEY)).filter(Way::isClosed)
|
||||
.forEach(way -> commands.addAll(mergeAddressBuilding(getAffectedDataSet(), way)));
|
||||
|
||||
possiblyAffectedPrimitives.stream().filter(Relation.class::isInstance).map(Relation.class::cast)
|
||||
.filter(rel -> rel.hasKey(BUILDING_KEY)).filter(Relation::isMultipolygon)
|
||||
.filter(rel -> rel.hasKey(KEY)).filter(Relation::isMultipolygon)
|
||||
.forEach(rel -> commands.addAll(mergeAddressBuilding(getAffectedDataSet(), rel)));
|
||||
}
|
||||
|
||||
Command returnCommand = null;
|
||||
if (!commands.isEmpty()) {
|
||||
if (commands.size() == 1) {
|
||||
returnCommand = commands.get(0);
|
||||
} else if (!commands.isEmpty()) {
|
||||
returnCommand = new SequenceCommand(getDescriptionText(), commands);
|
||||
}
|
||||
return returnCommand;
|
||||
|
@ -83,15 +90,25 @@ public class MergeAddressBuildings extends AbstractConflationCommand {
|
|||
String currentKey = null;
|
||||
try {
|
||||
// Remove the key to avoid the popup from utilsplugin2
|
||||
currentKey = object.get(BUILDING_KEY);
|
||||
object.remove(BUILDING_KEY);
|
||||
commandList.add(ReplaceGeometryUtils.buildUpgradeNodeCommand(nodesWithAddresses.get(0), object));
|
||||
currentKey = object.get(KEY);
|
||||
object.remove(KEY);
|
||||
try {
|
||||
SwingUtilities.invokeAndWait(() -> commandList
|
||||
.add(ReplaceGeometryUtils.buildUpgradeNodeCommand(nodesWithAddresses.get(0), object)));
|
||||
} catch (InvocationTargetException | InterruptedException e) {
|
||||
Logging.error(e);
|
||||
}
|
||||
} finally {
|
||||
if (currentKey != null) {
|
||||
object.put(BUILDING_KEY, currentKey);
|
||||
object.put(KEY, currentKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return commandList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowUndo() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,134 @@
|
|||
// License: GPL. For details, see LICENSE file.
|
||||
package org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation;
|
||||
|
||||
import static org.openstreetmap.josm.tools.I18n.tr;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.openstreetmap.josm.command.ChangePropertyCommand;
|
||||
import org.openstreetmap.josm.command.Command;
|
||||
import org.openstreetmap.josm.command.DeleteCommand;
|
||||
import org.openstreetmap.josm.command.SequenceCommand;
|
||||
import org.openstreetmap.josm.data.osm.BBox;
|
||||
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.Relation;
|
||||
import org.openstreetmap.josm.data.osm.Way;
|
||||
import org.openstreetmap.josm.plugins.mapwithai.backend.MapWithAIPreferenceHelper;
|
||||
import org.openstreetmap.josm.tools.Geometry;
|
||||
|
||||
public class MergeBuildingAddress extends AbstractConflationCommand {
|
||||
public static final String KEY = "addr:housenumber";
|
||||
|
||||
public MergeBuildingAddress(DataSet data) {
|
||||
super(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescriptionText() {
|
||||
return tr("Merge added addresses with existing buildings");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends OsmPrimitive>> getInterestedTypes() {
|
||||
return Collections.singleton(Node.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getKey() {
|
||||
return KEY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Command getRealCommand() {
|
||||
List<Command> commands = new ArrayList<>();
|
||||
if (MapWithAIPreferenceHelper.isMergeBuildingAddress()) {
|
||||
possiblyAffectedPrimitives.stream().filter(Node.class::isInstance).map(Node.class::cast)
|
||||
.filter(n -> n.hasKey(KEY))
|
||||
.forEach(n -> commands.addAll(mergeBuildingAddress(getAffectedDataSet(), n)));
|
||||
}
|
||||
|
||||
Command returnCommand = null;
|
||||
if (commands.size() == 1) {
|
||||
returnCommand = commands.get(0);
|
||||
} else if (!commands.isEmpty()) {
|
||||
returnCommand = new SequenceCommand(getDescriptionText(), commands);
|
||||
}
|
||||
return returnCommand;
|
||||
}
|
||||
|
||||
private static Collection<Command> mergeBuildingAddress(DataSet affectedDataSet, Node node) {
|
||||
final List<OsmPrimitive> toCheck = new ArrayList<>();
|
||||
final BBox bbox = new BBox(node.getCoor().getX(), node.getCoor().getY(), 0.001);
|
||||
toCheck.addAll(affectedDataSet.searchWays(bbox));
|
||||
toCheck.addAll(affectedDataSet.searchRelations(bbox));
|
||||
toCheck.addAll(affectedDataSet.searchNodes(bbox));
|
||||
List<OsmPrimitive> possibleDuplicates = toCheck.parallelStream().filter(prim -> prim.hasTag(KEY))
|
||||
.filter(prim -> prim.get(KEY).equals(node.get(KEY))).filter(prim -> !prim.equals(node))
|
||||
.collect(Collectors.toList());
|
||||
for (String tag : Arrays.asList("addr:street", "addr:unit")) {
|
||||
if (node.hasTag(tag)) {
|
||||
possibleDuplicates = possibleDuplicates.parallelStream().filter(prim -> prim.hasTag(tag))
|
||||
.filter(prim -> prim.get(tag).equals(node.get(tag))).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
List<OsmPrimitive> buildings = toCheck.parallelStream().filter(prim -> prim.hasTag("building"))
|
||||
.filter(prim -> checkInside(node, prim)).collect(Collectors.toList());
|
||||
|
||||
final List<Command> commandList = new ArrayList<>();
|
||||
if (possibleDuplicates.size() == 1) {
|
||||
commandList.add(new ChangePropertyCommand(possibleDuplicates, node.getKeys()));
|
||||
commandList.add(DeleteCommand.delete(Collections.singleton(node)));
|
||||
} else if (buildings.size() == 1 && getAddressPoints(buildings.get(0)).size() == 1) {
|
||||
commandList.add(new ChangePropertyCommand(buildings, node.getKeys()));
|
||||
commandList.add(DeleteCommand.delete(Collections.singleton(node)));
|
||||
}
|
||||
|
||||
return commandList;
|
||||
}
|
||||
|
||||
private static Collection<Node> getAddressPoints(OsmPrimitive prim) {
|
||||
if (prim instanceof Way && ((Way) prim).isClosed()) {
|
||||
return Geometry
|
||||
.filterInsidePolygon(new ArrayList<>(prim.getDataSet().allNonDeletedPrimitives()), (Way) prim)
|
||||
.parallelStream().filter(Node.class::isInstance).map(Node.class::cast).filter(n -> n.hasTag(KEY))
|
||||
.collect(Collectors.toList());
|
||||
} else if (prim instanceof Relation) {
|
||||
return Geometry
|
||||
.filterInsideMultipolygon(new ArrayList<>(prim.getDataSet().allNonDeletedPrimitives()),
|
||||
(Relation) prim)
|
||||
.parallelStream().filter(Node.class::isInstance).map(Node.class::cast).filter(n -> n.hasKey(KEY))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the node is inside the other primitive
|
||||
*
|
||||
* @param node The node to check
|
||||
* @param prim The primitive that the node may be inside
|
||||
* @return true if the node is inside the primitive
|
||||
*/
|
||||
private static boolean checkInside(Node node, OsmPrimitive prim) {
|
||||
if (prim instanceof Relation) {
|
||||
return !Geometry.filterInsideMultipolygon(Collections.singleton(node), (Relation) prim).isEmpty();
|
||||
} else if (prim instanceof Way) {
|
||||
return !Geometry.filterInsidePolygon(Arrays.asList(node), (Way) prim).isEmpty();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean allowUndo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ import static org.openstreetmap.josm.tools.I18n.tr;
|
|||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -14,8 +15,11 @@ import java.util.Set;
|
|||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import org.openstreetmap.josm.command.Command;
|
||||
import org.openstreetmap.josm.command.SequenceCommand;
|
||||
import org.openstreetmap.josm.data.UndoRedoHandler;
|
||||
import org.openstreetmap.josm.data.osm.DataSet;
|
||||
import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
||||
import org.openstreetmap.josm.plugins.mapwithai.MapWithAIPlugin;
|
||||
|
@ -23,17 +27,20 @@ import org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation.Abst
|
|||
import org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation.ConnectedCommand;
|
||||
import org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation.DuplicateCommand;
|
||||
import org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation.MergeAddressBuildings;
|
||||
import org.openstreetmap.josm.plugins.mapwithai.backend.commands.conflation.MergeBuildingAddress;
|
||||
import org.openstreetmap.josm.tools.Logging;
|
||||
import org.openstreetmap.josm.tools.Utils;
|
||||
|
||||
public class CreateConnectionsCommand extends Command {
|
||||
private final Collection<OsmPrimitive> primitives;
|
||||
private Command command;
|
||||
private Command undoCommands;
|
||||
private static final LinkedHashSet<Class<? extends AbstractConflationCommand>> CONFLATION_COMMANDS = new LinkedHashSet<>();
|
||||
static {
|
||||
CONFLATION_COMMANDS.add(ConnectedCommand.class);
|
||||
CONFLATION_COMMANDS.add(DuplicateCommand.class);
|
||||
CONFLATION_COMMANDS.add(MergeAddressBuildings.class);
|
||||
CONFLATION_COMMANDS.add(MergeBuildingAddress.class);
|
||||
}
|
||||
|
||||
public CreateConnectionsCommand(DataSet data, Collection<OsmPrimitive> primitives) {
|
||||
|
@ -44,11 +51,17 @@ public class CreateConnectionsCommand extends Command {
|
|||
@Override
|
||||
public boolean executeCommand() {
|
||||
if (command == null) {
|
||||
command = createConnections(getAffectedDataSet(), primitives);
|
||||
List<Command> commands = createConnections(getAffectedDataSet(), primitives);
|
||||
command = commands.get(0);
|
||||
undoCommands = commands.get(1);
|
||||
}
|
||||
if (command != null) {
|
||||
command.executeCommand();
|
||||
}
|
||||
if (undoCommands != null) {
|
||||
undoCommands.executeCommand();
|
||||
SwingUtilities.invokeLater(() -> UndoRedoHandler.getInstance().add(undoCommands, false));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -66,10 +79,13 @@ public class CreateConnectionsCommand extends Command {
|
|||
* connecting to
|
||||
* @param collection The primitives with connection information (currently only
|
||||
* checks Nodes)
|
||||
* @return A {@link Command} to create connections with
|
||||
* @return A list {@link Command} to create connections with (first is one that
|
||||
* can be folded into other commands, second is one that should be
|
||||
* undoable individually)
|
||||
*/
|
||||
public static Command createConnections(DataSet dataSet, Collection<OsmPrimitive> collection) {
|
||||
final List<Command> changedKeyList = new ArrayList<>();
|
||||
public static List<Command> createConnections(DataSet dataSet, Collection<OsmPrimitive> collection) {
|
||||
final List<Command> permanent = new ArrayList<>();
|
||||
final List<Command> undoable = new ArrayList<>();
|
||||
final Collection<OsmPrimitive> realPrimitives = collection.stream().map(dataSet::getPrimitiveById)
|
||||
.filter(Objects::nonNull).collect(Collectors.toList());
|
||||
for (final Class<? extends AbstractConflationCommand> abstractCommandClass : getConflationCommands()) {
|
||||
|
@ -88,18 +104,29 @@ public class CreateConnectionsCommand extends Command {
|
|||
final Command actualCommand = abstractCommand.getCommand(tPrimitives.stream()
|
||||
.filter(prim -> prim.hasKey(abstractCommand.getKey())).collect(Collectors.toList()));
|
||||
if (Objects.nonNull(actualCommand)) {
|
||||
changedKeyList.add(actualCommand);
|
||||
if (abstractCommand.allowUndo()) {
|
||||
undoable.add(actualCommand);
|
||||
} else {
|
||||
permanent.add(actualCommand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command returnSequence = null;
|
||||
if (changedKeyList.size() == 1) {
|
||||
returnSequence = changedKeyList.get(0);
|
||||
} else if (!changedKeyList.isEmpty()) {
|
||||
returnSequence = new SequenceCommand(getRealDescriptionText(), changedKeyList);
|
||||
Command permanentCommand = null;
|
||||
if (permanent.size() == 1) {
|
||||
permanentCommand = permanent.get(0);
|
||||
} else if (!permanent.isEmpty()) {
|
||||
permanentCommand = new SequenceCommand(getRealDescriptionText(), permanent);
|
||||
}
|
||||
|
||||
return returnSequence;
|
||||
Command undoCommand = null;
|
||||
if (undoable.size() == 1) {
|
||||
undoCommand = undoable.get(0);
|
||||
} else if (!undoable.isEmpty()) {
|
||||
undoCommand = new SequenceCommand(getRealDescriptionText(), undoable);
|
||||
}
|
||||
|
||||
return Arrays.asList(permanentCommand, undoCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -178,7 +178,8 @@ public class CreateConnectionsCommandTest {
|
|||
final Node node1 = new Node(new LatLon(39.0674124, -108.5592645));
|
||||
final DataSet dataSet = new DataSet(node1);
|
||||
node1.put(DuplicateCommand.KEY, "n6146500887");
|
||||
Command replaceNodeCommand = CreateConnectionsCommand.createConnections(dataSet, Collections.singleton(node1));
|
||||
Command replaceNodeCommand = CreateConnectionsCommand.createConnections(dataSet, Collections.singleton(node1))
|
||||
.get(0);
|
||||
|
||||
replaceNodeCommand.executeCommand();
|
||||
assertEquals(1, dataSet.allNonDeletedPrimitives().size(), "There should be one primitive left");
|
||||
|
@ -195,7 +196,7 @@ public class CreateConnectionsCommandTest {
|
|||
final OsmDataLayer layer = new OsmDataLayer(dataSet, "temp layer", null);
|
||||
MainApplication.getLayerManager().addLayer(layer);
|
||||
|
||||
replaceNodeCommand = CreateConnectionsCommand.createConnections(dataSet, Collections.singleton(node1));
|
||||
replaceNodeCommand = CreateConnectionsCommand.createConnections(dataSet, Collections.singleton(node1)).get(0);
|
||||
replaceNodeCommand.executeCommand();
|
||||
assertEquals(2, dataSet.allNonDeletedPrimitives().size(), "The dupe node no longer matches with the OSM node");
|
||||
assertNotNull(dataSet.getPrimitiveById(6146500887L, OsmPrimitiveType.NODE), "The OSM node should still exist");
|
||||
|
|
Ładowanie…
Reference in New Issue