Added split / join segments feature to plan route

This commit is contained in:
max-klaus 2020-12-05 19:53:54 +03:00
parent c091fb6cc0
commit 53de5bf80e
8 changed files with 341 additions and 45 deletions

View file

@ -11,6 +11,10 @@
Thx - Hardy Thx - Hardy
--> -->
<string name="plan_route_add_new_segment">Add new segment</string>
<string name="plan_route_split_after">Split after</string>
<string name="plan_route_split_before">Split before</string>
<string name="plan_route_join_segments">Join segments</string>
<string name="app_mode_light_aircraft">Light aircraft</string> <string name="app_mode_light_aircraft">Light aircraft</string>
<string name="elevation_data">You can use Elevation data for consideration of Ascent / Descent for your trip</string> <string name="elevation_data">You can use Elevation data for consideration of Ascent / Descent for your trip</string>
<string name="ltr_or_rtl_combine_via_star">%1$s * %2$s</string> <string name="ltr_or_rtl_combine_via_star">%1$s * %2$s</string>

View file

@ -296,31 +296,6 @@ public class MeasurementEditingContext {
return !newData && hasDefaultPointsOnly && getPoints().size() > 2; return !newData && hasDefaultPointsOnly && getPoints().size() > 2;
} }
public boolean isSelectionNeedApproximation() {
boolean hasDefaultPointsOnly = false;
boolean newData = isNewData();
if (!newData && selectedPointPosition != -1) {
WptPt selectedPoint = getPoints().get(selectedPointPosition);
List<TrkSegment> segments = getBeforeSegments();
List<WptPt> points = null;
for (TrkSegment segment : segments) {
if (segment.points.contains(selectedPoint)) {
points = segment.points;
}
}
if (!Algorithms.isEmpty(points)) {
hasDefaultPointsOnly = true;
for (WptPt point : points) {
if (point.hasProfile()) {
hasDefaultPointsOnly = false;
break;
}
}
}
}
return !newData && hasDefaultPointsOnly && getPoints().size() > 2;
}
public void clearSnappedToRoadPoints() { public void clearSnappedToRoadPoints() {
roadSegmentData.clear(); roadSegmentData.clear();
} }
@ -430,13 +405,19 @@ public class MeasurementEditingContext {
if (position > 0 && position <= points.size()) { if (position > 0 && position <= points.size()) {
WptPt prevPt = points.get(position - 1); WptPt prevPt = points.get(position - 1);
if (prevPt.isGap()) { if (prevPt.isGap()) {
point.setGap(); if (position == points.size() && getAfterPoints().size() == 0) {
if (position > 1) { if (appMode != MeasurementEditingContext.DEFAULT_APP_MODE) {
WptPt pt = points.get(position - 2); point.setProfileType(appMode.getStringKey());
if (pt.hasProfile()) { }
prevPt.setProfileType(pt.getProfileType()); } else {
} else { point.setGap();
prevPt.removeProfileType(); if (position > 1) {
WptPt pt = points.get(position - 2);
if (pt.hasProfile()) {
prevPt.setProfileType(pt.getProfileType());
} else {
prevPt.removeProfileType();
}
} }
} }
} else if (prevPt.hasProfile()) { } else if (prevPt.hasProfile()) {
@ -540,6 +521,45 @@ public class MeasurementEditingContext {
clearAfterSegments(); clearAfterSegments();
} }
public void splitPoints(int selectedPointPosition, boolean after) {
int pointIndex = after ? selectedPointPosition : selectedPointPosition - 1;
if (pointIndex >= 0 && pointIndex < before.points.size()) {
WptPt point = before.points.get(pointIndex);
WptPt newPoint = new WptPt();
newPoint.lat = point.lat;
newPoint.lon = point.lon;
newPoint.copyExtensions(point);
newPoint.setGap();
before.points.remove(pointIndex);
before.points.add(pointIndex, newPoint);
updateSegmentsForSnap(false);
}
}
public void joinPoints(int selectedPointPosition) {
WptPt gapPoint = null;
int gapIndex = -1;
if (isFirstPointSelected(selectedPointPosition, false)) {
if (selectedPointPosition - 1 >= 0) {
gapPoint = before.points.get(selectedPointPosition - 1);
gapIndex = selectedPointPosition - 1;
}
} else if (isLastPointSelected(selectedPointPosition, false)) {
gapPoint = before.points.get(selectedPointPosition);
gapIndex = selectedPointPosition;
}
if (gapPoint != null) {
WptPt newPoint = new WptPt();
newPoint.lat = gapPoint.lat;
newPoint.lon = gapPoint.lon;
newPoint.copyExtensions(gapPoint);
newPoint.removeProfileType();
before.points.remove(gapIndex);
before.points.add(gapIndex, newPoint);
updateSegmentsForSnap(false);
}
}
public void clearSegments() { public void clearSegments() {
clearBeforeSegments(); clearBeforeSegments();
clearAfterSegments(); clearAfterSegments();
@ -560,15 +580,43 @@ public class MeasurementEditingContext {
} }
} }
public boolean isFirstPointSelected() { public boolean canSplit(boolean after) {
return isBorderPointSelected(true); WptPt selectedPoint = getPoints().get(selectedPointPosition);
List<TrkSegment> segments = getBeforeSegments();
for (TrkSegment segment : segments) {
int i = segment.points.indexOf(selectedPoint);
if (i != -1) {
return after ? i < segment.points.size() - 2 : i > 1;
}
}
return false;
} }
public boolean isLastPointSelected() { public boolean isFirstPointSelected(boolean outer) {
return isBorderPointSelected(false); return isFirstPointSelected(selectedPointPosition, outer);
} }
private boolean isBorderPointSelected(boolean first) { public boolean isFirstPointSelected(int selectedPointPosition, boolean outer) {
if (outer) {
return selectedPointPosition == 0;
} else {
return isBorderPointSelected(selectedPointPosition, true);
}
}
public boolean isLastPointSelected(boolean outer) {
return isLastPointSelected(selectedPointPosition, outer);
}
public boolean isLastPointSelected(int selectedPointPosition, boolean outer) {
if (outer) {
return selectedPointPosition == getPoints().size() - 1;
} else {
return isBorderPointSelected(selectedPointPosition, false);
}
}
private boolean isBorderPointSelected(int selectedPointPosition, boolean first) {
WptPt selectedPoint = getPoints().get(selectedPointPosition); WptPt selectedPoint = getPoints().get(selectedPointPosition);
List<TrkSegment> segments = getBeforeSegments(); List<TrkSegment> segments = getBeforeSegments();
int count = 0; int count = 0;

View file

@ -64,10 +64,12 @@ import net.osmand.plus.measurementtool.command.ApplyGpxApproximationCommand;
import net.osmand.plus.measurementtool.command.ChangeRouteModeCommand; import net.osmand.plus.measurementtool.command.ChangeRouteModeCommand;
import net.osmand.plus.measurementtool.command.ChangeRouteModeCommand.ChangeRouteType; import net.osmand.plus.measurementtool.command.ChangeRouteModeCommand.ChangeRouteType;
import net.osmand.plus.measurementtool.command.ClearPointsCommand; import net.osmand.plus.measurementtool.command.ClearPointsCommand;
import net.osmand.plus.measurementtool.command.JoinPointsCommand;
import net.osmand.plus.measurementtool.command.MovePointCommand; import net.osmand.plus.measurementtool.command.MovePointCommand;
import net.osmand.plus.measurementtool.command.RemovePointCommand; import net.osmand.plus.measurementtool.command.RemovePointCommand;
import net.osmand.plus.measurementtool.command.ReorderPointCommand; import net.osmand.plus.measurementtool.command.ReorderPointCommand;
import net.osmand.plus.measurementtool.command.ReversePointsCommand; import net.osmand.plus.measurementtool.command.ReversePointsCommand;
import net.osmand.plus.measurementtool.command.SplitPointsCommand;
import net.osmand.plus.routepreparationmenu.cards.BaseCard; import net.osmand.plus.routepreparationmenu.cards.BaseCard;
import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.ApplicationMode;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
@ -76,8 +78,8 @@ import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControll
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarView; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarView;
import net.osmand.plus.widgets.MultiStateToggleButton; import net.osmand.plus.widgets.MultiStateToggleButton;
import net.osmand.plus.widgets.MultiStateToggleButton.RadioItem;
import net.osmand.plus.widgets.MultiStateToggleButton.OnRadioItemClickListener; import net.osmand.plus.widgets.MultiStateToggleButton.OnRadioItemClickListener;
import net.osmand.plus.widgets.MultiStateToggleButton.RadioItem;
import net.osmand.router.RoutePlannerFrontEnd.GpxRouteApproximation; import net.osmand.router.RoutePlannerFrontEnd.GpxRouteApproximation;
import net.osmand.util.Algorithms; import net.osmand.util.Algorithms;
@ -1012,6 +1014,39 @@ public class MeasurementToolFragment extends BaseOsmAndFragment implements Route
trimRoute(AFTER); trimRoute(AFTER);
} }
@Override
public void onSplitPointsAfter() {
MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new SplitPointsCommand(measurementLayer, true));
collapseInfoViewIfExpanded();
editingCtx.setSelectedPointPosition(-1);
updateUndoRedoButton(false, redoBtn);
updateUndoRedoButton(true, undoBtn);
updateDistancePointsText();
}
@Override
public void onSplitPointsBefore() {
MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new SplitPointsCommand(measurementLayer, false));
collapseInfoViewIfExpanded();
editingCtx.setSelectedPointPosition(-1);
updateUndoRedoButton(false, redoBtn);
updateUndoRedoButton(true, undoBtn);
updateDistancePointsText();
}
@Override
public void onJoinPoints() {
MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new JoinPointsCommand(measurementLayer));
collapseInfoViewIfExpanded();
editingCtx.setSelectedPointPosition(-1);
updateUndoRedoButton(false, redoBtn);
updateUndoRedoButton(true, undoBtn);
updateDistancePointsText();
}
private void trimRoute(ClearCommandMode before) { private void trimRoute(ClearCommandMode before) {
MeasurementToolLayer measurementLayer = getMeasurementLayer(); MeasurementToolLayer measurementLayer = getMeasurementLayer();
editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, before)); editingCtx.getCommandManager().execute(new ClearPointsCommand(measurementLayer, before));

