Skip to content

Commit

Permalink
Support extracting way segments between two nodes
Browse files Browse the repository at this point in the history
Resolves #2722
  • Loading branch information
simonpoole committed Nov 29, 2024
1 parent 0909ac6 commit a5e20c9
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
import de.blau.android.R;
import de.blau.android.dialogs.ElementIssueDialog;
import de.blau.android.dialogs.ErrorAlert;
import de.blau.android.exception.OsmIllegalOperationException;
import de.blau.android.exception.StorageException;
import de.blau.android.osm.Node;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.RelationUtils;
Expand Down Expand Up @@ -553,6 +555,45 @@ public void onError(@Nullable AsyncResult result) {
}
}

/**
* Extract a segment(s) from a List of Way given two nodes on all the Ways
*
* @param ways a List of Ways
* @param n1 1st Node
* @param n2 2nd Node
* @return a Runnable that will actually do the work
*/
@NonNull
protected Runnable extractSegment(@NonNull final List<Way> ways, @NonNull final Node n1, @NonNull final Node n2) {
return () -> {
try {
List<OsmElement> segments = new ArrayList<>();
for (Way way : ways) {
List<Result> result = logic.performExtractSegment(main, way, n1, n2);
checkSplitResult(way, result);
Way segment = newWayFromSplitResult(result);
if (segment == null) {
throw new OsmIllegalOperationException("null segment");
}
segments.add(segment);
}
if (segments.size() == 1) {
Way segment = (Way) segments.get(0);
if (segment.hasTagKey(Tags.KEY_HIGHWAY) || segment.hasTagKey(Tags.KEY_WATERWAY)) {
main.startSupportActionMode(new WaySegmentModifyActionModeCallback(manager, segment));
} else {
main.startSupportActionMode(new WaySelectionActionModeCallback(manager, segment));
}
} else {
main.startSupportActionMode(new MultiSelectActionModeCallback(manager, segments));
}
} catch (OsmIllegalOperationException | StorageException ex) {
// toast has already been displayed
manager.finish();
}
};
}

