Workout: map improvments

This commit is contained in:
a0z
2025-04-04 22:02:46 +00:00
committed by José Rebelo
parent 8cec2282a8
commit 68b51627d2
7 changed files with 148 additions and 114 deletions
+4 -4
View File
@@ -173,14 +173,14 @@
android:name=".activities.DashboardPreferencesActivity"
android:label="@string/dashboard_settings"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.maps.MapsTrackActivity"
android:label="@string/maps"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.maps.MapsSettingsActivity"
android:label="@string/maps_settings"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.maps.MapsTrackActivity"
android:label="@string/maps"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".activities.SleepAsAndroidPreferencesActivity"
android:label="@string/sleepasandroid_settings"
@@ -1,4 +1,4 @@
/* Copyright (C) 2020-2024 José Rebelo, Petr Vaněk
/* Copyright (C) 2020-2024 José Rebelo, Petr Vaněk, a0z
This file is part of Gadgetbridge.
@@ -17,17 +17,17 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
@@ -41,11 +41,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Collectors;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.maps.MapsTrackActivity;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
@@ -57,26 +55,31 @@ import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParseException;
import nodomain.freeyourgadget.gadgetbridge.util.gpx.GpxParser;
import nodomain.freeyourgadget.gadgetbridge.util.maps.MapsManager;
import static android.graphics.Bitmap.createBitmap;
public class ActivitySummariesGpsFragment extends AbstractGBFragment {
private static final Logger LOG = LoggerFactory.getLogger(ActivitySummariesGpsFragment.class);
private final int CANVAS_SIZE = 360;
private MapView mapView;
private TextView gpsWarning;
private File inputFile;
private long downTime;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_gps, container, false);
gpsWarning = rootView.findViewById(R.id.gpsWarning);
mapView = rootView.findViewById(R.id.activitygpsview);
//mapView.getMapScaleBar().setVisible(true);
mapView.setBuiltInZoomControls(false);
MapsManager mapsManager = new MapsManager(requireContext());
mapsManager.loadMaps(mapView);
if (mapsManager.isMapLoaded()) {
gpsWarning.setVisibility(View.GONE);
}
if (inputFile != null) {
processInBackgroundThread();
}
@@ -92,47 +95,51 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
private void processInBackgroundThread() {
new Thread(() -> {
final ArrayList<GPSCoordinate> points = new ArrayList<>();
if (inputFile.getName().endsWith(".gpx")) {
try (FileInputStream inputStream = new FileInputStream(inputFile)) {
final GpxParser gpxParser = new GpxParser(inputStream);
points.addAll(gpxParser.getGpxFile().getPoints());
} catch (final IOException e) {
LOG.error("Failed to open {}", inputFile, e);
return;
} catch (final GpxParseException e) {
LOG.error("Failed to parse gpx file", e);
return;
}
} else if (inputFile.getName().endsWith(".fit")) {
try {
FitFile fitFile = FitFile.parseIncoming(inputFile);
for (final RecordData record : fitFile.getRecords()) {
if (record instanceof FitRecord) {
final ActivityPoint activityPoint = ((FitRecord) record).toActivityPoint();
if (activityPoint.getLocation() != null) {
points.add(activityPoint.getLocation());
}
}
}
} catch (final IOException e) {
LOG.error("Failed to open {}", inputFile, e);
return;
} catch (final Exception e) {
LOG.error("Failed to parse fit file", e);
return;
}
} else {
LOG.warn("Unknown file type {}", inputFile.getName());
return;
}
final List<GPSCoordinate> points = getActivityPoints(inputFile)
.stream()
.map(ActivityPoint::getLocation)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (!points.isEmpty()) {
drawTrack(mapView, points);
drawTrack(mapView, (ArrayList<? extends GPSCoordinate>) points);
}
}).start();
}
public static List<ActivityPoint> getActivityPoints(final File trackFile) {
final List<ActivityPoint> points = new ArrayList<>();
if (trackFile == null) {
return points;
}
if (trackFile.getName().endsWith(".gpx")) {
try (FileInputStream inputStream = new FileInputStream(trackFile)) {
final GpxParser gpxParser = new GpxParser(inputStream);
points.addAll(gpxParser.getGpxFile().getActivityPoints());
} catch (final IOException e) {
LOG.error("Failed to open {}", trackFile, e);
} catch (final GpxParseException e) {
LOG.error("Failed to parse gpx file", e);
}
} else if (trackFile.getName().endsWith(".fit")) {
try {
FitFile fitFile = FitFile.parseIncoming(trackFile);
for (final RecordData record : fitFile.getRecords()) {
if (record instanceof FitRecord) {
points.add(((FitRecord) record).toActivityPoint());
}
}
} catch (final IOException e) {
LOG.error("Failed to open {}", trackFile, e);
} catch (final Exception e) {
LOG.error("Failed to parse fit file", e);
}
} else {
LOG.warn("Unknown file type {}", trackFile.getName());
}
return points;
}
private void drawTrack(MapView mapView, final ArrayList<? extends GPSCoordinate> trackPoints) {
double maxLat = (Collections.max(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
double minLat = (Collections.min(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
@@ -142,44 +149,36 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
.map(p -> new LatLong(p.getLatitude(), p.getLongitude()))
.collect(Collectors.toList());
Paint p = AndroidGraphicFactory.INSTANCE.createPaint();
p.setColor(R.color.hrv_status_char_line_color);
Polyline polyline = new Polyline(p, AndroidGraphicFactory.INSTANCE);
polyline.setPoints(latlongs);
mapView.addLayer(polyline);
Paint paint = AndroidGraphicFactory.INSTANCE.createPaint();
paint.setColor(getResources().getColor(R.color.chart_activity_light));
paint.setStrokeWidth(8);
paint.setStyle(Style.STROKE);
Polyline polyline = new Polyline(paint, AndroidGraphicFactory.INSTANCE);
polyline.addPoints(latlongs);
mapView.getLayerManager().getLayers().add(polyline);
mapView.getLayerManager().redrawLayers();
mapView.setCenter(new LatLong(minLat + (maxLat - minLat) / 2, minLon + (maxLon - minLon) / 2));
mapView.setZoomLevel((byte) 12);
mapView.setZoomLevel((byte) 13);
mapView.setOnTouchListener((a, b) -> {
final Intent startIntent = new Intent(requireContext(), MapsTrackActivity.class);
startIntent.putParcelableArrayListExtra("points", trackPoints);
requireContext().startActivity(startIntent);
final long DOUBLE_TAP_THRESHOLD = 300;
mapView.setOnTouchListener((view, event) -> {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
long currentTime = System.currentTimeMillis();
if ((currentTime - downTime) <= DOUBLE_TAP_THRESHOLD) {
Intent intent = new Intent(getContext(), MapsTrackActivity.class);
intent.putExtra("file", inputFile);
startActivity(intent);
}
downTime = currentTime;
break;
}
return true;
});
mapView.setOnClickListener(v -> {
final Intent startIntent = new Intent(requireContext(), MapsTrackActivity.class);
startIntent.putParcelableArrayListExtra("points", trackPoints);
requireContext().startActivity(startIntent);
});
}
private Canvas createCanvas(ImageView imageView) {
Bitmap bitmap = createBitmap(CANVAS_SIZE, CANVAS_SIZE, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(GBApplication.getWindowBackgroundColor(requireActivity()));
//frame around, but it doesn't look so nice
/*
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStrokeWidth(0);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(getResources().getColor(R.color.chart_activity_light));
canvas.drawRect(0,0,360,360,paint);
*/
imageView.setImageBitmap(bitmap);
imageView.setScaleY(-1f); //flip the canvas
return canvas;
mapView.invalidate();
}
@Nullable
@@ -187,4 +186,5 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
protected CharSequence getTitle() {
return null;
}
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2024 José Rebelo
/* Copyright (C) 2024 José Rebelo, a0z
This file is part of Gadgetbridge.
@@ -22,34 +22,54 @@ import android.view.MenuItem;
import androidx.annotation.NonNull;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.layer.overlay.Polyline;
import java.util.ArrayList;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesGpsFragment;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityPoint;
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
import nodomain.freeyourgadget.gadgetbridge.util.maps.MapsManager;
public class MapsTrackActivity extends AbstractGBActivity {
private MapView mapView;
private File file;
public static boolean isActivityOpen = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps_track);
if (isActivityOpen) {
finish();
return;
}
isActivityOpen = true;
mapView = findViewById(R.id.activitygpsview);
MapsManager mapsManager = new MapsManager(this);
mapsManager.loadMaps(mapView);
final ArrayList<? extends GPSCoordinate> trackPoints = getIntent().getParcelableArrayListExtra("points");
file = (File) getIntent().getExtras().get("file");
final List<GPSCoordinate> trackPoints = ActivitySummariesGpsFragment.getActivityPoints(file)
.stream()
.map(ActivityPoint::getLocation)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (trackPoints.isEmpty()) {
return;
}
double maxLat = (Collections.max(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
double minLat = (Collections.min(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
@@ -59,9 +79,11 @@ public class MapsTrackActivity extends AbstractGBActivity {
.map(p -> new LatLong(p.getLatitude(), p.getLongitude()))
.collect(Collectors.toList());
Paint p = AndroidGraphicFactory.INSTANCE.createPaint();
p.setColor(R.color.hrv_status_char_line_color);
Polyline polyline = new Polyline(p, AndroidGraphicFactory.INSTANCE);
Paint paint = AndroidGraphicFactory.INSTANCE.createPaint();
paint.setColor(getResources().getColor(R.color.chart_activity_light));
paint.setStrokeWidth(8);
paint.setStyle(Style.STROKE);
Polyline polyline = new Polyline(paint, AndroidGraphicFactory.INSTANCE);
polyline.setPoints(latlongs);
mapView.addLayer(polyline);
@@ -89,4 +111,13 @@ public class MapsTrackActivity extends AbstractGBActivity {
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
synchronized (MapsTrackActivity.class) {
isActivityOpen = false;
}
}
}
@@ -18,10 +18,13 @@ package nodomain.freeyourgadget.gadgetbridge.util.maps;
import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
import androidx.documentfile.provider.DocumentFile;
import org.mapsforge.core.graphics.Canvas;
import org.mapsforge.core.graphics.Color;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.Point;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
@@ -34,11 +37,8 @@ import org.slf4j.LoggerFactory;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
public final class MapsManager {
@@ -48,6 +48,8 @@ public final class MapsManager {
private final Context mContext;
private boolean isMapLoaded = false;
public MapsManager(final Context context) {
this.mContext = context;
}
@@ -88,6 +90,7 @@ public final class MapsManager {
assert inputStream != null;
MapFile mapFile = new MapFile(inputStream, 0, null);
multiMapDataStore.addMapDataStore(mapFile, true, true);
isMapLoaded = true;
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
@@ -97,13 +100,22 @@ public final class MapsManager {
mapView.getModel().displayModel.getTileSize(), 1f,
mapView.getModel().frameBufferModel.getOverdrawFactor());
TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, multiMapDataStore,
mapView.getModel().mapViewPosition, AndroidGraphicFactory.INSTANCE);
TileRendererLayer tileRendererLayer = new TileRendererLayer(
tileCache, multiMapDataStore,
mapView.getModel().mapViewPosition, true, false, false, AndroidGraphicFactory.INSTANCE) {
@Override
public void draw(BoundingBox boundingBox, byte zoomLevel, Canvas canvas, Point topLeftPoint) {
// Clear the canvas with the desired background color
canvas.fillColor(Color.TRANSPARENT);
super.draw(boundingBox, zoomLevel, canvas, topLeftPoint);
}
};
tileRendererLayer.setXmlRenderTheme(MapTheme.DEFAULT);
mapView.getLayerManager().getLayers().add(tileRendererLayer);
}
// TODO on exit mapView.destroyAll();
//AndroidGraphicFactory.clearResourceMemoryCache();
public boolean isMapLoaded() {
return isMapLoaded;
}
}
@@ -4,7 +4,6 @@
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.maps.MapsTrackActivity">
<org.mapsforge.map.android.view.MapView
android:id="@+id/activitygpsview"
android:layout_width="match_parent"
+8 -17
View File
@@ -17,34 +17,25 @@
android:gravity="center"
android:text="@string/gps_track"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"
android:orientation="vertical">
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:id="@+id/gpsWarning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Maps not found" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="20dp"
android:orientation="vertical">
android:text="@string/maps_not_found" />
<org.mapsforge.map.android.view.MapView
android:id="@+id/activitygpsview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" />
android:layout_height="300dp"
android:orientation="vertical"
/>
</LinearLayout>
</LinearLayout>
</RelativeLayout>
+1
View File
@@ -3559,6 +3559,7 @@
<string name="pref_garmin_agps_help">The list below contains all URLs requested by the watch for AGPS updates. You can select a file from the phone\'s storage that will be sent to the watch when it requests an update.</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="loading">Loading…</string>
<string name="maps_not_found">Maps not found</string>
<string name="toggle_debug_mode">Toggle debug mode</string>
<string name="share_debug_info">Share debug info</string>
<string name="realtime_settings">Realtime settings</string>