View file

@ -328,7 +328,7 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
hasPointsBefore = true; hasPointsBefore = true;
WptPt pt = segment.points.get(segment.points.size() - 1); WptPt pt = segment.points.get(segment.points.size() - 1);
hasGapBefore = pt.isGap(); hasGapBefore = pt.isGap();
if (!pt.isGap() || !editingCtx.isInAddPointBeforeMode()) { if (!pt.isGap() || (editingCtx.isInAddPointMode() && !editingCtx.isInAddPointBeforeMode())) {
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon); float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon); float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);
tx.add(locX); tx.add(locX);
@ -345,7 +345,7 @@ public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuL
tx.add((float) tb.getCenterPixelX()); tx.add((float) tb.getCenterPixelX());
ty.add((float) tb.getCenterPixelY()); ty.add((float) tb.getCenterPixelY());
} }
if (!hasGapBefore || editingCtx.isInAddPointBeforeMode()) { if (!hasGapBefore || (editingCtx.isInAddPointMode() && editingCtx.isInAddPointBeforeMode())) {
WptPt pt = segment.points.get(0); WptPt pt = segment.points.get(0);
float locX = tb.getPixXFromLatLon(pt.lat, pt.lon); float locX = tb.getPixXFromLatLon(pt.lat, pt.lon);
float locY = tb.getPixYFromLatLon(pt.lat, pt.lon); float locY = tb.getPixYFromLatLon(pt.lat, pt.lon);

View file

@ -133,7 +133,7 @@ public class SelectedPointBottomSheetDialogFragment extends MenuBottomSheetDialo
dismiss(); dismiss();
} }
}) })
.setDisabled(editingCtx.isFirstPointSelected()) .setDisabled(editingCtx.isFirstPointSelected(false))
.create(); .create();
items.add(trimRouteBefore); items.add(trimRouteBefore);
@ -152,10 +152,93 @@ public class SelectedPointBottomSheetDialogFragment extends MenuBottomSheetDialo
dismiss(); dismiss();
} }
}) })
.setDisabled(editingCtx.isLastPointSelected()) .setDisabled(editingCtx.isLastPointSelected(false))
.create(); .create();
items.add(trimRouteAfter); items.add(trimRouteAfter);
if (editingCtx.isFirstPointSelected(true)) {
// skip
} else if (editingCtx.isLastPointSelected(true)) {
items.add(new OptionsDividerItem(getContext()));
// new segment
BaseBottomSheetItem addNewSegment = new BottomSheetItemWithDescription.Builder()
//.setIcon(getContentIcon(R.drawable.ic_action_trim_right))
.setTitle(getString(R.string.plan_route_add_new_segment))
.setLayoutId(R.layout.bottom_sheet_item_with_descr_pad_32dp)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Fragment targetFragment = getTargetFragment();
if (targetFragment instanceof SelectedPointFragmentListener) {
((SelectedPointFragmentListener) targetFragment).onSplitPointsAfter();
}
dismiss();
}
})
.create();
items.add(addNewSegment);
} else if (editingCtx.isFirstPointSelected(false) || editingCtx.isLastPointSelected(false)) {
items.add(new OptionsDividerItem(getContext()));
// join
BaseBottomSheetItem joinSegments = new BottomSheetItemWithDescription.Builder()
//.setIcon(getContentIcon(R.drawable.ic_action_trim_right))
.setTitle(getString(R.string.plan_route_join_segments))
.setLayoutId(R.layout.bottom_sheet_item_with_descr_pad_32dp)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Fragment targetFragment = getTargetFragment();
if (targetFragment instanceof SelectedPointFragmentListener) {
((SelectedPointFragmentListener) targetFragment).onJoinPoints();
}
dismiss();
}
})
.create();
items.add(joinSegments);
} else {
items.add(new OptionsDividerItem(getContext()));
// split
BaseBottomSheetItem splitAfter = new BottomSheetItemWithDescription.Builder()
//.setIcon(getContentIcon(R.drawable.ic_action_trim_right))
.setTitle(getString(R.string.plan_route_split_after))
.setLayoutId(R.layout.bottom_sheet_item_with_descr_pad_32dp)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Fragment targetFragment = getTargetFragment();
if (targetFragment instanceof SelectedPointFragmentListener) {
((SelectedPointFragmentListener) targetFragment).onSplitPointsAfter();
}
dismiss();
}
})
.setDisabled(!editingCtx.canSplit(true))
.create();
items.add(splitAfter);
BaseBottomSheetItem splitBefore = new BottomSheetItemWithDescription.Builder()
//.setIcon(getContentIcon(R.drawable.ic_action_trim_right))
.setTitle(getString(R.string.plan_route_split_before))
.setLayoutId(R.layout.bottom_sheet_item_with_descr_pad_32dp)
.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Fragment targetFragment = getTargetFragment();
if (targetFragment instanceof SelectedPointFragmentListener) {
((SelectedPointFragmentListener) targetFragment).onSplitPointsBefore();
}
dismiss();
}
})
.setDisabled(!editingCtx.canSplit(false))
.create();
items.add(splitBefore);
}
items.add(new OptionsDividerItem(getContext())); items.add(new OptionsDividerItem(getContext()));
BaseBottomSheetItem changeRouteTypeBefore = new BottomSheetItemWithDescription.Builder() BaseBottomSheetItem changeRouteTypeBefore = new BottomSheetItemWithDescription.Builder()
@ -172,7 +255,7 @@ public class SelectedPointBottomSheetDialogFragment extends MenuBottomSheetDialo
dismiss(); dismiss();
} }
}) })
.setDisabled(editingCtx.isFirstPointSelected() || editingCtx.isSelectionNeedApproximation()) .setDisabled(editingCtx.isFirstPointSelected(false) || editingCtx.isApproximationNeeded())
.create(); .create();
items.add(changeRouteTypeBefore); items.add(changeRouteTypeBefore);
@ -190,7 +273,7 @@ public class SelectedPointBottomSheetDialogFragment extends MenuBottomSheetDialo
dismiss(); dismiss();
} }
}) })
.setDisabled(editingCtx.isLastPointSelected() || editingCtx.isSelectionNeedApproximation()) .setDisabled(editingCtx.isLastPointSelected(false) || editingCtx.isApproximationNeeded())
.create(); .create();
items.add(changeRouteTypeAfter); items.add(changeRouteTypeAfter);
@ -352,6 +435,12 @@ public class SelectedPointBottomSheetDialogFragment extends MenuBottomSheetDialo
void onTrimRouteAfter(); void onTrimRouteAfter();
void onSplitPointsAfter();
void onSplitPointsBefore();
void onJoinPoints();
void onChangeRouteTypeBefore(); void onChangeRouteTypeBefore();
void onChangeRouteTypeAfter(); void onChangeRouteTypeAfter();