/**
* De-select elements
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,39 +222,40 @@ public boolean elementsOnly() {

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case ElementSelectionActionModeCallback.MENUITEM_TAG:
main.performTagEdit(selection, false, false);
break;
case MENUITEM_ZOOM_TO_SELECTION:
main.zoomTo(selection);
main.invalidateMap();
break;
case MENUITEM_SEARCH_OBJECTS:
Search.search(main);
break;
case MENUITEM_ADD_TO_TODO:
ElementSelectionActionModeCallback.addToTodoList(main, manager, selection);
break;
case MENUITEM_UPLOAD:
main.descheduleAutoLock();
main.confirmUpload(ElementSelectionActionModeCallback.addRequiredElements(main, new ArrayList<>(selection)));
break;
case ElementSelectionActionModeCallback.MENUITEM_PREFERENCES:
PrefEditor.start(main);
break;
case ElementSelectionActionModeCallback.MENUITEM_JS_CONSOLE:
Main.showJsConsole(main);
break;
case R.id.undo_action:
// should not happen
Log.d(DEBUG_TAG, "menu undo clicked");
undoListener.onClick(null);
break;
default:
return false;
}
if (super.onActionItemClicked(mode, item)) {
return true;
}
switch (item.getItemId()) {
case ElementSelectionActionModeCallback.MENUITEM_TAG:
main.performTagEdit(selection, false, false);
break;
case MENUITEM_ZOOM_TO_SELECTION:
main.zoomTo(selection);
main.invalidateMap();
break;
case MENUITEM_SEARCH_OBJECTS:
Search.search(main);
break;
case MENUITEM_ADD_TO_TODO:
ElementSelectionActionModeCallback.addToTodoList(main, manager, selection);
break;
case MENUITEM_UPLOAD:
main.descheduleAutoLock();
main.confirmUpload(ElementSelectionActionModeCallback.addRequiredElements(main, new ArrayList<>(selection)));
break;
case ElementSelectionActionModeCallback.MENUITEM_PREFERENCES:
PrefEditor.start(main);
break;
case ElementSelectionActionModeCallback.MENUITEM_JS_CONSOLE:
Main.showJsConsole(main);
break;
case R.id.undo_action:
// should not happen
Log.d(DEBUG_TAG, "menu undo clicked");
undoListener.onClick(null);
break;
default:
return false;
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,18 @@
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ActionMode;
import de.blau.android.App;
import de.blau.android.DisambiguationMenu;
import de.blau.android.Map;
import de.blau.android.R;
import de.blau.android.DisambiguationMenu.Type;
import de.blau.android.dialogs.ElementIssueDialog;
import de.blau.android.exception.OsmIllegalOperationException;
import de.blau.android.osm.Node;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.OsmElement.ElementType;
import de.blau.android.osm.Relation;
import de.blau.android.osm.Result;
import de.blau.android.osm.Storage;
import de.blau.android.osm.StorageDelegator;
import de.blau.android.osm.Tags;
import de.blau.android.osm.Way;
Expand All @@ -46,11 +49,13 @@ public class MultiSelectWithGeometryActionModeCallback extends MultiSelectAction
private static final int MENUITEM_INTERSECT = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 5;
private static final int MENUITEM_CREATE_CIRCLE = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 6;
private static final int MENUITEM_ROTATE = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 7;
private static final int MENUITEM_EXTRACT_SEGMENT = ElementSelectionActionModeCallback.LAST_REGULAR_MENUITEM + 8;

private MenuItem mergeItem;
private MenuItem orthogonalizeItem;
private MenuItem intersectItem;
private MenuItem createCircleItem;
private MenuItem extractSegmentItem;

/**
* Construct an Multi-Select actionmode from a List of OsmElements
Expand Down Expand Up @@ -87,6 +92,8 @@ public boolean onCreateActionMode(@NonNull ActionMode mode, @NonNull Menu menu)

mergeItem = menu.add(Menu.NONE, MENUITEM_MERGE, Menu.NONE, R.string.menu_merge).setIcon(ThemeUtils.getResIdFromAttribute(main, R.attr.menu_merge));

extractSegmentItem = menu.add(Menu.NONE, MENUITEM_EXTRACT_SEGMENT, Menu.NONE, R.string.menu_extract_segment);

menu.add(Menu.NONE, MENUITEM_RELATION, Menu.CATEGORY_SYSTEM, R.string.menu_relation)
.setIcon(ThemeUtils.getResIdFromAttribute(main, R.attr.menu_relation));

Expand Down Expand Up @@ -123,6 +130,11 @@ public boolean onPrepareActionMode(@NonNull ActionMode mode, @NonNull Menu menu)
updated |= ElementSelectionActionModeCallback.setItemVisibility(countType(ElementType.NODE) >= StorageDelegator.MIN_NODES_CIRCLE, createCircleItem,
false);

if (selection.size() == 2 && selection.get(0) instanceof Node && selection.get(1) instanceof Node) {
List<Way> commonWays = getWaysForNodes((Node) selection.get(0), (Node) selection.get(1));
updated |= ElementSelectionActionModeCallback.setItemVisibility(!commonWays.isEmpty(), extractSegmentItem, true);
}

if (updated) {
arrangeMenu(menu);
}
Expand Down Expand Up @@ -176,60 +188,96 @@ private boolean canMergePolygons(@NonNull List<OsmElement> selection) {

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (!super.onActionItemClicked(mode, item)) {
switch (item.getItemId()) {
case ElementSelectionActionModeCallback.MENUITEM_DELETE:
menuDelete(false);
break;
case ElementSelectionActionModeCallback.MENUITEM_COPY:
logic.copyToClipboard(selection);
mode.finish();
break;
case ElementSelectionActionModeCallback.MENUITEM_CUT:
logic.cutToClipboard(main, selection);
mode.finish();
break;
case MENUITEM_RELATION:
ElementSelectionActionModeCallback.buildPresetSelectDialog(main,
p -> main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager,
p != null ? p.getPath(App.getCurrentRootPreset(main).getRootGroup()) : null, selection)),
ElementType.RELATION, R.string.select_relation_type_title, Tags.KEY_TYPE, null).show();
break;
case MENUITEM_ADD_RELATION_MEMBERS:
ElementSelectionActionModeCallback.buildRelationSelectDialog(main, r -> {
Relation relation = (Relation) App.getDelegator().getOsmElement(Relation.NAME, r);
if (relation != null) {
main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager, relation, selection));
}
}, -1, R.string.select_relation_title, null, null, selection).show();
break;
case MENUITEM_ORTHOGONALIZE:
orthogonalizeWays();
break;
case MENUITEM_MERGE:
if (canMergePolygons(selection)) {
mergePolygons();
} else {
mergeWays();
if (super.onActionItemClicked(mode, item)) {
return true;
}
switch (item.getItemId()) {
case ElementSelectionActionModeCallback.MENUITEM_DELETE:
menuDelete(false);
break;
case ElementSelectionActionModeCallback.MENUITEM_COPY:
logic.copyToClipboard(selection);
mode.finish();
break;
case ElementSelectionActionModeCallback.MENUITEM_CUT:
logic.cutToClipboard(main, selection);
mode.finish();
break;
case MENUITEM_RELATION:
ElementSelectionActionModeCallback.buildPresetSelectDialog(main,
p -> main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager,
p != null ? p.getPath(App.getCurrentRootPreset(main).getRootGroup()) : null, selection)),
ElementType.RELATION, R.string.select_relation_type_title, Tags.KEY_TYPE, null).show();
break;
case MENUITEM_ADD_RELATION_MEMBERS:
ElementSelectionActionModeCallback.buildRelationSelectDialog(main, r -> {
Relation relation = (Relation) App.getDelegator().getOsmElement(Relation.NAME, r);
if (relation != null) {
main.startSupportActionMode(new EditRelationMembersActionModeCallback(manager, relation, selection));
}
break;
case MENUITEM_INTERSECT:
intersectWays();
break;
case MENUITEM_CREATE_CIRCLE:
createCircle();
break;
case MENUITEM_ROTATE:
deselectOnExit = false;
main.startSupportActionMode(new RotationActionModeCallback(manager));
break;
default:
return false;
}, -1, R.string.select_relation_title, null, null, selection).show();
break;
case MENUITEM_ORTHOGONALIZE:
orthogonalizeWays();
break;
case MENUITEM_MERGE:
if (canMergePolygons(selection)) {
mergePolygons();
} else {
mergeWays();
}
break;
case MENUITEM_INTERSECT:
intersectWays();
break;
case MENUITEM_CREATE_CIRCLE:
createCircle();
break;
case MENUITEM_ROTATE:
deselectOnExit = false;
main.startSupportActionMode(new RotationActionModeCallback(manager));
break;
case MENUITEM_EXTRACT_SEGMENT:
extractSegment();
break;
default:
return false;
}
return true;
}

/**
* Extract a segment from way(s) between two nodes
*/
private void extractSegment() {
if (selection.size() == 2 && selection.get(0) instanceof Node && selection.get(1) instanceof Node) {
final Node node1 = (Node) selection.get(0);
final Node node2 = (Node) selection.get(1);
List<Way> commonWays = getWaysForNodes(node1, node2);
if (!commonWays.isEmpty()) {
if (commonWays.size() == 1) {
splitSafe(commonWays, extractSegment(commonWays, node1, node2));
} else {
DisambiguationMenu menu = new DisambiguationMenu(main.getMap());
menu.setHeaderTitle(R.string.select_way_to_extract_from);
int id = 0;
menu.add(id, Type.WAY, main.getString(R.string.split_all_ways),
(int position) -> splitSafe(commonWays, extractSegment(commonWays, node1, node2)));
id++;
for (Way w : commonWays) {
menu.add(id, Type.WAY, w.getDescription(main),
(int position) -> splitSafe(Util.wrapInList(w), extractSegment(Util.wrapInList(w), node1, node2)));
id++;
}
menu.show();
}
return;
}
}
Log.e(DEBUG_TAG, "extractSegment called but selection is invalid");

}

