Merge pull request #4262 from osmandapp/measurement_tools

Measurement tools
This commit is contained in:
Alexey 2017-08-04 15:10:20 +03:00 committed by GitHub
commit edb25fbd95
6 changed files with 343 additions and 43 deletions

View file

@ -43,11 +43,9 @@
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:background="@null"
android:paddingEnd="16dp"
android:paddingStart="16dp"
tools:src="@drawable/ic_action_arrow_down"/>
<TextView
@ -83,6 +81,28 @@
android:layout_height="1dp"
android:background="?attr/dashboard_divider"/>
<LinearLayout
android:id="@+id/points_list_container"
android:layout_width="match_parent"
android:layout_height="220dp"
android:background="@color/ctx_menu_info_view_bg_dark"
android:orientation="vertical"
android:visibility="gone">
<include layout="@layout/card_bottom_divider"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/points_recycler_view"
android:layout_width="match_parent"
android:layout_height="215dp"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="@drawable/bg_shadow_onmap"/>
</LinearLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
@ -94,11 +114,9 @@
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginStart="16dp"
android:background="@null"
android:paddingEnd="16dp"
android:paddingStart="16dp"
tools:src="@drawable/ic_action_undo_dark"/>
<ImageButton
@ -106,11 +124,11 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:layout_toEndOf="@id/undo_point_button"
android:layout_toRightOf="@id/undo_point_button"
android:background="@null"
android:paddingEnd="8dp"
android:paddingStart="8dp"
tools:src="@drawable/ic_action_redo_dark"/>
<Button

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/gpx_name_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:inputType="text"/>
<TextView
android:id="@+id/file_exists_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:gravity="center_horizontal"
android:text="@string/file_with_name_already_exists"
android:textColor="@color/marker_red"
android:visibility="invisible"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:background="?attr/dashboard_divider"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<android.support.v7.widget.SwitchCompat
android:id="@+id/toggle_show_on_map"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:layout_marginStart="16dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toLeftOf="@id/toggle_row_toggle"
android:layout_toStartOf="@id/toggle_row_toggle"
android:text="@string/show_on_map_after_saving"/>
</RelativeLayout>
</LinearLayout>

View file