View file

@ -0,0 +1,58 @@
package net.osmand.plus.measurementtool.command;
import android.util.Pair;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.plus.measurementtool.MeasurementEditingContext;
import net.osmand.plus.measurementtool.MeasurementEditingContext.RoadSegmentData;
import net.osmand.plus.measurementtool.MeasurementToolLayer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class JoinPointsCommand extends MeasurementModeCommand {
private List<WptPt> points;
private Map<Pair<WptPt, WptPt>, RoadSegmentData> roadSegmentData;
private int pointPosition;
public JoinPointsCommand(MeasurementToolLayer measurementLayer) {
super(measurementLayer);
}
@Override
public boolean execute() {
pointPosition = getEditingCtx().getSelectedPointPosition();
executeCommand();
return true;
}
private void executeCommand() {
MeasurementEditingContext ctx = getEditingCtx();
List<WptPt> pts = ctx.getPoints();
points = new ArrayList<>(pts);
roadSegmentData = ctx.getRoadSegmentData();
ctx.joinPoints(pointPosition);
refreshMap();
}
@Override
public void undo() {
MeasurementEditingContext ctx = getEditingCtx();
ctx.clearSegments();
ctx.setRoadSegmentData(roadSegmentData);
ctx.addPoints(points);
refreshMap();
}
@Override
public void redo() {
executeCommand();
}
@Override
public MeasurementCommandType getType() {
return MeasurementCommandType.JOIN_POINTS;
}
}

