package net.osmand.plus.helpers;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.Application;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.AppCompatCheckBox;
import android.support.v7.widget.ListPopupWindow;
import android.support.v7.widget.SwitchCompat;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.MarkerView;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
import com.github.mikephil.charting.formatter.IFillFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.utils.MPPointF;
import net.osmand.AndroidUtils;
import net.osmand.CallbackWithObject;
import net.osmand.IndexConstants;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.GPXDatabase.GpxDataItem;
import net.osmand.plus.GPXUtilities;
import net.osmand.plus.GPXUtilities.Elevation;
import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.GPXUtilities.Speed;
import net.osmand.plus.GPXUtilities.TrkSegment;
import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile;
import net.osmand.plus.IconsCache;
import net.osmand.plus.OsmAndFormatter;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.OsmandSettings;
import net.osmand.plus.R;
import net.osmand.plus.activities.ActivityResultListener;
import net.osmand.plus.activities.ActivityResultListener.OnActivityResultListener;
import net.osmand.plus.activities.MapActivity;
import net.osmand.plus.activities.PluginActivity;
import net.osmand.plus.activities.SettingsActivity;
import net.osmand.plus.dialogs.ConfigureMapMenu;
import net.osmand.plus.dialogs.ConfigureMapMenu.AppearanceListItem;
import net.osmand.plus.dialogs.ConfigureMapMenu.GpxAppearanceAdapter;
import net.osmand.plus.download.ui.LocalIndexesFragment;
import net.osmand.plus.monitoring.OsmandMonitoringPlugin;
import net.osmand.plus.rastermaps.OsmandRasterMapsPlugin;
import net.osmand.render.RenderingRuleProperty;
import net.osmand.render.RenderingRulesStorage;
import net.osmand.util.Algorithms;
import net.osmand.util.MapUtils;
import java.io.File;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.github.mikephil.charting.components.XAxis.XAxisPosition.BOTTOM;
import static net.osmand.plus.OsmAndFormatter.FEET_IN_ONE_METER;
import static net.osmand.plus.OsmAndFormatter.METERS_IN_KILOMETER;
import static net.osmand.plus.OsmAndFormatter.METERS_IN_ONE_MILE;
import static net.osmand.plus.OsmAndFormatter.METERS_IN_ONE_NAUTICALMILE;
import static net.osmand.plus.OsmAndFormatter.YARDS_IN_ONE_METER;
import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_COLOR_ATTR;
import static net.osmand.plus.dialogs.ConfigureMapMenu.CURRENT_TRACK_WIDTH_ATTR;
import static net.osmand.plus.download.DownloadActivity.formatMb;
import static net.osmand.plus.download.DownloadActivity.formatKb;
public class GpxUiHelper {
private static final int OPEN_GPX_DOCUMENT_REQUEST = 1005;
public static String getDescription(OsmandApplication app, GPXFile result, File f, boolean html) {
GPXTrackAnalysis analysis = result.getAnalysis(f == null ? 0 : f.lastModified());
return getDescription(app, analysis, html);
}
public static String getDescription(OsmandApplication app, TrkSegment t, boolean html) {
return getDescription(app, GPXTrackAnalysis.segment(0, t), html);
}
public static String getColorValue(String clr, String value, boolean html) {
if (!html) {
return value;
}
return "<font color=\"" + clr + "\">" + value + "</font>";
}
public static String getColorValue(String clr, String value) {
return getColorValue(clr, value, true);
}
public static String getDescription(OsmandApplication app, GPXTrackAnalysis analysis, boolean html) {
StringBuilder description = new StringBuilder();
String nl = html ? "<br/>" : "\n";
String timeSpanClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_time_span_color));
String distanceClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_distance_color));
String speedClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_speed));
String ascClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_altitude_asc));
String descClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_altitude_desc));
// OUTPUT:
// 1. Total distance, Start time, End time
description.append(app.getString(R.string.gpx_info_distance, getColorValue(distanceClr,
OsmAndFormatter.getFormattedDistance(analysis.totalDistance, app), html),
getColorValue(distanceClr, analysis.points + "", html)));
if (analysis.totalTracks > 1) {
description.append(nl).append(app.getString(R.string.gpx_info_subtracks, getColorValue(speedClr, analysis.totalTracks + "", html)));
}
if (analysis.wptPoints > 0) {
description.append(nl).append(app.getString(R.string.gpx_info_waypoints, getColorValue(speedClr, analysis.wptPoints + "", html)));
}
if (analysis.isTimeSpecified()) {
description.append(nl).append(app.getString(R.string.gpx_info_start_time, analysis.startTime));
description.append(nl).append(app.getString(R.string.gpx_info_end_time, analysis.endTime));
}
// 2. Time span
if (analysis.timeSpan > 0 && analysis.timeSpan / 1000 != analysis.timeMoving / 1000) {
final String formatDuration = Algorithms.formatDuration((int) (analysis.timeSpan / 1000), app.accessibilityEnabled());
description.append(nl).append(app.getString(R.string.gpx_timespan,
getColorValue(timeSpanClr, formatDuration, html)));
}
// 3. Time moving, if any
if (analysis.isTimeMoving()) {
//Next few lines for Issue 3222 heuristic testing only
//final String formatDuration0 = Algorithms.formatDuration((int) (analysis.timeMoving0 / 1000), app.accessibilityEnabled());
//description.append(nl).append(app.getString(R.string.gpx_timemoving,
// getColorValue(timeSpanClr, formatDuration0, html)));
//description.append(" (" + getColorValue(distanceClr, OsmAndFormatter.getFormattedDistance(analysis.totalDistanceMoving0, app), html) + ")");
final String formatDuration = Algorithms.formatDuration((int) (analysis.timeMoving / 1000), app.accessibilityEnabled());
description.append(nl).append(app.getString(R.string.gpx_timemoving,
getColorValue(timeSpanClr, formatDuration, html)));
description.append(" (" + getColorValue(distanceClr, OsmAndFormatter.getFormattedDistance(analysis.totalDistanceMoving, app), html) + ")");
}
// 4. Elevation, eleUp, eleDown, if recorded
if (analysis.isElevationSpecified()) {
description.append(nl);
description.append(app.getString(R.string.gpx_info_avg_altitude,
getColorValue(speedClr, OsmAndFormatter.getFormattedAlt(analysis.avgElevation, app), html)));
description.append(nl);
String min = getColorValue(descClr, OsmAndFormatter.getFormattedAlt(analysis.minElevation, app), html);
String max = getColorValue(ascClr, OsmAndFormatter.getFormattedAlt(analysis.maxElevation, app), html);
String asc = getColorValue(ascClr, OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app), html);
String desc = getColorValue(descClr, OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app), html);
description.append(app.getString(R.string.gpx_info_diff_altitude, min + " - " + max));
description.append(nl);
description.append(app.getString(R.string.gpx_info_asc_altitude, "\u2193 " + desc + " \u2191 " + asc + ""));
}
if (analysis.isSpeedSpecified()) {
String avg = getColorValue(speedClr, OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app), html);
String max = getColorValue(ascClr, OsmAndFormatter.getFormattedSpeed(analysis.maxSpeed, app), html);
description.append(nl).append(app.getString(R.string.gpx_info_average_speed, avg));
description.append(nl).append(app.getString(R.string.gpx_info_maximum_speed, max));
}
return description.toString();
}
public static AlertDialog selectGPXFiles(List<String> selectedGpxList, final Activity activity,
final CallbackWithObject<GPXFile[]> callbackWithObject) {
OsmandApplication app = (OsmandApplication) activity.getApplication();
final File dir = app.getAppPath(IndexConstants.GPX_INDEX_DIR);
final List<GPXInfo> allGpxList = getSortedGPXFilesInfo(dir, selectedGpxList, false);
if (allGpxList.isEmpty()) {
Toast.makeText(activity, R.string.gpx_files_not_found, Toast.LENGTH_LONG).show();
}
allGpxList.add(0, new GPXInfo(activity.getString(R.string.show_current_gpx_title), 0, 0));
final ContextMenuAdapter adapter = createGpxContextMenuAdapter(allGpxList, selectedGpxList, true);
return createDialog(activity, true, true, true, callbackWithObject, allGpxList, adapter);
}
public static AlertDialog selectGPXFile(final Activity activity,
final boolean showCurrentGpx, final boolean multipleChoice, final CallbackWithObject<GPXFile[]> callbackWithObject) {
OsmandApplication app = (OsmandApplication) activity.getApplication();
final File dir = app.getAppPath(IndexConstants.GPX_INDEX_DIR);
final List<GPXInfo> list = getSortedGPXFilesInfo(dir, null, false);
if (list.isEmpty()) {
Toast.makeText(activity, R.string.gpx_files_not_found, Toast.LENGTH_LONG).show();
}
if (!list.isEmpty() || showCurrentGpx) {
if (showCurrentGpx) {
list.add(0, new GPXInfo(activity.getString(R.string.show_current_gpx_title), 0, 0));
}
final ContextMenuAdapter adapter = createGpxContextMenuAdapter(list, null, showCurrentGpx);
return createDialog(activity, showCurrentGpx, multipleChoice, false, callbackWithObject, list, adapter);
}
return null;
}
public static AlertDialog selectSingleGPXFile(final Activity activity, boolean showCurrentGpx,
final CallbackWithObject<GPXFile[]> callbackWithObject) {
OsmandApplication app = (OsmandApplication) activity.getApplication();
int gpxDirLength = app.getAppPath(IndexConstants.GPX_INDEX_DIR).getAbsolutePath().length();
List<SelectedGpxFile> selectedGpxFiles = app.getSelectedGpxHelper().getSelectedGPXFiles();
final List<GPXInfo> list = new ArrayList<>(selectedGpxFiles.size() + 1);
if (OsmandPlugin.getEnabledPlugin(OsmandMonitoringPlugin.class) == null) {
showCurrentGpx = false;
}
if (!selectedGpxFiles.isEmpty() || showCurrentGpx) {
if (showCurrentGpx) {
list.add(new GPXInfo(activity.getString(R.string.shared_string_currently_recording_track), 0, 0));
}
for (SelectedGpxFile selectedGpx : selectedGpxFiles) {
if (!selectedGpx.getGpxFile().showCurrentTrack) {
list.add(new GPXInfo(selectedGpx.getGpxFile().path.substring(gpxDirLength + 1), selectedGpx.getGpxFile().modifiedTime, 0));
}
}
final ContextMenuAdapter adapter = createGpxContextMenuAdapter(list, null, showCurrentGpx);
return createSingleChoiceDialog(activity, showCurrentGpx, callbackWithObject, list, adapter);
}
return null;
}
private static ContextMenuAdapter createGpxContextMenuAdapter(List<GPXInfo> allGpxList,
List<String> selectedGpxList,
boolean showCurrentTrack) {
final ContextMenuAdapter adapter = new ContextMenuAdapter();
//element position in adapter
int i = 0;
for (GPXInfo gpxInfo : allGpxList) {
String s = gpxInfo.getFileName();
String fileName = s;
if (s.endsWith(".gpx")) {
s = s.substring(0, s.length() - ".gpx".length());
}
s = s.replace('_', ' ');
adapter.addItem(ContextMenuItem.createBuilder(s).setSelected(false)
.setIcon(R.drawable.ic_action_polygom_dark).createItem());
//if there's some selected files - need to mark them as selected
if (selectedGpxList != null) {
updateSelection(selectedGpxList, showCurrentTrack, adapter, i, fileName);
}
i++;
}
return adapter;
}
protected static void updateSelection(List<String> selectedGpxList, boolean showCurrentTrack,
final ContextMenuAdapter adapter, int position, String fileName) {
ContextMenuItem item = adapter.getItem(position);
if (position == 0 && showCurrentTrack) {
if (selectedGpxList.contains("")) {
item.setSelected(true);
}
} else {
for (String file : selectedGpxList) {
if (file.endsWith(fileName)) {
item.setSelected(true);
break;
}
}
}
}
private static void setDescripionInDialog(final ArrayAdapter<?> adapter, final ContextMenuAdapter cmAdapter, Activity activity,
final File dir, String filename, final int position) {
final Application app = activity.getApplication();
final File f = new File(dir, filename);
loadGPXFileInDifferentThread(activity, new CallbackWithObject<GPXUtilities.GPXFile[]>() {
@Override
public boolean processResult(GPXFile[] result) {
ContextMenuItem item = cmAdapter.getItem(position);
item.setTitle(item.getTitle() + "\n" + getDescription((OsmandApplication) app, result[0], f, false));
adapter.notifyDataSetInvalidated();
return true;
}
}, dir, null, filename);
}
private static AlertDialog createSingleChoiceDialog(final Activity activity,
final boolean showCurrentGpx,
final CallbackWithObject<GPXFile[]> callbackWithObject,
final List<GPXInfo> list,
final ContextMenuAdapter adapter) {
final OsmandApplication app = (OsmandApplication) activity.getApplication();
final IconsCache iconsCache = app.getIconsCache();
final File dir = app.getAppPath(IndexConstants.GPX_INDEX_DIR);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final int layout = R.layout.list_menu_item_native_singlechoice;
final ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(activity, layout, R.id.text1,
adapter.getItemNames()) {
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// User super class to create the View
View v = convertView;
if (v == null) {
v = activity.getLayoutInflater().inflate(layout, null);
}
final ContextMenuItem item = adapter.getItem(position);
TextView tv = (TextView) v.findViewById(R.id.text1);
Drawable icon;
if (showCurrentGpx && position == 0) {
icon = null;
} else {
icon = iconsCache.getThemedIcon(item.getIcon());
}
tv.setCompoundDrawablePadding(AndroidUtils.dpToPx(activity, 10f));
tv.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
tv.setText(item.getTitle());
tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
return v;
}
};
int selectedIndex = 0;
String prevSelectedGpx = app.getSettings().LAST_SELECTED_GPX_TRACK_FOR_NEW_POINT.get();
if (prevSelectedGpx != null) {
selectedIndex = list.indexOf(prevSelectedGpx);
}
if (selectedIndex == -1) {
selectedIndex = 0;
}
final int[] selectedPosition = {selectedIndex};
builder.setSingleChoiceItems(listAdapter, selectedIndex, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int position) {
selectedPosition[0] = position;
}
});
builder.setTitle(R.string.select_gpx)
.setPositiveButton(R.string.shared_string_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int position = selectedPosition[0];
if (position != -1) {
if (showCurrentGpx && position == 0) {
callbackWithObject.processResult(null);
app.getSettings().LAST_SELECTED_GPX_TRACK_FOR_NEW_POINT.set(null);
} else {
String fileName = list.get(position).getFileName();
app.getSettings().LAST_SELECTED_GPX_TRACK_FOR_NEW_POINT.set(fileName);
SelectedGpxFile selectedGpxFile =
app.getSelectedGpxHelper().getSelectedFileByName(fileName);
if (selectedGpxFile != null) {
callbackWithObject.processResult(new GPXFile[]{selectedGpxFile.getGpxFile()});
} else {
loadGPXFileInDifferentThread(activity, callbackWithObject, dir, null, fileName);
}
}
}
}
})
.setNegativeButton(R.string.shared_string_cancel, null);
final AlertDialog dlg = builder.create();
dlg.setCanceledOnTouchOutside(false);
dlg.show();
try {
dlg.getListView().setFastScrollEnabled(true);
} catch (Exception e) {
// java.lang.ClassCastException: com.android.internal.widget.RoundCornerListAdapter
// Unknown reason but on some devices fail
}
return dlg;
}
private static AlertDialog createDialog(final Activity activity,
final boolean showCurrentGpx,
final boolean multipleChoice,
final boolean showAppearanceSetting,
final CallbackWithObject<GPXFile[]> callbackWithObject,
final List<GPXInfo> list,
final ContextMenuAdapter adapter) {
final OsmandApplication app = (OsmandApplication) activity.getApplication();
final DateFormat dateFormat = android.text.format.DateFormat.getMediumDateFormat(activity);
final File dir = app.getAppPath(IndexConstants.GPX_INDEX_DIR);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
final int layout = R.layout.gpx_track_item;
final Map<String, String> gpxAppearanceParams = new HashMap<>();
final ArrayAdapter<String> listAdapter = new ArrayAdapter<String>(activity, layout, R.id.title,
adapter.getItemNames()) {
List<GpxDataItem> dataItems = null;
@Override
public int getItemViewType(int position) {
return showCurrentGpx && position == 0 ? 1 : 0;
}
@Override
public int getViewTypeCount() {
return 2;
}
private GpxDataItem getDataItem(GPXInfo info) {
if (dataItems != null) {
for (GpxDataItem item : dataItems) {
if (item.getFile().getAbsolutePath().endsWith(info.fileName)) {
return item;
}
}
}
return null;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
// User super class to create the View
View v = convertView;
boolean checkLayout = getItemViewType(position) == 0;
if (v == null) {
v = activity.getLayoutInflater().inflate(layout, null);
}
if (dataItems == null) {
dataItems = app.getGpxDatabase().getItems();
}
final ContextMenuItem item = adapter.getItem(position);
GPXInfo info = list.get(position);
udpateGpxInfoView(v, item, info, getDataItem(info), showCurrentGpx && position == 0, app);
if (item.getSelected() == null) {
v.findViewById(R.id.check_item).setVisibility(View.GONE);
} else {
if (checkLayout) {
final AppCompatCheckBox ch = ((AppCompatCheckBox) v.findViewById(R.id.toggle_checkbox_item));
ch.setVisibility(View.VISIBLE);
v.findViewById(R.id.toggle_item).setVisibility(View.GONE);
ch.setOnCheckedChangeListener(null);
ch.setChecked(item.getSelected());
ch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
item.setSelected(isChecked);
}
});
} else {
final SwitchCompat ch = ((SwitchCompat) v.findViewById(R.id.toggle_item));
ch.setVisibility(View.VISIBLE);
v.findViewById(R.id.toggle_checkbox_item).setVisibility(View.GONE);
ch.setOnCheckedChangeListener(null);
ch.setChecked(item.getSelected());
ch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
item.setSelected(isChecked);
}
});
}
v.findViewById(R.id.check_item).setVisibility(View.VISIBLE);
}
return v;
}
};
OnClickListener onClickListener = new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int position) {
}
};
builder.setAdapter(listAdapter, onClickListener);
if (multipleChoice) {
if (showAppearanceSetting) {
final RenderingRuleProperty trackWidthProp;
final RenderingRuleProperty trackColorProp;
final RenderingRulesStorage renderer = app.getRendererRegistry().getCurrentSelectedRenderer();
if (renderer != null) {
trackWidthProp = renderer.PROPS.getCustomRule(CURRENT_TRACK_WIDTH_ATTR);
trackColorProp = renderer.PROPS.getCustomRule(CURRENT_TRACK_COLOR_ATTR);
} else {
trackWidthProp = null;
trackColorProp = null;
}
if (trackWidthProp == null || trackColorProp == null) {
builder.setTitle(R.string.show_gpx);
} else {
final View apprTitleView = activity.getLayoutInflater().inflate(R.layout.select_gpx_appearance_title, null);
final OsmandSettings.CommonPreference<String> prefWidth
= app.getSettings().getCustomRenderProperty(CURRENT_TRACK_WIDTH_ATTR);
final OsmandSettings.CommonPreference<String> prefColor
= app.getSettings().getCustomRenderProperty(CURRENT_TRACK_COLOR_ATTR);
updateAppearanceTitle(activity, app, trackWidthProp, renderer, apprTitleView, prefWidth.get(), prefColor.get());
apprTitleView.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final ListPopupWindow popup = new ListPopupWindow(activity);
popup.setAnchorView(apprTitleView);
popup.setContentWidth(AndroidUtils.dpToPx(activity, 200f));
popup.setModal(true);
popup.setDropDownGravity(Gravity.RIGHT | Gravity.TOP);
popup.setVerticalOffset(AndroidUtils.dpToPx(activity, -48f));
popup.setHorizontalOffset(AndroidUtils.dpToPx(activity, -6f));
final GpxAppearanceAdapter gpxApprAdapter = new GpxAppearanceAdapter(activity,
gpxAppearanceParams.containsKey(CURRENT_TRACK_COLOR_ATTR) ? gpxAppearanceParams.get(CURRENT_TRACK_COLOR_ATTR) : prefColor.get(),
GpxAppearanceAdapter.GpxAppearanceAdapterType.TRACK_WIDTH_COLOR);
popup.setAdapter(gpxApprAdapter);
popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
AppearanceListItem item = gpxApprAdapter.getItem(position);
if (item != null) {
if (item.getAttrName() == CURRENT_TRACK_WIDTH_ATTR) {
gpxAppearanceParams.put(CURRENT_TRACK_WIDTH_ATTR, item.getValue());
} else if (item.getAttrName() == CURRENT_TRACK_COLOR_ATTR) {
gpxAppearanceParams.put(CURRENT_TRACK_COLOR_ATTR, item.getValue());
}
}
popup.dismiss();
updateAppearanceTitle(activity, app, trackWidthProp, renderer,
apprTitleView,
gpxAppearanceParams.containsKey(CURRENT_TRACK_WIDTH_ATTR) ? gpxAppearanceParams.get(CURRENT_TRACK_WIDTH_ATTR) : prefWidth.get(),
gpxAppearanceParams.containsKey(CURRENT_TRACK_COLOR_ATTR) ? gpxAppearanceParams.get(CURRENT_TRACK_COLOR_ATTR) : prefColor.get());
}
});
popup.show();
}
});
builder.setCustomTitle(apprTitleView);
}
} else {
builder.setTitle(R.string.show_gpx);
}
builder.setPositiveButton(R.string.shared_string_ok, new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (gpxAppearanceParams.size() > 0) {
for (Map.Entry<String, String> entry : gpxAppearanceParams.entrySet()) {
final OsmandSettings.CommonPreference<String> pref
= app.getSettings().getCustomRenderProperty(entry.getKey());
pref.set(entry.getValue());
}
if (activity instanceof MapActivity) {
ConfigureMapMenu.refreshMapComplete((MapActivity) activity);
}
}
GPXFile currentGPX = null;
//clear all previously selected files before adding new one
OsmandApplication app = (OsmandApplication) activity.getApplication();
if (app != null && app.getSelectedGpxHelper() != null) {
app.getSelectedGpxHelper().clearAllGpxFileToShow();
}
if (app != null && showCurrentGpx && adapter.getItem(0).getSelected()) {
currentGPX = app.getSavingTrackHelper().getCurrentGpx();
}
List<String> s = new ArrayList<>();
for (int i = (showCurrentGpx ? 1 : 0); i < adapter.length(); i++) {
if (adapter.getItem(i).getSelected()) {
s.add(list.get(i).getFileName());
}
}
dialog.dismiss();
loadGPXFileInDifferentThread(activity, callbackWithObject, dir, currentGPX,
s.toArray(new String[s.size()]));
}
});
builder.setNegativeButton(R.string.shared_string_cancel, null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& list.size() > 1 || !showCurrentGpx && list.size() > 0) {
builder.setNeutralButton(R.string.gpx_add_track, null);
}
}
final AlertDialog dlg = builder.create();
dlg.setCanceledOnTouchOutside(true);
if (list.size() == 0 || showCurrentGpx && list.size() == 1) {
final View footerView = activity.getLayoutInflater().inflate(R.layout.no_gpx_files_list_footer, null);
TextView descTextView = (TextView)footerView.findViewById(R.id.descFolder);
String descPrefix = app.getString(R.string.gpx_no_tracks_title_folder);
SpannableString spannableDesc = new SpannableString(descPrefix + ": " + dir.getAbsolutePath());
spannableDesc.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
descPrefix.length() + 1, spannableDesc.length(), 0);
descTextView.setText(spannableDesc);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
footerView.findViewById(R.id.button).setVisibility(View.GONE);
} else {
footerView.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addTrack(activity, dlg);
}
});
}
dlg.getListView().addFooterView(footerView);
}
dlg.getListView().setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (multipleChoice) {
ContextMenuItem item = adapter.getItem(position);
item.setSelected(!item.getSelected());
listAdapter.notifyDataSetInvalidated();
if (position == 0 && showCurrentGpx && item.getSelected()) {
OsmandMonitoringPlugin monitoringPlugin = OsmandPlugin.getEnabledPlugin(OsmandMonitoringPlugin.class);
if (monitoringPlugin == null) {
AlertDialog.Builder confirm = new AlertDialog.Builder(activity);
confirm.setPositiveButton(R.string.shared_string_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(activity, PluginActivity.class);
intent.putExtra(PluginActivity.EXTRA_PLUGIN_ID, OsmandMonitoringPlugin.ID);
activity.startActivity(intent);
}
});
confirm.setNegativeButton(R.string.shared_string_cancel, null);
confirm.setMessage(activity.getString(R.string.enable_plugin_monitoring_services));
confirm.show();
} else if (!app.getSettings().SAVE_GLOBAL_TRACK_TO_GPX.get()) {
monitoringPlugin.controlDialog(activity, false);
}
}
} else {
dlg.dismiss();
if (showCurrentGpx && position == 0) {
callbackWithObject.processResult(null);
} else {
String fileName = list.get(position).getFileName();
SelectedGpxFile selectedGpxFile =
app.getSelectedGpxHelper().getSelectedFileByName(fileName);
if (selectedGpxFile != null) {
callbackWithObject.processResult(new GPXFile[]{selectedGpxFile.getGpxFile()});
} else {
loadGPXFileInDifferentThread(activity, callbackWithObject, dir, null, fileName);
}
}
}
}
});
dlg.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
Button addTrackButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL);
if (addTrackButton != null) {
addTrackButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addTrack(activity, dlg);
}
});
}
}
});
dlg.show();
try {
dlg.getListView().setFastScrollEnabled(true);
} catch (Exception e) {
// java.lang.ClassCastException: com.android.internal.widget.RoundCornerListAdapter
// Unknown reason but on some devices fail
}
return dlg;
}
public static void udpateGpxInfoView(View v, ContextMenuItem item, GPXInfo info, GpxDataItem dataItem, boolean currentlyRecordingTrack, OsmandApplication app) {
TextView viewName = ((TextView) v.findViewById(R.id.name));
viewName.setText(item.getTitle().replace("/", " • ").trim());
ImageView icon = (ImageView) v.findViewById(R.id.icon);
icon.setVisibility(View.GONE);
//icon.setImageDrawable(app.getIconsCache().getThemedIcon(R.drawable.ic_action_polygom_dark));
viewName.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
GPXTrackAnalysis analysis = null;
if (currentlyRecordingTrack) {
analysis = app.getSavingTrackHelper().getCurrentTrack().getTrackAnalysis();
} else if (dataItem != null) {
analysis = dataItem.getAnalysis();
}
boolean sectionRead = analysis == null;
if (sectionRead) {
v.findViewById(R.id.read_section).setVisibility(View.GONE);
v.findViewById(R.id.unknown_section).setVisibility(View.VISIBLE);
String date = "";
String size = "";
if (info.getFileSize() >= 0) {
if (info.getFileSize() > (100 * (1 << 10))) {
size = formatMb.format(new Object[]{(float) info.getFileSize() / (1 << 20)});
} else {
size = formatKb.format(new Object[]{(float) info.getFileSize() / (1 << 10)});
}
}
DateFormat df = app.getResourceManager().getDateFormat();
long fd = info.getLastModified();
if (fd > 0) {
date = (df.format(new Date(fd)));
}
TextView sizeText = (TextView) v.findViewById(R.id.date_and_size_details);
sizeText.setText(date + " \u2022 " + size);
} else {
v.findViewById(R.id.read_section).setVisibility(View.VISIBLE);
v.findViewById(R.id.unknown_section).setVisibility(View.GONE);
ImageView distanceI = (ImageView) v.findViewById(R.id.distance_icon);
distanceI.setVisibility(View.VISIBLE);
distanceI.setImageDrawable(app.getIconsCache().getThemedIcon(R.drawable.ic_small_distance));
ImageView pointsI = (ImageView) v.findViewById(R.id.points_icon);
pointsI.setVisibility(View.VISIBLE);
pointsI.setImageDrawable(app.getIconsCache().getThemedIcon(R.drawable.ic_small_point));
ImageView timeI = (ImageView) v.findViewById(R.id.time_icon);
timeI.setVisibility(View.VISIBLE);
timeI.setImageDrawable(app.getIconsCache().getThemedIcon(R.drawable.ic_small_time));
TextView time = (TextView) v.findViewById(R.id.time);
TextView distance = (TextView) v.findViewById(R.id.distance);
TextView pointsCount = (TextView) v.findViewById(R.id.points_count);
pointsCount.setText(analysis.wptPoints + "");
distance.setText(OsmAndFormatter.getFormattedDistance(analysis.totalDistance, app));
if (analysis.isTimeSpecified()) {
time.setText(Algorithms.formatDuration((int) (analysis.timeSpan / 1000), app.accessibilityEnabled()) + "");
} else {
time.setText("");
}
}
TextView descr = ((TextView) v.findViewById(R.id.description));
descr.setVisibility(View.GONE);
v.findViewById(R.id.check_item).setVisibility(View.GONE);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private static void addTrack(final Activity activity, final AlertDialog dialog) {
if (activity instanceof MapActivity) {
final MapActivity mapActivity = (MapActivity) activity;
ActivityResultListener listener = new ActivityResultListener(OPEN_GPX_DOCUMENT_REQUEST, new OnActivityResultListener() {
@Override
public void onResult(int resultCode, Intent resultData) {
if (resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
if (mapActivity.getGpxImportHelper().handleGpxImport(uri, false)) {
dialog.dismiss();
}
}
}
}
});
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
//intent.addCategory(Intent.CATEGORY_OPENABLE);
//intent.setType("application/gpx+xml");
//intent.setType("text/plain");
//intent.setType("text/xml");
intent.setType("*/*");
mapActivity.registerActivityResultListener(listener);
activity.startActivityForResult(intent, OPEN_GPX_DOCUMENT_REQUEST);
}
}
private static void updateAppearanceTitle(Activity activity, OsmandApplication app,
RenderingRuleProperty trackWidthProp,
RenderingRulesStorage renderer,
View apprTitleView,
String prefWidthValue,
String prefColorValue) {
TextView widthTextView = (TextView) apprTitleView.findViewById(R.id.widthTitle);
ImageView colorImageView = (ImageView) apprTitleView.findViewById(R.id.colorImage);
if (Algorithms.isEmpty(prefWidthValue)) {
widthTextView.setText(SettingsActivity.getStringPropertyValue(activity, trackWidthProp.getDefaultValueDescription()));
} else {
widthTextView.setText(SettingsActivity.getStringPropertyValue(activity, prefWidthValue));
}
int color = GpxAppearanceAdapter.parseTrackColor(renderer, prefColorValue);
if (color == -1) {
colorImageView.setImageDrawable(app.getIconsCache().getThemedIcon(R.drawable.ic_action_circle));
} else {
colorImageView.setImageDrawable(app.getIconsCache().getPaintedIcon(R.drawable.ic_action_circle, color));
}
}
public static List<GPXInfo> getSortedGPXFilesInfoByDate(File dir, boolean absolutePath) {
final List<GPXInfo> list = new ArrayList<>();
readGpxDirectory(dir, list, "", absolutePath);
Collections.sort(list, new Comparator<GPXInfo>() {
@Override
public int compare(GPXInfo object1, GPXInfo object2) {
long lhs = object1.getLastModified();
long rhs = object2.getLastModified();
return lhs < rhs ? 1 : (lhs == rhs ? 0 : -1);
}
});
return list;
}
public static List<GPXInfo> getSortedGPXFilesInfo(File dir, final List<String> selectedGpxList, boolean absolutePath) {
final List<GPXInfo> list = new ArrayList<>();
readGpxDirectory(dir, list, "", absolutePath);
if (selectedGpxList != null) {
for (GPXInfo info : list) {
for (String fileName : selectedGpxList) {
if (fileName.endsWith(info.getFileName())) {
info.setSelected(true);
break;
}
}
}
}
Collections.sort(list, new Comparator<GPXInfo>() {
@Override
public int compare(GPXInfo i1, GPXInfo i2) {
int res = i1.isSelected() == i2.isSelected() ? 0 : i1.isSelected() ? -1 : 1;
if (res != 0) {
return res;
}
return -i1.getFileName().toLowerCase().compareTo(i2.getFileName().toLowerCase());
}
});
return list;
}
private static void readGpxDirectory(File dir, final List<GPXInfo> list, String parent,
boolean absolutePath) {
if (dir != null && dir.canRead()) {
File[] files = dir.listFiles();
if (files != null) {
for (File f : files) {
if (f.getName().toLowerCase().endsWith(".gpx")) { //$NON-NLS-1$
list.add(new GPXInfo(absolutePath ? f.getAbsolutePath() :
parent + f.getName(), f.lastModified(), f.length()));
} else if (f.isDirectory()) {
readGpxDirectory(f, list, parent + f.getName() + "/", absolutePath);
}
}
}
}
}
private static void loadGPXFileInDifferentThread(final Activity activity, final CallbackWithObject<GPXFile[]> callbackWithObject,
final File dir, final GPXFile currentFile, final String... filename) {
final ProgressDialog dlg = ProgressDialog.show(activity, activity.getString(R.string.loading_smth, ""),
activity.getString(R.string.loading_data));
new Thread(new Runnable() {
@Override
public void run() {
final GPXFile[] result = new GPXFile[filename.length + (currentFile == null ? 0 : 1)];
int k = 0;
String w = "";
if (currentFile != null) {
result[k++] = currentFile;
}
for (String fname : filename) {
final File f = new File(dir, fname);
GPXFile res = GPXUtilities.loadGPXFile(activity.getApplication(), f);
if (res.warning != null && res.warning.length() > 0) {
w += res.warning + "\n";
}
result[k++] = res;
}
dlg.dismiss();
final String warn = w;
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (warn.length() > 0) {
Toast.makeText(activity, warn, Toast.LENGTH_LONG).show();
} else {
callbackWithObject.processResult(result);
}
}
});
}
}, "Loading gpx").start(); //$NON-NLS-1$
}
public static void setupGPXChart(OsmandApplication ctx, LineChart mChart, int yLabelsCount) {
OsmandSettings settings = ctx.getSettings();
boolean light = settings.isLightContent();
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
mChart.setHardwareAccelerationEnabled(false);
} else {
mChart.setHardwareAccelerationEnabled(true);
}
mChart.setTouchEnabled(true);
mChart.setDragEnabled(true);
mChart.setScaleEnabled(true);
mChart.setPinchZoom(true);
mChart.setScaleYEnabled(false);
mChart.setAutoScaleMinMaxEnabled(true);
mChart.setDrawBorders(false);
mChart.getDescription().setEnabled(false);
mChart.setMaxVisibleValueCount(10);
mChart.setMinOffset(0f);
mChart.setDragDecelerationEnabled(false);
mChart.setExtraTopOffset(24f);
mChart.setExtraBottomOffset(16f);
// create a custom MarkerView (extend MarkerView) and specify the layout
// to use for it
GPXMarkerView mv = new GPXMarkerView(mChart.getContext());
mv.setChartView(mChart); // For bounds control
mChart.setMarker(mv); // Set the marker to the chart
mChart.setDrawMarkers(true);
XAxis xAxis = mChart.getXAxis();
xAxis.setDrawAxisLine(false);
xAxis.setDrawGridLines(false);
xAxis.setPosition(BOTTOM);
xAxis.setTextColor(light ? mChart.getResources().getColor(R.color.secondary_text_light) : mChart.getResources().getColor(R.color.secondary_text_dark));
YAxis yAxis = mChart.getAxisLeft();
yAxis.enableGridDashedLine(10f, 5f, 0f);
yAxis.setGridColor(ActivityCompat.getColor(mChart.getContext(), R.color.divider_color));
yAxis.setDrawAxisLine(false);
yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
yAxis.setXOffset(16f);
yAxis.setYOffset(-6f);
yAxis.setLabelCount(yLabelsCount);
yAxis.setTextColor(light ? mChart.getResources().getColor(R.color.secondary_text_light) : mChart.getResources().getColor(R.color.secondary_text_dark));
yAxis = mChart.getAxisRight();
yAxis.enableGridDashedLine(10f, 5f, 0f);
yAxis.setGridColor(ActivityCompat.getColor(mChart.getContext(), R.color.divider_color));
yAxis.setDrawAxisLine(false);
yAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART);
yAxis.setXOffset(16f);
yAxis.setYOffset(-6f);
yAxis.setLabelCount(yLabelsCount);
yAxis.setTextColor(light ? mChart.getResources().getColor(R.color.secondary_text_light) : mChart.getResources().getColor(R.color.secondary_text_dark));
yAxis.setEnabled(false);
Legend legend = mChart.getLegend();
legend.setEnabled(false);
}
private static float setupXAxisDistance(OsmandApplication ctx, XAxis xAxis, float meters) {
OsmandSettings settings = ctx.getSettings();
OsmandSettings.MetricsConstants mc = settings.METRIC_SYSTEM.get();
float divX;
String format1 = "{0,number,0.#} ";
String format2 = "{0,number,0.##} ";
String fmt = null;
float granularity = 1f;
int mainUnitStr;
float mainUnitInMeters;
if (mc == OsmandSettings.MetricsConstants.KILOMETERS_AND_METERS) {
mainUnitStr = R.string.km;
mainUnitInMeters = METERS_IN_KILOMETER;
} else if (mc == OsmandSettings.MetricsConstants.NAUTICAL_MILES) {
mainUnitStr = R.string.nm;
mainUnitInMeters = METERS_IN_ONE_NAUTICALMILE;
} else {
mainUnitStr = R.string.mile;
mainUnitInMeters = METERS_IN_ONE_MILE;
}
if (meters > 9.99f * mainUnitInMeters) {
fmt = format1;
granularity = .1f;
}
if (meters >= 100 * mainUnitInMeters ||
meters > 9.99f * mainUnitInMeters ||
meters > 0.999f * mainUnitInMeters ||
mc == OsmandSettings.MetricsConstants.MILES_AND_FEET && meters > 0.249f * mainUnitInMeters ||
mc == OsmandSettings.MetricsConstants.MILES_AND_METERS && meters > 0.249f * mainUnitInMeters ||
mc == OsmandSettings.MetricsConstants.MILES_AND_YARDS && meters > 0.249f * mainUnitInMeters ||
mc == OsmandSettings.MetricsConstants.NAUTICAL_MILES && meters > 0.99f * mainUnitInMeters) {
divX = mainUnitInMeters;
if (fmt == null) {
fmt = format2;
granularity = .01f;
}
} else {
fmt = null;
granularity = 1f;
if (mc == OsmandSettings.MetricsConstants.KILOMETERS_AND_METERS || mc == OsmandSettings.MetricsConstants.MILES_AND_METERS) {
divX = 1f;
mainUnitStr = R.string.m;
} else if (mc == OsmandSettings.MetricsConstants.MILES_AND_FEET) {
divX = 1f / FEET_IN_ONE_METER;
mainUnitStr = R.string.foot;
} else if (mc == OsmandSettings.MetricsConstants.MILES_AND_YARDS) {
divX = 1f / YARDS_IN_ONE_METER;
mainUnitStr = R.string.yard;
} else {
divX = 1f;
mainUnitStr = R.string.m;
}
}
final String formatX = fmt;
final String mainUnitX = ctx.getString(mainUnitStr);
xAxis.setGranularity(granularity);
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
if (!Algorithms.isEmpty(formatX)) {
return MessageFormat.format(formatX + mainUnitX, value);
} else {
return (int)value + " " + mainUnitX;
}
}
});
return divX;
}
private static float setupXAxisTime(XAxis xAxis, long timeSpan) {
final boolean useHours = timeSpan / 3600000 > 0;
xAxis.setGranularity(1f);
xAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
int seconds = (int)value;
if (useHours) {
int hours = seconds / (60 * 60);
int minutes = (seconds / 60) % 60;
int sec = seconds % 60;
return hours + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (sec < 10 ? "0" + sec : sec);
} else {
int minutes = (seconds / 60) % 60;
int sec = seconds % 60;
return (minutes < 10 ? "0" + minutes : minutes) + ":" + (sec < 10 ? "0" + sec : sec);
}
}
});
return 1f;
}
private static List<Entry> calculateElevationArray(GPXTrackAnalysis analysis, GPXDataSetAxisType axisType,
float divX, float convEle) {
List<Entry> values = new ArrayList<>();
List<Elevation> elevationData = analysis.elevationData;
float nextX = 0;
float nextY;
float elev;
float prevElevOrig = -80000;
float prevElev = 0;
int i = -1;
int lastIndex = elevationData.size() - 1;
Entry lastEntry = null;
float lastXSameY = -1;
boolean hasSameY = false;
float x;
for (Elevation e : elevationData) {
i++;
x = axisType == GPXDataSetAxisType.TIME ? e.time : e.distance;
if (x > 0) {
nextX += x / divX;
if (!Float.isNaN(e.elevation)) {
elev = e.elevation;
if (prevElevOrig != -80000) {
if (elev > prevElevOrig) {
elev -= 1f;
} else if (prevElevOrig == elev && i < lastIndex) {
hasSameY = true;
lastXSameY = nextX;
continue;
}
if (prevElev == elev && i < lastIndex) {
hasSameY = true;
lastXSameY = nextX;
continue;
}
if (hasSameY) {
values.add(new Entry(lastXSameY, lastEntry.getY()));
}
hasSameY = false;
}
prevElevOrig = e.elevation;
prevElev = elev;
nextY = elev * convEle;
lastEntry = new Entry(nextX, nextY);
values.add(lastEntry);
}
}
}
return values;
}
public static OrderedLineDataSet createGPXElevationDataSet(OsmandApplication ctx, LineChart mChart,
GPXTrackAnalysis analysis,
GPXDataSetAxisType axisType,
boolean useRightAxis, boolean drawFilled) {
OsmandSettings settings = ctx.getSettings();
OsmandSettings.MetricsConstants mc = settings.METRIC_SYSTEM.get();
boolean useFeet = (mc == OsmandSettings.MetricsConstants.MILES_AND_FEET) || (mc == OsmandSettings.MetricsConstants.MILES_AND_YARDS);
boolean light = settings.isLightContent();
final float convEle = useFeet ? 3.28084f : 1.0f;
float divX;
XAxis xAxis = mChart.getXAxis();
if (axisType == GPXDataSetAxisType.TIME && analysis.isTimeSpecified()) {
divX = setupXAxisTime(xAxis, analysis.timeSpan);
} else {
divX = setupXAxisDistance(ctx, xAxis, analysis.totalDistance);
}
final String mainUnitY = useFeet ? ctx.getString(R.string.foot) : ctx.getString(R.string.m);
YAxis yAxis;
if (useRightAxis) {
yAxis = mChart.getAxisRight();
yAxis.setEnabled(true);
} else {
yAxis = mChart.getAxisLeft();
}
yAxis.setTextColor(ActivityCompat.getColor(mChart.getContext(), R.color.gpx_chart_blue_label));
yAxis.setGridColor(ActivityCompat.getColor(mChart.getContext(), R.color.gpx_chart_blue_grid));
yAxis.setGranularity(1f);
yAxis.resetAxisMinimum();
yAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return (int)value + " " + mainUnitY;
}
});
List<Entry> values = calculateElevationArray(analysis, axisType, divX, convEle);
OrderedLineDataSet dataSet = new OrderedLineDataSet(values, "", GPXDataSetType.ALTITUDE, axisType);
dataSet.priority = (float) (analysis.avgElevation - analysis.minElevation) * convEle;
dataSet.divX = divX;
dataSet.mulY = convEle;
dataSet.divY = Float.NaN;
dataSet.units = mainUnitY;
dataSet.setColor(ContextCompat.getColor(mChart.getContext(), R.color.gpx_chart_blue));
dataSet.setLineWidth(1f);
if (drawFilled) {
dataSet.setFillAlpha(128);
dataSet.setFillColor(ContextCompat.getColor(mChart.getContext(), R.color.gpx_chart_blue));
dataSet.setDrawFilled(true);
} else {
dataSet.setDrawFilled(false);
}
dataSet.setDrawValues(false);
dataSet.setValueTextSize(9f);
dataSet.setFormLineWidth(1f);
dataSet.setFormSize(15.f);
dataSet.setDrawCircles(false);
dataSet.setDrawCircleHole(false);
dataSet.setHighlightEnabled(true);
dataSet.setDrawVerticalHighlightIndicator(true);
dataSet.setDrawHorizontalHighlightIndicator(false);
dataSet.setHighLightColor(light ? mChart.getResources().getColor(R.color.secondary_text_light) : mChart.getResources().getColor(R.color.secondary_text_dark));
//dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
dataSet.setFillFormatter(new IFillFormatter() {
@Override
public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
return dataProvider.getYChartMin();
}
});
if (useRightAxis) {
dataSet.setAxisDependency(YAxis.AxisDependency.RIGHT);
}
return dataSet;
}
public static OrderedLineDataSet createGPXSpeedDataSet(OsmandApplication ctx, LineChart mChart,
GPXTrackAnalysis analysis,
GPXDataSetAxisType axisType,
boolean useRightAxis, boolean drawFilled) {
OsmandSettings settings = ctx.getSettings();
boolean light = settings.isLightContent();
float divX;
XAxis xAxis = mChart.getXAxis();
if (axisType == GPXDataSetAxisType.TIME && analysis.isTimeSpecified()) {
divX = setupXAxisTime(xAxis, analysis.timeSpan);
} else {
divX = setupXAxisDistance(ctx, xAxis, analysis.totalDistance);
}
OsmandSettings.SpeedConstants sps = settings.SPEED_SYSTEM.get();
float mulSpeed = Float.NaN;
float divSpeed = Float.NaN;
final String mainUnitY = sps.toShortString(ctx);
if (sps == OsmandSettings.SpeedConstants.KILOMETERS_PER_HOUR) {
mulSpeed = 3.6f;
} else if (sps == OsmandSettings.SpeedConstants.MILES_PER_HOUR) {
mulSpeed = 3.6f * METERS_IN_KILOMETER / METERS_IN_ONE_MILE;
} else if (sps == OsmandSettings.SpeedConstants.NAUTICALMILES_PER_HOUR) {
mulSpeed = 3.6f * METERS_IN_KILOMETER / METERS_IN_ONE_NAUTICALMILE;
} else if (sps == OsmandSettings.SpeedConstants.MINUTES_PER_KILOMETER) {
divSpeed = METERS_IN_KILOMETER / 60;
} else if (sps == OsmandSettings.SpeedConstants.MINUTES_PER_MILE) {
divSpeed = METERS_IN_ONE_MILE / 60;
} else {
mulSpeed = 1f;
}
YAxis yAxis;
if (useRightAxis) {
yAxis = mChart.getAxisRight();
yAxis.setEnabled(true);
} else {
yAxis = mChart.getAxisLeft();
}
yAxis.setTextColor(ActivityCompat.getColor(mChart.getContext(), R.color.gpx_chart_orange_label));
yAxis.setGridColor(ActivityCompat.getColor(mChart.getContext(), R.color.gpx_chart_orange_grid));
yAxis.setAxisMinimum(0f);
ArrayList<Entry> values = new ArrayList<>();
List<Speed> speedData = analysis.speedData;
float nextX = 0;
float nextY;
float x;
for (Speed s : speedData) {
x = axisType == GPXDataSetAxisType.TIME ? s.time : s.distance;
if (x > 0) {
if (axisType == GPXDataSetAxisType.TIME && x > 60) {
values.add(new Entry(nextX + 1, 0));
values.add(new Entry(nextX + x - 1, 0));
}
nextX += x / divX;
if (Float.isNaN(divSpeed)) {
nextY = s.speed * mulSpeed;
} else {
nextY = divSpeed / s.speed;
}
if (nextY < 0 || Float.isInfinite(nextY)) {
nextY = 0;
}
values.add(new Entry(nextX, nextY));
}
}
OrderedLineDataSet dataSet = new OrderedLineDataSet(values, "", GPXDataSetType.SPEED, axisType);
String format = null;
if (dataSet.getYMax() < 3) {
format = "{0,number,0.#} ";
}
final String formatY = format;
yAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
if (!Algorithms.isEmpty(formatY)) {
return MessageFormat.format(formatY + mainUnitY, value);
} else {
return (int)value + " " + mainUnitY;
}
}
});
if (Float.isNaN(divSpeed)) {
dataSet.priority = analysis.avgSpeed * mulSpeed;
} else {
dataSet.priority = divSpeed / analysis.avgSpeed;
}
dataSet.divX = divX;
if (Float.isNaN(divSpeed)) {
dataSet.mulY = mulSpeed;
dataSet.divY = Float.NaN;
} else {
dataSet.divY = divSpeed;
dataSet.mulY = Float.NaN;
}
dataSet.units = mainUnitY;
dataSet.setColor(ContextCompat.getColor(mChart.getContext(), R.color.gpx_chart_orange));
dataSet.setLineWidth(1f);
if (drawFilled) {
dataSet.setFillAlpha(128);
dataSet.setFillColor(ContextCompat.getColor(mChart.getContext(), R.color.gpx_chart_orange));
dataSet.setDrawFilled(true);
} else {
dataSet.setDrawFilled(false);
}
dataSet.setDrawValues(false);
dataSet.setValueTextSize(9f);
dataSet.setFormLineWidth(1f);
dataSet.setFormSize(15.f);
dataSet.setDrawCircles(false);
dataSet.setDrawCircleHole(false);
dataSet.setHighlightEnabled(true);
dataSet.setDrawVerticalHighlightIndicator(true);
dataSet.setDrawHorizontalHighlightIndicator(false);
dataSet.setHighLightColor(light ? mChart.getResources().getColor(R.color.secondary_text_light) : mChart.getResources().getColor(R.color.secondary_text_dark));
//dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
if (useRightAxis) {
dataSet.setAxisDependency(YAxis.AxisDependency.RIGHT);
}
return dataSet;
}
public static OrderedLineDataSet createGPXSlopeDataSet(OsmandApplication ctx, LineChart mChart,
GPXTrackAnalysis analysis,
GPXDataSetAxisType axisType,
List<Entry> eleValues,
boolean useRightAxis, boolean drawFilled) {
if (axisType == GPXDataSetAxisType.TIME) {
return null;
}
OsmandSettings settings = ctx.getSettings();
boolean light = settings.isLightContent();
OsmandSettings.MetricsConstants mc = settings.METRIC_SYSTEM.get();
boolean useFeet = (mc == OsmandSettings.MetricsConstants.MILES_AND_FEET) || (mc == OsmandSettings.MetricsConstants.MILES_AND_YARDS);
final float convEle = useFeet ? 3.28084f : 1.0f;
final float totalDistance = analysis.totalDistance;
XAxis xAxis = mChart.getXAxis();
float divX = setupXAxisDistance(ctx, xAxis, analysis.totalDistance);
final String mainUnitY = "%";
YAxis yAxis;
if (useRightAxis) {
yAxis = mChart.getAxisRight();
yAxis.setEnabled(true);
} else {
yAxis = mChart.getAxisLeft();
}
yAxis.setTextColor(ActivityCompat.getColor(mChart.getContext(), R.color.gpx_chart_green_label));
yAxis.setGridColor(ActivityCompat.getColor(mChart.getContext(), R.color.gpx_chart_green_grid));
yAxis.setGranularity(1f);
yAxis.resetAxisMinimum();
yAxis.setValueFormatter(new IAxisValueFormatter() {
@Override
public String getFormattedValue(float value, AxisBase axis) {
return (int)value + " " + mainUnitY;
}
});
List<Entry> values;
if (eleValues == null) {
values = calculateElevationArray(analysis, GPXDataSetAxisType.DISTANCE, 1f, 1f);
} else {
values = new ArrayList<>(eleValues.size());
for (Entry e : eleValues) {
values.add(new Entry(e.getX() * divX, e.getY() / convEle));
}
}
if (values == null) {
if (useRightAxis) {
yAxis.setEnabled(false);
}
return null;
}
int lastIndex = values.size() - 1;
double STEP = 5;
double[] calculatedDist = new double[(int) (totalDistance / STEP) + 1];
double[] calculatedH = new double[(int) (totalDistance / STEP) + 1];
int nextW = 0;
for (int k = 0; k < calculatedDist.length; k++) {
if (k > 0) {
calculatedDist[k] = calculatedDist[k - 1] + STEP;
}
while(nextW < lastIndex && calculatedDist[k] > values.get(nextW).getX()) {
nextW ++;
}
double pd = nextW == 0 ? 0 : values.get(nextW - 1).getX();
double ph = nextW == 0 ? values.get(0).getY() : values.get(nextW - 1).getY();
calculatedH[k] = ph + (values.get(nextW).getY() - ph) / (values.get(nextW).getX() - pd) * (calculatedDist[k] - pd);
}
double SLOPE_PROXIMITY = 150;
if (totalDistance - SLOPE_PROXIMITY < 0) {
if (useRightAxis) {
yAxis.setEnabled(false);
}
return null;
}
double[] calculatedSlopeDist = new double[(int) ((totalDistance - SLOPE_PROXIMITY) / STEP) + 1];
double[] calculatedSlope = new double[(int) ((totalDistance - SLOPE_PROXIMITY) / STEP) + 1];
int index = (int) ((SLOPE_PROXIMITY / STEP) / 2);
for (int k = 0; k < calculatedSlopeDist.length; k++) {
calculatedSlopeDist[k] = calculatedDist[index + k];
calculatedSlope[k] = (calculatedH[ 2 * index + k] - calculatedH[k]) * 100 / SLOPE_PROXIMITY;
if (Double.isNaN(calculatedSlope[k])) {
calculatedSlope[k] = 0;
}
}
List<Entry> slopeValues = new ArrayList<>(calculatedSlopeDist.length);
float prevSlope = -80000;
float slope;
float x;
float lastXSameY = 0;
boolean hasSameY = false;
Entry lastEntry = null;
lastIndex = calculatedSlopeDist.length - 1;
for (int i = 0; i < calculatedSlopeDist.length; i++) {
x = (float) calculatedSlopeDist[i] / divX;
slope = (float) calculatedSlope[i];
if (prevSlope != -80000) {
if (prevSlope == slope && i < lastIndex) {
hasSameY = true;
lastXSameY = x;
continue;
}
if (hasSameY) {
slopeValues.add(new Entry(lastXSameY, lastEntry.getY()));
}
hasSameY = false;
}
prevSlope = slope;
lastEntry = new Entry(x, slope);
slopeValues.add(lastEntry);
}
OrderedLineDataSet dataSet = new OrderedLineDataSet(slopeValues, "", GPXDataSetType.SLOPE, axisType);
dataSet.divX = divX;
dataSet.units = mainUnitY;
dataSet.setColor(ContextCompat.getColor(mChart.getContext(), R.color.gpx_chart_green));
dataSet.setLineWidth(1f);
if (drawFilled) {
dataSet.setFillAlpha(128);
dataSet.setFillColor(ContextCompat.getColor(mChart.getContext(), R.color.gpx_chart_green));
dataSet.setDrawFilled(true);
} else {
dataSet.setDrawFilled(false);
}
dataSet.setDrawValues(false);
dataSet.setValueTextSize(9f);
dataSet.setFormLineWidth(1f);
dataSet.setFormSize(15.f);
dataSet.setDrawCircles(false);
dataSet.setDrawCircleHole(false);
dataSet.setHighlightEnabled(true);
dataSet.setDrawVerticalHighlightIndicator(true);
dataSet.setDrawHorizontalHighlightIndicator(false);
dataSet.setHighLightColor(light ? mChart.getResources().getColor(R.color.secondary_text_light) : mChart.getResources().getColor(R.color.secondary_text_dark));
//dataSet.setMode(LineDataSet.Mode.HORIZONTAL_BEZIER);
/*
dataSet.setFillFormatter(new IFillFormatter() {
@Override
public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) {
return dataProvider.getYChartMin();
}
});
*/
if (useRightAxis) {
dataSet.setAxisDependency(YAxis.AxisDependency.RIGHT);
}
return dataSet;
}
public enum GPXDataSetType {
ALTITUDE(R.string.altitude, R.drawable.ic_action_altitude_average),
SPEED(R.string.map_widget_speed, R.drawable.ic_action_speed),
SLOPE(R.string.shared_string_slope, R.drawable.ic_action_altitude_ascent);
private int stringId;
private int imageId;
private GPXDataSetType(int stringId, int imageId) {
this.stringId = stringId;
this.imageId = imageId;
}
public String getName(@NonNull Context ctx) {
return ctx.getString(stringId);
}
public int getStringId() {
return stringId;
}
public int getImageId() {
return imageId;
}
public Drawable getImageDrawable(@NonNull OsmandApplication app) {
return app.getIconsCache().getThemedIcon(imageId);
}
public static String getName(@NonNull Context ctx, @NonNull GPXDataSetType[] types) {
List<String> list = new ArrayList<>();
for (GPXDataSetType type : types) {
list.add(type.getName(ctx));
}
Collections.sort(list);
StringBuilder sb = new StringBuilder();
for (String s : list) {
if (sb.length() > 0) {
sb.append("/");
}
sb.append(s);
}
return sb.toString();
}
public static Drawable getImageDrawable(@NonNull OsmandApplication app, @NonNull GPXDataSetType[] types) {
if (types.length > 0) {
return types[0].getImageDrawable(app);
} else {
return null;
}
}
}
public enum GPXDataSetAxisType {
DISTANCE(R.string.distance, R.drawable.ic_action_marker_dark),
TIME(R.string.shared_string_time, R.drawable.ic_action_time);
private int stringId;
private int imageId;
private GPXDataSetAxisType(int stringId, int imageId) {
this.stringId = stringId;
this.imageId = imageId;
}
public String getName(Context ctx) {
return ctx.getString(stringId);
}
public int getStringId() {
return stringId;
}
public int getImageId() {
return imageId;
}
public Drawable getImageDrawable(OsmandApplication app) {
return app.getIconsCache().getThemedIcon(imageId);
}
}
public static class OrderedLineDataSet extends LineDataSet {
private GPXDataSetType dataSetType;
private GPXDataSetAxisType dataSetAxisType;
float priority;
String units;
float divX = 1f;
float divY = 1f;
float mulY = 1f;
OrderedLineDataSet(List<Entry> yVals, String label, GPXDataSetType dataSetType, GPXDataSetAxisType dataSetAxisType) {
super(yVals, label);
this.dataSetType = dataSetType;
this.dataSetAxisType = dataSetAxisType;
}
public GPXDataSetType getDataSetType() {
return dataSetType;
}
public GPXDataSetAxisType getDataSetAxisType() {
return dataSetAxisType;
}
public float getPriority() {
return priority;
}
public float getDivX() {
return divX;
}
public float getDivY() {
return divY;
}
public float getMulY() {
return mulY;
}
public String getUnits() {
return units;
}
}
@SuppressLint("ViewConstructor")
private static class GPXMarkerView extends MarkerView {
private View textAltView;
private View textSpdView;
private View textSlpView;
public GPXMarkerView(Context context) {
super(context, R.layout.chart_marker_view);
textAltView = findViewById(R.id.text_alt_container);
textSpdView = findViewById(R.id.text_spd_container);
textSlpView = findViewById(R.id.text_slp_container);
}
// callbacks everytime the MarkerView is redrawn, can be used to update the
// content (user-interface)
@Override
public void refreshContent(Entry e, Highlight highlight) {
ChartData chartData = getChartView().getData();
if (chartData.getDataSetCount() == 1) {
OrderedLineDataSet dataSet = (OrderedLineDataSet) chartData.getDataSetByIndex(0);
String value = Integer.toString((int) e.getY()) + " ";
String units = dataSet.units;
switch (dataSet.getDataSetType()) {
case ALTITUDE:
((TextView) textAltView.findViewById(R.id.text_alt_value)).setText(value);
((TextView) textAltView.findViewById(R.id.text_alt_units)).setText(units);
textAltView.setVisibility(VISIBLE);
textSpdView.setVisibility(GONE);
textSlpView.setVisibility(GONE);
break;
case SPEED:
((TextView) textSpdView.findViewById(R.id.text_spd_value)).setText(value);
((TextView) textSpdView.findViewById(R.id.text_spd_units)).setText(units);
textAltView.setVisibility(GONE);
textSpdView.setVisibility(VISIBLE);
textSlpView.setVisibility(GONE);
break;
case SLOPE:
((TextView) textSlpView.findViewById(R.id.text_slp_value)).setText(value);
textAltView.setVisibility(GONE);
textSpdView.setVisibility(GONE);
textSlpView.setVisibility(VISIBLE);
break;
}
findViewById(R.id.divider).setVisibility(GONE);
} else if (chartData.getDataSetCount() == 2) {
OrderedLineDataSet dataSet1 = (OrderedLineDataSet) chartData.getDataSetByIndex(0);
OrderedLineDataSet dataSet2 = (OrderedLineDataSet) chartData.getDataSetByIndex(1);
int altSetIndex = -1;
int spdSetIndex = -1;
int slpSetIndex = -1;
switch (dataSet1.getDataSetType()) {
case ALTITUDE:
altSetIndex = 0;
break;
case SPEED:
spdSetIndex = 0;
break;
case SLOPE:
slpSetIndex = 0;
break;
}
switch (dataSet2.getDataSetType()) {
case ALTITUDE:
altSetIndex = 1;
break;
case SPEED:
spdSetIndex = 1;
break;
case SLOPE:
slpSetIndex = 1;
break;
}
if (altSetIndex != -1) {
float y = getInterpolatedY(altSetIndex == 0 ? dataSet1 : dataSet2, e);
((TextView) textAltView.findViewById(R.id.text_alt_value)).setText(Integer.toString((int) y) + " ");
((TextView) textAltView.findViewById(R.id.text_alt_units)).setText((altSetIndex == 0 ? dataSet1.units : dataSet2.units));
textAltView.setVisibility(VISIBLE);
} else {
textAltView.setVisibility(GONE);
}
if (spdSetIndex != -1) {
float y = getInterpolatedY(spdSetIndex == 0 ? dataSet1 : dataSet2, e);
((TextView) textSpdView.findViewById(R.id.text_spd_value)).setText(Integer.toString((int) y) + " ");
((TextView) textSpdView.findViewById(R.id.text_spd_units)).setText(spdSetIndex == 0 ? dataSet1.units : dataSet2.units);
textSpdView.setVisibility(VISIBLE);
} else {
textSpdView.setVisibility(GONE);
}
if (slpSetIndex != -1) {
float y = getInterpolatedY(slpSetIndex == 0 ? dataSet1 : dataSet2, e);
((TextView) textSlpView.findViewById(R.id.text_slp_value)).setText(Integer.toString((int) y) + " ");
textSlpView.setVisibility(VISIBLE);
} else {
textSlpView.setVisibility(GONE);
}
findViewById(R.id.divider).setVisibility(VISIBLE);
} else {
textAltView.setVisibility(GONE);
textSpdView.setVisibility(GONE);
textSlpView.setVisibility(GONE);
findViewById(R.id.divider).setVisibility(GONE);
}
super.refreshContent(e, highlight);
}
private float getInterpolatedY(OrderedLineDataSet ds, Entry e) {
if (ds.getEntryIndex(e) == -1) {
Entry upEntry = ds.getEntryForXValue(e.getX(), Float.NaN, DataSet.Rounding.UP);
Entry downEntry = upEntry;
int upIndex = ds.getEntryIndex(upEntry);
if (upIndex > 0) {
downEntry = ds.getEntryForIndex(upIndex - 1);
}
return MapUtils.getInterpolatedY(downEntry.getX(), downEntry.getY(), upEntry.getX(), upEntry.getY(), e.getX());
} else {
return e.getY();
}
}
@Override
public MPPointF getOffset() {
if (getChartView().getData().getDataSetCount() == 2) {
int x = findViewById(R.id.divider).getLeft();
return new MPPointF(-x - AndroidUtils.dpToPx(getContext(), .5f), 0);
} else {
return new MPPointF(-getWidth() / 2f, 0);
}
}
@Override
public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) {
int margin = AndroidUtils.dpToPx(getContext(), 3f);
MPPointF offset = getOffset();
offset.y = -posY;
if (posX + offset.x - margin < 0) {
offset.x -= (offset.x + posX - margin);
}
if (posX + offset.x + getWidth() + margin > getChartView().getWidth()) {
offset.x -= (getWidth() - (getChartView().getWidth() - posX) + offset.x) + margin;
}
return offset;
}
}
public static class GPXInfo {
private String fileName;
private long lastModified;
private long fileSize;
private boolean selected;
GPXInfo(String fileName, long lastModified, long fileSize) {
this.fileName = fileName;
this.lastModified = lastModified;
this.fileSize = fileSize;
}
public String getFileName() {
return fileName;
}
public long getLastModified() {
return lastModified;
}
public long getFileSize() {
return fileSize;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
}
}