/**
* Check if the current selection are ways that can be intersected
*
Expand Down Expand Up @@ -338,6 +386,32 @@ private void mergePolygons() {
}
}

/**
* Get a list of all the Ways common to the two given Nodes.
*
* @param node1 the 1st Node
* @param node2 the 2nd Node
* @return A list of all Ways connected to both Nodes
*/
@NonNull
public List<Way> getWaysForNodes(@NonNull final Node node1, @NonNull final Node node2) {
List<Way> result = new ArrayList<>();
final Storage currentStorage = App.getDelegator().getCurrentStorage();
List<Way> ways1 = currentStorage.getWays(node1);
List<Way> ways2 = currentStorage.getWays(node2);
if (ways1.size() < ways2.size()) {
List<Way> temp = ways2;
ways2 = ways1;
ways1 = temp;
}
for (Way w : ways1) {
if (ways2.contains(w)) {
result.add(w);
}
}
return result;
}

/**
* Delete action
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,8 @@
import de.blau.android.App;
import de.blau.android.Logic;
import de.blau.android.R;
import de.blau.android.exception.OsmIllegalOperationException;
import de.blau.android.exception.StorageException;
import de.blau.android.osm.Node;
import de.blau.android.osm.OsmElement;
import de.blau.android.osm.Result;
import de.blau.android.osm.Tags;
import de.blau.android.osm.Way;
import de.blau.android.util.Geometry;
import de.blau.android.util.SerializableState;
Expand Down Expand Up @@ -117,21 +113,8 @@ public boolean handleElementClick(OsmElement element) { // due to clickableEleme
if (segmentNodes.length == 2) {
final Node n1 = segmentNodes[0];
final Node n2 = segmentNodes[1];
splitSafe(Util.wrapInList(way), () -> {
try {
List<Result> result = logic.performExtractSegment(main, way, n1, n2);
checkSplitResult(way, result);
Way segment = newWayFromSplitResult(result);
if (segment.hasTagKey(Tags.KEY_HIGHWAY) || segment.hasTagKey(Tags.KEY_WATERWAY)) {
main.startSupportActionMode(new WaySegmentModifyActionModeCallback(manager, segment));
} else {
main.startSupportActionMode(new WaySelectionActionModeCallback(manager, segment));
}
} catch (OsmIllegalOperationException | StorageException ex) {
// toast has already been displayed
manager.finish();
}
});
final List<Way> wayList = Util.wrapInList(way);
splitSafe(wayList, extractSegment(wayList, n1, n2));
}
return true;
}
Expand Down
Loading

0 comments on commit a5e20c9

Please sign in to comment.