View file

@ -41,6 +41,8 @@ public abstract class MeasurementModeCommand implements Command {
SNAP_TO_ROAD, SNAP_TO_ROAD,
CHANGE_ROUTE_MODE, CHANGE_ROUTE_MODE,
APPROXIMATE_POINTS, APPROXIMATE_POINTS,
REVERSE_POINTS REVERSE_POINTS,
SPLIT_POINTS,
JOIN_POINTS
} }
} }

View file

@ -0,0 +1,60 @@
package net.osmand.plus.measurementtool.command;
import android.util.Pair;
import net.osmand.GPXUtilities.WptPt;
import net.osmand.plus.measurementtool.MeasurementEditingContext;
import net.osmand.plus.measurementtool.MeasurementEditingContext.RoadSegmentData;
import net.osmand.plus.measurementtool.MeasurementToolLayer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class SplitPointsCommand extends MeasurementModeCommand {
private boolean after;
private List<WptPt> points;
private Map<Pair<WptPt, WptPt>, RoadSegmentData> roadSegmentData;
private int pointPosition;
public SplitPointsCommand(MeasurementToolLayer measurementLayer, boolean after) {
super(measurementLayer);
this.after = after;
}
@Override
public boolean execute() {
pointPosition = getEditingCtx().getSelectedPointPosition();
executeCommand();
return true;
}
private void executeCommand() {
MeasurementEditingContext ctx = getEditingCtx();
List<WptPt> pts = ctx.getPoints();
points = new ArrayList<>(pts);
roadSegmentData = ctx.getRoadSegmentData();
ctx.splitPoints(pointPosition, after);
refreshMap();
}
@Override
public void undo() {
MeasurementEditingContext ctx = getEditingCtx();
ctx.clearSegments();
ctx.setRoadSegmentData(roadSegmentData);
ctx.addPoints(points);
refreshMap();
}
@Override
public void redo() {
executeCommand();
}
@Override
public MeasurementCommandType getType() {
return MeasurementCommandType.SPLIT_POINTS;
}
}