package net.osmand.plus;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import net.osmand.IProgress;
import net.osmand.plus.GPXUtilities.GPXFile;
import net.osmand.plus.GPXUtilities.GPXTrackAnalysis;
import net.osmand.plus.GPXUtilities.Route;
import net.osmand.plus.GPXUtilities.Track;
import net.osmand.plus.GPXUtilities.TrkSegment;
import net.osmand.plus.GPXUtilities.WptPt;
import net.osmand.plus.OsmandSettings.MetricsConstants;
import net.osmand.plus.activities.SavingTrackHelper;
import net.osmand.plus.helpers.GpxUiHelper;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetAxisType;
import net.osmand.plus.helpers.GpxUiHelper.GPXDataSetType;
import net.osmand.util.Algorithms;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class GpxSelectionHelper {
private static final String CURRENT_TRACK = "currentTrack";
private static final String FILE = "file";
private static final String COLOR = "color";
private OsmandApplication app;
@NonNull
private List<SelectedGpxFile> selectedGPXFiles = new java.util.ArrayList<>();
private SavingTrackHelper savingTrackHelper;
public GpxSelectionHelper(OsmandApplication osmandApplication, SavingTrackHelper trackHelper) {
this.app = osmandApplication;
savingTrackHelper = trackHelper;
}
public void clearAllGpxFileToShow() {
selectedGPXFiles.clear();
saveCurrentSelections();
}
public boolean isShowingAnyGpxFiles() {
return !selectedGPXFiles.isEmpty();
}
@NonNull
public List<SelectedGpxFile> getSelectedGPXFiles() {
return selectedGPXFiles;
}
public String getGpxDescription() {
if (selectedGPXFiles.size() == 1) {
GPXFile currentGPX = app.getSavingTrackHelper().getCurrentGpx();
if (selectedGPXFiles.get(0).getGpxFile() == currentGPX) {
return app.getResources().getString(R.string.current_track);
}
File file = new File(selectedGPXFiles.get(0).getGpxFile().path);
return Algorithms.getFileNameWithoutExtension(file).replace('_', ' ');
} else if (selectedGPXFiles.size() == 0) {
return null;
} else {
return app.getResources().getString(R.string.number_of_gpx_files_selected_pattern,
selectedGPXFiles.size());
}
}
public SelectedGpxFile getSelectedGPXFile(WptPt point) {
for (SelectedGpxFile g : selectedGPXFiles) {
if (g.getGpxFile().points.contains(point)) {
return g;
}
}
return null;
}
private String getString(int resId, Object... formatArgs) {
return app.getString(resId, formatArgs);
}
public GpxDisplayGroup buildGpxDisplayGroup(GPXFile g, int trackIndex, String name) {
Track t = g.tracks.get(trackIndex);
GpxDisplayGroup group = new GpxDisplayGroup(g);
group.gpxName = name;
group.color = t.getColor(g.getColor(0));
group.setType(GpxDisplayItemType.TRACK_SEGMENT);
group.setTrack(t);
String ks = (trackIndex + 1) + "";
group.setName(getString(R.string.gpx_selection_track, name, g.tracks.size() == 1 ? "" : ks));
String d = "";
if (t.name != null && t.name.length() > 0) {
d = t.name + " " + d;
}
group.setDescription(d);
processGroupTrack(app, group);
return group;
}
private String getGroupName(GPXFile g) {
String name = g.path;
if (g.showCurrentTrack) {
name = getString(R.string.shared_string_currently_recording_track);
} else {
int i = name.lastIndexOf('/');
if (i >= 0) {
name = name.substring(i + 1);
}
i = name.lastIndexOf('\\');
if (i >= 0) {
name = name.substring(i + 1);
}
if (name.toLowerCase().endsWith(".gpx")) {
name = name.substring(0, name.length() - 4);
}
name = name.replace('_', ' ');
}
return name;
}
public List<GpxDisplayGroup> collectDisplayGroups(GPXFile g) {
List<GpxDisplayGroup> dg = new ArrayList<>();
String name = getGroupName(g);
if (g.tracks.size() > 0) {
for (int i = 0; i < g.tracks.size(); i++) {
GpxDisplayGroup group = buildGpxDisplayGroup(g, i, name);
dg.add(group);
}
}
if (g.routes.size() > 0) {
int k = 0;
for (Route route : g.routes) {
GpxDisplayGroup group = new GpxDisplayGroup(g);
group.gpxName = name;
group.setType(GpxDisplayItemType.TRACK_ROUTE_POINTS);
String d = getString(R.string.gpx_selection_number_of_points, name, route.points.size());
if (route.name != null && route.name.length() > 0) {
d = route.name + " " + d;
}
group.setDescription(d);
String ks = (k++) + "";
group.setName(getString(R.string.gpx_selection_route_points, name, g.routes.size() == 1 ? "" : ks));
dg.add(group);
List<GpxDisplayItem> list = group.getModifiableList();
int t = 0;
for (WptPt r : route.points) {
GpxDisplayItem item = new GpxDisplayItem();
item.group = group;
item.description = r.desc;
item.expanded = true;
item.name = r.name;
t++;
if (Algorithms.isEmpty(item.name)) {
item.name = getString(R.string.gpx_selection_point, t + "");
}
item.locationStart = r;
item.locationEnd = r;
list.add(item);
}
}
}
if (g.points.size() > 0) {
GpxDisplayGroup group = new GpxDisplayGroup(g);
group.gpxName = name;
group.setType(GpxDisplayItemType.TRACK_POINTS);
group.setDescription(getString(R.string.gpx_selection_number_of_points, g.points.size()));
group.setName(getString(R.string.gpx_selection_points, name));
dg.add(group);
List<GpxDisplayItem> list = group.getModifiableList();
int k = 0;
for (WptPt r : g.points) {
GpxDisplayItem item = new GpxDisplayItem();
item.group = group;
item.description = r.desc;
item.name = r.name;
k++;
if (Algorithms.isEmpty(item.name)) {
item.name = getString(R.string.gpx_selection_point, k + "");
}
item.expanded = true;
item.locationStart = r;
item.locationEnd = r;
list.add(item);
}
}
return dg;
}
private static void processGroupTrack(OsmandApplication app, GpxDisplayGroup group) {
List<GpxDisplayItem> list = group.getModifiableList();
String timeSpanClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_time_span_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));
String distanceClr = Algorithms.colorToString(ContextCompat.getColor(app, R.color.gpx_distance_color));
final float eleThreshold = 3;
// int t = 1;
for (TrkSegment r : group.track.segments) {
if (r.points.size() == 0) {
continue;
}
GPXTrackAnalysis[] as;
boolean split = true;
if (group.splitDistance > 0) {
List<GPXTrackAnalysis> trackSegments = r.splitByDistance(group.splitDistance);
as = trackSegments.toArray(new GPXTrackAnalysis[trackSegments.size()]);
} else if (group.splitTime > 0) {
List<GPXTrackAnalysis> trackSegments = r.splitByTime(group.splitTime);
as = trackSegments.toArray(new GPXTrackAnalysis[trackSegments.size()]);
} else {
split = false;
as = new GPXTrackAnalysis[]{GPXTrackAnalysis.segment(0, r)};
}
for (GPXTrackAnalysis analysis : as) {
GpxDisplayItem item = new GpxDisplayItem();
item.group = group;
if (split) {
item.splitMetric = analysis.metricEnd;
item.secondarySplitMetric = analysis.secondaryMetricEnd;
item.splitName = formatSplitName(analysis.metricEnd, group, app);
item.splitName += " (" + formatSecondarySplitName(analysis.secondaryMetricEnd, group, app) + ") ";
}
item.description = GpxUiHelper.getDescription(app, analysis, true);
item.analysis = analysis;
String name = "";
// if(group.track.segments.size() > 1) {
// name += t++ + ". ";
// }
if (!group.isSplitDistance()) {
name += GpxUiHelper.getColorValue(distanceClr, OsmAndFormatter.getFormattedDistance(analysis.totalDistance, app));
}
if ((analysis.timeSpan > 0 || analysis.timeMoving > 0) && !group.isSplitTime()) {
long tm = analysis.timeMoving;
if (tm == 0) {
tm = analysis.timeSpan;
}
if (name.length() != 0)
name += ", ";
name += GpxUiHelper.getColorValue(timeSpanClr, Algorithms.formatDuration((int) (tm / 1000), app.accessibilityEnabled()));
}
if (analysis.isSpeedSpecified()) {
if (name.length() != 0)
name += ", ";
name += GpxUiHelper.getColorValue(speedClr, OsmAndFormatter.getFormattedSpeed(analysis.avgSpeed, app));
}
// add min/max elevation data to split track analysis to facilitate easier track/segment identification
if (analysis.isElevationSpecified()) {
if (name.length() != 0)
name += ", ";
name += GpxUiHelper.getColorValue(descClr, OsmAndFormatter.getFormattedAlt(analysis.minElevation, app));
name += " - ";
name += GpxUiHelper.getColorValue(ascClr, OsmAndFormatter.getFormattedAlt(analysis.maxElevation, app));
}
if (analysis.isElevationSpecified() && (analysis.diffElevationUp > eleThreshold ||
analysis.diffElevationDown > eleThreshold)) {
if (name.length() != 0)
name += ", ";
if (analysis.diffElevationDown > eleThreshold) {
name += GpxUiHelper.getColorValue(descClr, " \u2193 " +
OsmAndFormatter.getFormattedAlt(analysis.diffElevationDown, app));
}
if (analysis.diffElevationUp > eleThreshold) {
name += GpxUiHelper.getColorValue(ascClr, " \u2191 " +
OsmAndFormatter.getFormattedAlt(analysis.diffElevationUp, app));
}
}
item.name = name;
item.locationStart = analysis.locationStart;
item.locationEnd = analysis.locationEnd;
list.add(item);
}
}
}
private static String formatSecondarySplitName(double metricEnd, GpxDisplayGroup group, OsmandApplication app) {
if (group.isSplitDistance()) {
return Algorithms.formatDuration((int) metricEnd, app.accessibilityEnabled());
} else {
return OsmAndFormatter.getFormattedDistance((float) metricEnd, app);
}
}
private static String formatSplitName(double metricEnd, GpxDisplayGroup group, OsmandApplication app) {
if (group.isSplitDistance()) {
MetricsConstants mc = app.getSettings().METRIC_SYSTEM.get();
if (mc == MetricsConstants.KILOMETERS_AND_METERS) {
final double sd = group.getSplitDistance();
int digits = sd < 100 ? 2 : (sd < 1000 ? 1 : 0);
int rem1000 = (int) (metricEnd + 0.5) % 1000;
if (rem1000 > 1 && digits < 1) {
digits = 1;
}
int rem100 = (int) (metricEnd + 0.5) % 100;
if (rem100 > 1 && digits < 2) {
digits = 2;
}
return OsmAndFormatter.getFormattedRoundDistanceKm((float) metricEnd, digits, app);
} else {
return OsmAndFormatter.getFormattedDistance((float) metricEnd, app);
}
} else {
return Algorithms.formatDuration((int) metricEnd, app.accessibilityEnabled());
}
}
public SelectedGpxFile getSelectedFileByPath(String path) {
for (SelectedGpxFile s : selectedGPXFiles) {
if (s.getGpxFile().path.equals(path)) {
return s;
}
}
return null;
}
public SelectedGpxFile getSelectedFileByName(String path) {
for (SelectedGpxFile s : selectedGPXFiles) {
if (s.getGpxFile().path.endsWith("/" + path)) {
return s;
}
}
return null;
}
public SelectedGpxFile getSelectedCurrentRecordingTrack() {
for (SelectedGpxFile s : selectedGPXFiles) {
if (s.isShowCurrentTrack()) {
return s;
}
}
return null;
}
public void setGpxFileToDisplay(GPXFile... gpxs) {
// special case for gpx current route
for (GPXFile gpx : gpxs) {
selectGpxFileImpl(gpx, true, false);
}
saveCurrentSelections();
}
public void loadGPXTracks(IProgress p) {
String load = app.getSettings().SELECTED_GPX.get();
if (!Algorithms.isEmpty(load)) {
try {
JSONArray ar = new JSONArray(load);
boolean save = false;
for (int i = 0; i < ar.length(); i++) {
JSONObject obj = ar.getJSONObject(i);
if (obj.has(FILE)) {
File fl = new File(obj.getString(FILE));
if (p != null) {
p.startTask(getString(R.string.loading_smth, fl.getName()), -1);
}
GPXFile gpx = GPXUtilities.loadGPXFile(app, fl);
if (obj.has(COLOR)) {
int clr = Algorithms.parseColor(obj.getString(COLOR));
gpx.setColor(clr);
}
if (gpx.warning != null) {
save = true;
} else {
selectGpxFile(gpx, true, false);
}
} else if (obj.has(CURRENT_TRACK)) {
selectedGPXFiles.add(savingTrackHelper.getCurrentTrack());
}
}
if (save) {
saveCurrentSelections();
}
} catch (Exception e) {
app.getSettings().SELECTED_GPX.set("");
e.printStackTrace();
}
}
}
private void saveCurrentSelections() {
JSONArray ar = new JSONArray();
for (SelectedGpxFile s : selectedGPXFiles) {
if (s.gpxFile != null && !s.notShowNavigationDialog) {
JSONObject obj = new JSONObject();
try {
if (s.isShowCurrentTrack()) {
obj.put(CURRENT_TRACK, true);
} else if (!Algorithms.isEmpty(s.gpxFile.path)) {
obj.put(FILE, s.gpxFile.path);
if (s.gpxFile.getColor(0) != 0) {
obj.put(COLOR, Algorithms.colorToString(s.gpxFile.getColor(0)));
}
}
} catch (JSONException e) {
e.printStackTrace();
}
ar.put(obj);
}
}
app.getSettings().SELECTED_GPX.set(ar.toString());
}
private SelectedGpxFile selectGpxFileImpl(GPXFile gpx, boolean show, boolean notShowNavigationDialog) {
boolean displayed;
SelectedGpxFile sf;
if (gpx != null && gpx.showCurrentTrack) {
sf = savingTrackHelper.getCurrentTrack();
sf.notShowNavigationDialog = notShowNavigationDialog;
displayed = selectedGPXFiles.contains(sf);
} else {
assert gpx != null;
sf = getSelectedFileByPath(gpx.path);
displayed = sf != null;
if (show && sf == null) {
sf = new SelectedGpxFile();
sf.setGpxFile(gpx);
sf.notShowNavigationDialog = notShowNavigationDialog;
}
}
if (displayed != show) {
if (show) {
selectedGPXFiles.add(sf);
} else {
selectedGPXFiles.remove(sf);
}
}
return sf;
}
public SelectedGpxFile selectGpxFile(GPXFile gpx, boolean show, boolean notShowNavigationDialog) {
SelectedGpxFile sf = selectGpxFileImpl(gpx, show, notShowNavigationDialog);
saveCurrentSelections();
return sf;
}
public static class SelectedGpxFile {
public boolean notShowNavigationDialog = false;
private boolean showCurrentTrack;
private GPXFile gpxFile;
private int color;
private GPXTrackAnalysis trackAnalysis;
private long modifiedTime = -1;
private List<TrkSegment> processedPointsToDisplay = new ArrayList<>();
private boolean routePoints;
private List<GpxDisplayGroup> displayGroups;
public void setGpxFile(GPXFile gpxFile) {
this.gpxFile = gpxFile;
if (gpxFile.tracks.size() > 0) {
this.color = gpxFile.tracks.get(0).getColor(0);
}
processPoints();
}
public GPXTrackAnalysis getTrackAnalysis() {
if (modifiedTime != gpxFile.modifiedTime) {
update();
}
return trackAnalysis;
}
private void update() {
modifiedTime = gpxFile.modifiedTime;
trackAnalysis = gpxFile.getAnalysis(
Algorithms.isEmpty(gpxFile.path) ? System.currentTimeMillis() :
new File(gpxFile.path).lastModified());
displayGroups = null;
}
public void processPoints() {
update();
this.processedPointsToDisplay = gpxFile.proccessPoints();
if (this.processedPointsToDisplay.isEmpty()) {
this.processedPointsToDisplay = gpxFile.processRoutePoints();
routePoints = !this.processedPointsToDisplay.isEmpty();
}
}
public boolean isRoutePoints() {
return routePoints;
}
public List<TrkSegment> getPointsToDisplay() {
return processedPointsToDisplay;
}
public List<TrkSegment> getModifiablePointsToDisplay() {
return processedPointsToDisplay;
}
public GPXFile getGpxFile() {
return gpxFile;
}
public GPXFile getModifiableGpxFile() {
// call process points after
return gpxFile;
}
public boolean isShowCurrentTrack() {
return showCurrentTrack;
}
public void setShowCurrentTrack(boolean showCurrentTrack) {
this.showCurrentTrack = showCurrentTrack;
}
public int getColor() {
return color;
}
public List<GpxDisplayGroup> getDisplayGroups() {
if (modifiedTime != gpxFile.modifiedTime) {
update();
}
return displayGroups;
}
public void setDisplayGroups(List<GpxDisplayGroup> displayGroups) {
if (modifiedTime != gpxFile.modifiedTime) {
update();
}
this.displayGroups = displayGroups;
}
}
public enum GpxDisplayItemType {
TRACK_SEGMENT,
TRACK_POINTS,
TRACK_ROUTE_POINTS
}
public static class GpxDisplayGroup {
private GpxDisplayItemType type = GpxDisplayItemType.TRACK_SEGMENT;
private List<GpxDisplayItem> list = new ArrayList<>();
private GPXFile gpx;
private String gpxName;
private String name;
private String description;
private Track track;
private double splitDistance = -1;
private int splitTime = -1;
private int color;
public GpxDisplayGroup(GPXFile gpx) {
this.gpx = gpx;
}
public void setTrack(Track track) {
this.track = track;
}
public GPXFile getGpx() {
return gpx;
}
public Track getTrack() {
return track;
}
public GpxDisplayGroup cloneInstance() {
GpxDisplayGroup group = new GpxDisplayGroup(gpx);
group.type = type;
group.name = name;
group.description = description;
group.track = track;
group.list = new ArrayList<>(list);
return group;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public void setName(String name) {
this.name = name;
}
public String getGpxName() {
return gpxName;
}
public String getName() {
return name;
}
public List<GpxDisplayItem> getModifiableList() {
return list;
}
public GpxDisplayItemType getType() {
return type;
}
public void setType(GpxDisplayItemType type) {
this.type = type;
}
public boolean isSplitDistance() {
return splitDistance > 0;
}
public double getSplitDistance() {
return splitDistance;
}
public boolean isSplitTime() {
return splitTime > 0;
}
public int getSplitTime() {
return splitTime;
}
public String getGroupName() {
return name;
}
public void noSplit(OsmandApplication app) {
list.clear();
splitDistance = -1;
splitTime = -1;
processGroupTrack(app, this);
}
public void splitByDistance(OsmandApplication app, double meters) {
list.clear();
splitDistance = meters;
splitTime = -1;
processGroupTrack(app, this);
}
public void splitByTime(OsmandApplication app, int seconds) {
list.clear();
splitDistance = -1;
splitTime = seconds;
processGroupTrack(app, this);
}
public int getColor() {
return color;
}
}
public static class GpxDisplayItem {
public GPXTrackAnalysis analysis;
public GpxDisplayGroup group;
public WptPt locationStart;
public WptPt locationEnd;
public double splitMetric = -1;
public double secondarySplitMetric = -1;
public String splitName;
public String name;
public String description;
public String url;
public Bitmap image;
public boolean expanded;
public boolean route;
public boolean wasHidden = true;
public WptPt locationOnMap;
public GPXDataSetType[] chartTypes;
public GPXDataSetAxisType chartAxisType = GPXDataSetAxisType.DISTANCE;
public Matrix chartMatrix;
public float chartHighlightPos = -1f;
}
}