@ -9,8 +9,11 @@
3. All your modified/created strings are in the top of the file (to make easier find what\'s translated).
PLEASE: Have a look at http://code.google.com/p/osmand/wiki/UIConsistency, it may really improve your and our work :-) Thx - Hardy
-->
<string name="measurement_tool_action_bar">Select a location on the map and click "Add" to add a point to the ruler.</string>
<string name="measurement_tool">Measurement tool</string>
<string name="none_point_error">You need to add at least one point.</string>
<string name="enter_gpx_name">Enter name for GPX</string>
<string name="show_on_map_after_saving">Show on map after saving</string>
<string name="measurement_tool_action_bar">Browse map and add points to a line</string>
<string name="measurement_tool">Measure distance</string>
<string name="quick_action_resume_pause_navigation">Resume/Pause Navigation</string>
<string name="quick_action_resume_pause_navigation_descr">Press this button to pause the navigation, or to resume it if it was already paused.</string>
<string name="quick_action_show_navigation_finish_dialog">Show Finish navigation dialog</string>

View file

@ -696,21 +696,6 @@ public class MapActivityActions implements DialogProvider {
}
}).createItem());
optionsMenuHelper.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.measurement_tool, mapActivity)
.setIcon(R.drawable.ic_action_ruler)
.setListener(new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int position, boolean isChecked) {
MeasurementToolFragment fragment = new MeasurementToolFragment();
mapActivity.getSupportFragmentManager()
.beginTransaction()
.add(R.id.bottomFragmentContainer, fragment, MeasurementToolFragment.TAG)
.addToBackStack(MeasurementToolFragment.TAG)
.commitAllowingStateLoss();
return true;
}
}).createItem());
optionsMenuHelper.addItem(new ItemBuilder().setTitleId(R.string.get_directions, mapActivity)
.setIcon(R.drawable.ic_action_gdirections_dark)
.setListener(new ContextMenuAdapter.ItemClickListener() {
@ -774,6 +759,21 @@ public class MapActivityActions implements DialogProvider {
}).createItem());
*/
optionsMenuHelper.addItem(new ContextMenuItem.ItemBuilder().setTitleId(R.string.measurement_tool, mapActivity)
.setIcon(R.drawable.ic_action_ruler)
.setListener(new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int itemId, int position, boolean isChecked) {
MeasurementToolFragment fragment = new MeasurementToolFragment();
mapActivity.getSupportFragmentManager()
.beginTransaction()
.add(R.id.bottomFragmentContainer, fragment, MeasurementToolFragment.TAG)
.addToBackStack(MeasurementToolFragment.TAG)
.commitAllowingStateLoss();
return true;
}
}).createItem());
optionsMenuHelper.addItem(new ItemBuilder().setTitleId(R.string.prefs_plugins, mapActivity)
.setIcon(R.drawable.ic_extension_dark)
.setListener(new ItemClickListener() {
@ -843,16 +843,16 @@ public class MapActivityActions implements DialogProvider {
//////////// Others
OsmandPlugin.registerOptionsMenu(mapActivity, optionsMenuHelper);
int pluginsItemIndex = -1;
int measureDistanceItemIndex = -1;
for (int i = 0; i < optionsMenuHelper.length(); i++) {
if (optionsMenuHelper.getItem(i).getTitleId() == R.string.prefs_plugins) {
pluginsItemIndex = i;
if (optionsMenuHelper.getItem(i).getTitleId() == R.string.measurement_tool) {
measureDistanceItemIndex = i;
break;
}
}
ItemBuilder divider = new ItemBuilder().setLayout(R.layout.drawer_divider);
divider.setPosition(pluginsItemIndex >= 0 ? pluginsItemIndex : 7);
divider.setPosition(measureDistanceItemIndex >= 0 ? measureDistanceItemIndex : 8);
optionsMenuHelper.addItem(divider.createItem());
getMyApplication().getAppCustomization().prepareOptionsMenu(mapActivity, optionsMenuHelper);

View file

@ -1,20 +1,34 @@
package net.osmand.plus.measurementtool;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.SwitchCompat;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.plus.GPXUtilities;
import net.osmand.plus.GPXUtilities.Route;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.IconsCache;
import net.osmand.plus.R;
import net.osmand.plus.activities.MapActivity;
@ -23,6 +37,16 @@ import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory;
import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController;
import net.osmand.plus.widgets.IconPopupMenu;
import java.io.File;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedList;
import java.util.Locale;
import static net.osmand.plus.GPXUtilities.GPXFile;
import static net.osmand.plus.helpers.GpxImportHelper.GPX_SUFFIX;
public class MeasurementToolFragment extends Fragment {
public static final String TAG = "MeasurementToolFragment";
@ -33,15 +57,18 @@ public class MeasurementToolFragment extends Fragment {
private String pointsSt;
private boolean wasCollapseButtonVisible;
private boolean pointsDetailsOpened;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
final MapActivity mapActivity = (MapActivity) getActivity();
final MeasurementToolLayer measurementLayer = mapActivity.getMapLayers().getMeasurementToolLayer();
IconsCache iconsCache = mapActivity.getMyApplication().getIconsCache();
final IconsCache iconsCache = mapActivity.getMyApplication().getIconsCache();
final boolean nightMode = mapActivity.getMyApplication().getDaynightHelper().isNightModeForMapControls();
final int themeRes = nightMode ? R.style.OsmandDarkTheme : R.style.OsmandLightTheme;
final int backgroundColor = ContextCompat.getColor(getActivity(),
nightMode ? R.color.ctx_menu_info_view_bg_dark : R.color.ctx_menu_info_view_bg_light);
boolean portrait = AndroidUiHelper.isOrientationPortrait(mapActivity);
pointsSt = mapActivity.getString(R.string.points).toLowerCase();
@ -50,6 +77,7 @@ public class MeasurementToolFragment extends Fragment {
final View mainView = view.findViewById(R.id.main_view);
AndroidUtils.setBackground(mapActivity, mainView, nightMode, R.drawable.bg_bottom_menu_light, R.drawable.bg_bottom_menu_dark);
view.findViewById(R.id.points_list_container).setBackgroundColor(backgroundColor);
distanceTv = (TextView) mainView.findViewById(R.id.measurement_distance_text_view);
pointsTv = (TextView) mainView.findViewById(R.id.measurement_points_text_view);
@ -62,7 +90,11 @@ public class MeasurementToolFragment extends Fragment {
upDownBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getActivity(), "Up / Down", Toast.LENGTH_SHORT).show();
if (!pointsDetailsOpened) {
upBtnOnClick(mainView, iconsCache.getThemedIcon(R.drawable.ic_action_arrow_down));
} else {
downBtnOnClick(mainView, iconsCache.getThemedIcon(R.drawable.ic_action_arrow_up));
}
}
});
@ -101,13 +133,13 @@ public class MeasurementToolFragment extends Fragment {
@Override
public void onClick(View view) {
measurementLayer.addPointOnClick();
enable(undoBtn);
enable(undoBtn, upDownBtn);
disable(redoBtn);
updateText();
}
});
disable(undoBtn, redoBtn);
disable(undoBtn, redoBtn, upDownBtn);
enterMeasurementMode();
@ -134,11 +166,15 @@ public class MeasurementToolFragment extends Fragment {
public boolean onMenuItemClick(MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.action_save_as_gpx:
Toast.makeText(mapActivity, "Save as gpx", Toast.LENGTH_SHORT).show();
if (measurementLayer.getPointsCount() > 0) {
saveAsGpxOnClick(mapActivity);
} else {
Toast.makeText(mapActivity, mapActivity.getString(R.string.none_point_error), Toast.LENGTH_SHORT).show();
}
return true;
case R.id.action_clear_all:
measurementLayer.clearPoints();
disable(undoBtn, redoBtn);
disable(undoBtn, redoBtn, upDownBtn);
updateText();
return true;
}
@ -154,6 +190,148 @@ public class MeasurementToolFragment extends Fragment {
return view;
}
private void upBtnOnClick(View view, Drawable icon) {
pointsDetailsOpened = true;
view.findViewById(R.id.points_list_container).setVisibility(View.VISIBLE);
((ImageButton) view.findViewById(R.id.up_down_button)).setImageDrawable(icon);
}
private void downBtnOnClick(View view, Drawable icon) {
pointsDetailsOpened = false;
view.findViewById(R.id.points_list_container).setVisibility(View.GONE);
((ImageButton) view.findViewById(R.id.up_down_button)).setImageDrawable(icon);
}
private void saveAsGpxOnClick(MapActivity mapActivity) {
final File dir = mapActivity.getMyApplication().getAppPath(IndexConstants.GPX_INDEX_DIR);
final LayoutInflater inflater = getLayoutInflater();
final View view = inflater.inflate(R.layout.save_gpx_dialog, null);
final EditText nameEt = (EditText) view.findViewById(R.id.gpx_name_et);
final TextView fileExistsTv = (TextView) view.findViewById(R.id.file_exists_text_view);
final SwitchCompat showOnMapToggle = (SwitchCompat) view.findViewById(R.id.toggle_show_on_map);
showOnMapToggle.setChecked(true);
final String suggestedName = new SimpleDateFormat("yyyy-M-dd_HH-mm_EEE", Locale.US).format(new Date());
String displayedName = suggestedName;
File fout = new File(dir, suggestedName + GPX_SUFFIX);
int ind = 1;
while (fout.exists()) {
displayedName = suggestedName + "_" + (++ind);
fout = new File(dir, displayedName + GPX_SUFFIX);
}
nameEt.setText(displayedName);
nameEt.setSelection(displayedName.length());
final boolean[] textChanged = new boolean[1];
nameEt.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (new File(dir, editable.toString() + GPX_SUFFIX).exists()) {
fileExistsTv.setVisibility(View.VISIBLE);
} else {
fileExistsTv.setVisibility(View.INVISIBLE);
}
textChanged[0] = true;
}
});
new AlertDialog.Builder(mapActivity)
.setTitle(R.string.enter_gpx_name)
.setView(view)
.setPositiveButton(R.string.shared_string_save, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
final String name = nameEt.getText().toString();
String fileName = name + GPX_SUFFIX;
if (textChanged[0]) {
File fout = new File(dir, fileName);
int ind = 1;
while (fout.exists()) {
fileName = name + "_" + (++ind) + GPX_SUFFIX;
fout = new File(dir, fileName);
}
}
saveGpx(dir, fileName, showOnMapToggle.isChecked());
}
})
.setNegativeButton(R.string.shared_string_cancel, null)
.show();
}
private void saveGpx(final File dir, final String fileName, final boolean showOnMap) {
new AsyncTask<Void, Void, String>() {
private ProgressDialog progressDialog;
private File toSave;
@Override
protected void onPreExecute() {
MapActivity activity = getMapActivity();
if (activity != null) {
progressDialog = new ProgressDialog(activity);
progressDialog.setMessage(activity.getString(R.string.saving_gpx_tracks));
progressDialog.show();
}
}
@Override
protected String doInBackground(Void... voids) {
toSave = new File(dir, fileName);
GPXFile gpx = new GPXFile();
MeasurementToolLayer measurementLayer = getMeasurementLayer();
if (measurementLayer != null) {
LinkedList<WptPt> points = measurementLayer.getMeasurementPoints();
if (points.size() == 1) {
gpx.points.add(points.getFirst());
} else if (points.size() > 1) {
Route rt = new Route();
gpx.routes.add(rt);
rt.points.addAll(points);
}
}
MapActivity activity = getMapActivity();
if (activity != null) {
// todo
String res = GPXUtilities.writeGpxFile(toSave, gpx, activity.getMyApplication());
gpx.path = toSave.getAbsolutePath();
if (showOnMap) {
activity.getMyApplication().getSelectedGpxHelper().selectGpxFile(gpx, true, false);
}
return res;
}
return null;
}
@Override
protected void onPostExecute(String warning) {
MapActivity activity = getMapActivity();
if (activity != null) {
if (warning == null) {
Toast.makeText(activity,
MessageFormat.format(activity.getString(R.string.gpx_saved_sucessfully), toSave.getAbsolutePath()),
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, warning, Toast.LENGTH_LONG).show();
}
activity.refreshMap();
}
if (progressDialog != null && progressDialog.isShowing()) {
progressDialog.dismiss();
}
}
}.execute();
}
@Override
public void onDestroyView() {
super.onDestroyView();

View file

@ -5,27 +5,31 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import net.osmand.data.LatLon;
import net.osmand.data.PointDescription;
import net.osmand.data.QuadPoint;
import net.osmand.data.RotatedTileBox;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.R;
import net.osmand.plus.views.ContextMenuLayer;
import net.osmand.plus.views.OsmandMapLayer;
import net.osmand.plus.views.OsmandMapTileView;
import net.osmand.util.MapUtils;
import java.util.LinkedList;
import java.util.List;
import gnu.trove.list.array.TIntArrayList;
public class MeasurementToolLayer extends OsmandMapLayer {
public class MeasurementToolLayer extends OsmandMapLayer implements ContextMenuLayer.IContextMenuProvider {
private OsmandMapTileView view;
private boolean inMeasurementMode;
private LinkedList<WptPt> measurementPoints = new LinkedList<>();
private LinkedList<WptPt> cacheMeasurementPoints;
private LinkedList<WptPt> cacheMeasurementPoints = new LinkedList<>();
private Bitmap centerIconDay;
private Bitmap centerIconNight;
private Bitmap pointIcon;
@ -66,6 +70,10 @@ public class MeasurementToolLayer extends OsmandMapLayer {
return measurementPoints.size();
}
public LinkedList<WptPt> getMeasurementPoints() {
return measurementPoints;
}
String getDistanceSt() {
float dist = 0;
if (measurementPoints.size() > 0) {
@ -104,16 +112,19 @@ public class MeasurementToolLayer extends OsmandMapLayer {
}
tx.add(locX);
ty.add(locY);
if (tb.containsLatLon(pt.lat, pt.lon)) {
canvas.drawBitmap(pointIcon, locX - marginX, locY - marginY, bitmapPaint);
}
}
path.lineTo(tb.getCenterPixelX(), tb.getCenterPixelY());
tx.add(tb.getCenterPixelX());
ty.add(tb.getCenterPixelY());
calculatePath(tb, tx, ty, path);
canvas.drawPath(path, lineAttrs.paint);
for (WptPt pt : measurementPoints) {
if (tb.containsLatLon(pt.lat, pt.lon)) {
int locX = tb.getPixXFromLonNoRot(pt.lon);
int locY = tb.getPixYFromLatNoRot(pt.lat);
canvas.drawBitmap(pointIcon, locX - marginX, locY - marginY, bitmapPaint);
}
}
}
}
}
@ -170,4 +181,34 @@ public class MeasurementToolLayer extends OsmandMapLayer {
public boolean drawInScreenPixels() {
return false;
}
@Override
public void collectObjectsFromPoint(PointF point, RotatedTileBox tileBox, List<Object> o) {
}
@Override
public LatLon getObjectLocation(Object o) {
return null;
}
@Override
public PointDescription getObjectName(Object o) {
return null;
}
@Override
public boolean disableSingleTap() {
return isInMeasurementMode();
}
@Override
public boolean disableLongPressOnMap() {
return isInMeasurementMode();
}
@Override
public boolean isObjectClickable(Object o) {
return !isInMeasurementMode();
}
}