mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Workout: offline map init
This commit is contained in:
@@ -248,6 +248,13 @@ dependencies {
|
||||
implementation 'com.android.volley:volley:1.2.1'
|
||||
implementation 'org.msgpack:msgpack-core:0.9.9'
|
||||
|
||||
implementation 'com.github.mapsforge.mapsforge:mapsforge-core:0.21.0'
|
||||
implementation 'com.github.mapsforge.mapsforge:mapsforge-map:0.21.0'
|
||||
implementation 'com.github.mapsforge.mapsforge:mapsforge-map-reader:0.21.0'
|
||||
implementation 'com.github.mapsforge.mapsforge:mapsforge-themes:0.21.0'
|
||||
implementation 'com.github.mapsforge.mapsforge:mapsforge-map-android:0.21.0'
|
||||
implementation 'com.caverock:androidsvg:1.4'
|
||||
|
||||
// Bouncy Castle is included directly in GB, to avoid pulling the entire dependency
|
||||
// It's included in the org.bouncycastle.shaded package, to fix conflicts with roboelectric
|
||||
//implementation 'org.bouncycastle:bcpkix-jdk18on:1.76'
|
||||
|
||||
@@ -173,6 +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.SleepAsAndroidPreferencesActivity"
|
||||
android:label="@string/sleepasandroid_settings"
|
||||
|
||||
+75
-68
@@ -16,9 +16,9 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -27,6 +27,11 @@ import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.mapsforge.core.graphics.Paint;
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -38,9 +43,11 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.FitFile;
|
||||
@@ -48,21 +55,28 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.RecordDat
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.fit.messages.FitRecord;
|
||||
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 ImageView gpsView;
|
||||
private final int CANVAS_SIZE = 360;
|
||||
private MapView mapView;
|
||||
private File inputFile;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_gps, container, false);
|
||||
gpsView = rootView.findViewById(R.id.activitygpsview);
|
||||
mapView = rootView.findViewById(R.id.activitygpsview);
|
||||
//mapView.getMapScaleBar().setVisible(true);
|
||||
mapView.setBuiltInZoomControls(false);
|
||||
|
||||
MapsManager mapsManager = new MapsManager(requireContext());
|
||||
mapsManager.loadMaps(mapView);
|
||||
|
||||
if (inputFile != null) {
|
||||
processInBackgroundThread();
|
||||
}
|
||||
@@ -71,88 +85,83 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
|
||||
|
||||
public void set_data(File inputFile) {
|
||||
this.inputFile = inputFile;
|
||||
if (gpsView != null) { //first fragment inflate is AFTER this is called
|
||||
if (mapView != null) { //first fragment inflate is AFTER this is called
|
||||
processInBackgroundThread();
|
||||
}
|
||||
}
|
||||
|
||||
private void processInBackgroundThread() {
|
||||
final Canvas canvas = createCanvas(gpsView);
|
||||
new Thread(() -> {
|
||||
final List<GPSCoordinate> points = getActivityPoints(inputFile)
|
||||
.stream()
|
||||
.map(ActivityPoint::getLocation)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
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;
|
||||
}
|
||||
|
||||
if (!points.isEmpty()) {
|
||||
drawTrack(canvas, points);
|
||||
drawTrack(mapView, 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(Canvas canvas, List<? extends GPSCoordinate> trackPoints) {
|
||||
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();
|
||||
double maxLon = (Collections.max(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
|
||||
double minLon = (Collections.min(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
|
||||
double maxAlt = (Collections.max(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
|
||||
double minAlt = (Collections.min(trackPoints, new GPSCoordinate.compareElevation())).getAltitude();
|
||||
float scale_factor_w = (float) ((maxLon - minLon) / (maxLat - minLat));
|
||||
float scale_factor_h = (float) ((maxLat - minLat) / (maxLon - minLon));
|
||||
List<LatLong> latlongs = trackPoints.stream()
|
||||
.map(p -> new LatLong(p.getLatitude(), p.getLongitude()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (scale_factor_h > scale_factor_w) { //scaling to draw proportionally
|
||||
scale_factor_h = 1;
|
||||
} else {
|
||||
scale_factor_w = 1;
|
||||
}
|
||||
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);
|
||||
|
||||
mapView.setCenter(new LatLong(minLat + (maxLat - minLat) / 2, minLon + (maxLon - minLon) / 2));
|
||||
mapView.setZoomLevel((byte) 12);
|
||||
|
||||
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
paint.setStrokeWidth(1);
|
||||
paint.setColor(getResources().getColor(R.color.chart_activity_light));
|
||||
|
||||
for (GPSCoordinate p : trackPoints) {
|
||||
float lat = (float) ((p.getLatitude() - minLat) / (maxLat - minLat));
|
||||
float lon = (float) ((p.getLongitude() - minLon) / (maxLon - minLon));
|
||||
float alt = (float) ((p.getAltitude() - minAlt) / (maxAlt - minAlt));
|
||||
paint.setStrokeWidth(1 + alt); //make thicker with higher altitude, we could do more here
|
||||
canvas.drawPoint(CANVAS_SIZE * lon * scale_factor_w, CANVAS_SIZE * lat * scale_factor_h, paint);
|
||||
}
|
||||
mapView.setOnTouchListener((a, b) -> {
|
||||
final Intent startIntent = new Intent(requireContext(), MapsTrackActivity.class);
|
||||
startIntent.putParcelableArrayListExtra("points", trackPoints);
|
||||
requireContext().startActivity(startIntent);
|
||||
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) {
|
||||
@@ -178,6 +187,4 @@ public class ActivitySummariesGpsFragment extends AbstractGBFragment {
|
||||
protected CharSequence getTitle() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -65,6 +65,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryPairingPreferenceActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.maps.MapsSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
|
||||
@@ -372,6 +373,15 @@ public class SettingsActivity extends AbstractSettingsActivityV2 {
|
||||
});
|
||||
}
|
||||
|
||||
pref = findPreference("pref_category_maps");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
Intent enableIntent = new Intent(requireContext(), MapsSettingsActivity.class);
|
||||
startActivity(enableIntent);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
pref = findPreference("pref_category_sleepasandroid");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/* Copyright (C) 2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.maps;
|
||||
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2;
|
||||
|
||||
public class MapsSettingsActivity extends AbstractSettingsActivityV2 {
|
||||
@Override
|
||||
protected String fragmentTag() {
|
||||
return MapsSettingsFragment.FRAGMENT_TAG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PreferenceFragmentCompat newFragment() {
|
||||
return new MapsSettingsFragment();
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/* Copyright (C) 2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.maps;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.maps.MapsManager;
|
||||
|
||||
public class MapsSettingsFragment extends AbstractPreferenceFragment {
|
||||
static final String FRAGMENT_TAG = "MAP_SETTINGS_FRAGMENT";
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(@Nullable final Bundle savedInstanceState, @Nullable final String rootKey) {
|
||||
setPreferencesFromResource(R.xml.map_settings, rootKey);
|
||||
|
||||
final SharedPreferences prefs = getPreferenceManager().getSharedPreferences();
|
||||
if (prefs == null) {
|
||||
requireActivity().finish();
|
||||
return;
|
||||
}
|
||||
|
||||
final Preference prefDownload = Objects.requireNonNull(findPreference("maps_download"));
|
||||
prefDownload.setOnPreferenceClickListener(preference -> {
|
||||
startActivity(new Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse("https://ftp-stud.hs-esslingen.de/pub/Mirrors/download.mapsforge.org/maps/v5/")
|
||||
));
|
||||
return true;
|
||||
});
|
||||
|
||||
final Preference prefFolder = Objects.requireNonNull(findPreference(MapsManager.PREF_MAPS_FOLDER));
|
||||
final ActivityResultLauncher<Uri> mapsFolderChooser = registerForActivityResult(
|
||||
new ActivityResultContracts.OpenDocumentTree(),
|
||||
localUri -> {
|
||||
LOG.info("Maps folder: {}", localUri);
|
||||
if (localUri != null) {
|
||||
requireContext().getContentResolver().takePersistableUriPermission(localUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
prefs.edit()
|
||||
.putString(MapsManager.PREF_MAPS_FOLDER, localUri.toString())
|
||||
.apply();
|
||||
prefFolder.setSummary(localUri.toString());
|
||||
}
|
||||
}
|
||||
);
|
||||
final String currentFolder = prefs.getString(MapsManager.PREF_MAPS_FOLDER, "");
|
||||
prefFolder.setSummary(currentFolder);
|
||||
prefFolder.setOnPreferenceClickListener(preference -> {
|
||||
mapsFolderChooser.launch(null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/* Copyright (C) 2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.maps;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.mapsforge.core.graphics.Paint;
|
||||
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.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GPSCoordinate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.maps.MapsManager;
|
||||
|
||||
public class MapsTrackActivity extends AbstractGBActivity {
|
||||
private MapView mapView;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_maps_track);
|
||||
|
||||
mapView = findViewById(R.id.activitygpsview);
|
||||
MapsManager mapsManager = new MapsManager(this);
|
||||
mapsManager.loadMaps(mapView);
|
||||
|
||||
final ArrayList<? extends GPSCoordinate> trackPoints = getIntent().getParcelableArrayListExtra("points");
|
||||
|
||||
double maxLat = (Collections.max(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
|
||||
double minLat = (Collections.min(trackPoints, new GPSCoordinate.compareLatitude())).getLatitude();
|
||||
double maxLon = (Collections.max(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
|
||||
double minLon = (Collections.min(trackPoints, new GPSCoordinate.compareLongitude())).getLongitude();
|
||||
List<LatLong> latlongs = trackPoints.stream()
|
||||
.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);
|
||||
|
||||
mapView.setCenter(new LatLong(minLat + (maxLat - minLat) / 2, minLon + (maxLon - minLon) / 2));
|
||||
mapView.setZoomLevel((byte) 12);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
// back button
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull final Bundle state) {
|
||||
super.onSaveInstanceState(state);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,10 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import android.location.Location;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -24,7 +28,7 @@ import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Comparator;
|
||||
|
||||
public class GPSCoordinate {
|
||||
public class GPSCoordinate implements Parcelable {
|
||||
private final double latitude;
|
||||
private final double longitude;
|
||||
private final double altitude;
|
||||
@@ -50,6 +54,12 @@ public class GPSCoordinate {
|
||||
this(longitude, latitude, UNKNOWN_ALTITUDE);
|
||||
}
|
||||
|
||||
protected GPSCoordinate(Parcel in) {
|
||||
latitude = in.readDouble();
|
||||
longitude = in.readDouble();
|
||||
altitude = in.readDouble();
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return latitude;
|
||||
}
|
||||
@@ -156,6 +166,30 @@ public class GPSCoordinate {
|
||||
return "lon: " + formatLocation(longitude) + ", lat: " + formatLocation(latitude) + ", alt: " + formatLocation(altitude) + "m";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull final Parcel dest, final int flags) {
|
||||
dest.writeDouble(latitude);
|
||||
dest.writeDouble(longitude);
|
||||
dest.writeDouble(altitude);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Creator<GPSCoordinate> CREATOR = new Creator<GPSCoordinate>() {
|
||||
@Override
|
||||
public GPSCoordinate createFromParcel(Parcel in) {
|
||||
return new GPSCoordinate(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GPSCoordinate[] newArray(int size) {
|
||||
return new GPSCoordinate[size];
|
||||
}
|
||||
};
|
||||
|
||||
public static class compareLatitude implements Comparator<GPSCoordinate> {
|
||||
@Override
|
||||
public int compare(GPSCoordinate trkPt1, GPSCoordinate trkPt2) {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util.maps;
|
||||
|
||||
import org.mapsforge.map.rendertheme.XmlRenderTheme;
|
||||
import org.mapsforge.map.rendertheme.XmlRenderThemeMenuCallback;
|
||||
import org.mapsforge.map.rendertheme.XmlThemeResourceProvider;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public enum MapTheme implements XmlRenderTheme {
|
||||
DEFAULT("/assets/mapsforge/default.xml"),
|
||||
OSMARENDER("/assets/mapsforge/osmarender.xml"),
|
||||
;
|
||||
|
||||
private XmlRenderThemeMenuCallback menuCallback;
|
||||
private final String path;
|
||||
|
||||
MapTheme(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlRenderThemeMenuCallback getMenuCallback() {
|
||||
return menuCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRelativePathPrefix() {
|
||||
return "/assets/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getRenderThemeAsStream() {
|
||||
return getClass().getResourceAsStream(this.path);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XmlThemeResourceProvider getResourceProvider() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMenuCallback(final XmlRenderThemeMenuCallback menuCallback) {
|
||||
this.menuCallback = menuCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setResourceProvider(final XmlThemeResourceProvider resourceProvider) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/* Copyright (C) 2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
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.map.android.graphics.AndroidGraphicFactory;
|
||||
import org.mapsforge.map.android.util.AndroidUtil;
|
||||
import org.mapsforge.map.android.view.MapView;
|
||||
import org.mapsforge.map.datastore.MultiMapDataStore;
|
||||
import org.mapsforge.map.layer.cache.TileCache;
|
||||
import org.mapsforge.map.layer.renderer.TileRendererLayer;
|
||||
import org.mapsforge.map.reader.MapFile;
|
||||
import org.slf4j.Logger;
|
||||
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 {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MapsManager.class);
|
||||
|
||||
public static final String PREF_MAPS_FOLDER = "maps_folder";
|
||||
|
||||
private final Context mContext;
|
||||
|
||||
public MapsManager(final Context context) {
|
||||
this.mContext = context;
|
||||
}
|
||||
|
||||
public void loadMaps(final MapView mapView) {
|
||||
AndroidGraphicFactory.createInstance(GBApplication.app());
|
||||
GBPrefs prefs = GBApplication.getPrefs();
|
||||
|
||||
String folderUri = prefs.getString(PREF_MAPS_FOLDER, "");
|
||||
if (folderUri.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final DocumentFile folder = DocumentFile.fromTreeUri(mContext, Uri.parse(folderUri));
|
||||
if (folder == null || folder.listFiles().length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
MultiMapDataStore multiMapDataStore = new MultiMapDataStore(MultiMapDataStore.DataPolicy.RETURN_ALL);
|
||||
|
||||
final DocumentFile[] documentFiles = folder.listFiles();
|
||||
|
||||
LOG.debug("Got {} map files", documentFiles.length);
|
||||
|
||||
for (final DocumentFile documentFile : documentFiles) {
|
||||
if (!documentFile.canRead()) {
|
||||
continue;
|
||||
}
|
||||
assert documentFile.getName() != null;
|
||||
if (!documentFile.getName().endsWith(".map")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG.debug("Loading {}", documentFile.getName());
|
||||
|
||||
try {
|
||||
FileInputStream inputStream = (FileInputStream) mContext.getContentResolver().openInputStream(documentFile.getUri());
|
||||
assert inputStream != null;
|
||||
MapFile mapFile = new MapFile(inputStream, 0, null);
|
||||
multiMapDataStore.addMapDataStore(mapFile, true, true);
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
TileCache tileCache = AndroidUtil.createTileCache(mContext, "mapcache",
|
||||
mapView.getModel().displayModel.getTileSize(), 1f,
|
||||
mapView.getModel().frameBufferModel.getOverdrawFactor());
|
||||
|
||||
TileRendererLayer tileRendererLayer = new TileRendererLayer(tileCache, multiMapDataStore,
|
||||
mapView.getModel().mapViewPosition, AndroidGraphicFactory.INSTANCE);
|
||||
tileRendererLayer.setXmlRenderTheme(MapTheme.DEFAULT);
|
||||
|
||||
mapView.getLayerManager().getLayers().add(tileRendererLayer);
|
||||
|
||||
// TODO on exit mapView.destroyAll();
|
||||
//AndroidGraphicFactory.clearResourceMemoryCache();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M5,20h14v-2H5V20zM19,9h-4V3H9v6H5l7,7L19,9z" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,13 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
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"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
</RelativeLayout>
|
||||
@@ -18,11 +18,33 @@
|
||||
android:text="@string/gps_track"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/activitygpsview"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
android:layout_marginBottom="20dp"
|
||||
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">
|
||||
|
||||
<org.mapsforge.map.android.view.MapView
|
||||
android:id="@+id/activitygpsview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -3786,7 +3786,6 @@
|
||||
<string name="ultrahuman_airplane_mode_question">Activate airplane mode?</string>
|
||||
<string name="ultrahuman_airplane_mode_title">airplane mode</string>
|
||||
<string name="ultrahuman_unhandled_error_response">received unhandled response code 0x%x for operation 0x%x</string>
|
||||
|
||||
<string name="huawei_stress_test_enable">Automatic stress test</string>
|
||||
<string name="huawei_stress_test_enable_summary">Enable or disable automatic stress test</string>
|
||||
<string name="huawei_stress_no_calibration_data">No initial data - calibrate first</string>
|
||||
@@ -3801,10 +3800,12 @@
|
||||
<string name="huawei_stress_calibrate_finish">Finish</string>
|
||||
<string name="huawei_stress_calibrate_again">Calibrate again</string>
|
||||
<string name="huawei_stress_calibrate_adjust_score">Adjust score</string>
|
||||
|
||||
<string name="banglejs_notification_missed_call_title">Missed call</string>
|
||||
<string name="banglejs_notification_missed_call_source">Incoming call</string>
|
||||
<string name="banglejs_notification_missed_call_suppressed_caller_id">Unknown caller. ID is suppressed</string>
|
||||
|
||||
<string name="huawei_voice_boost">Voice Boost</string>
|
||||
<string name="maps">Maps</string>
|
||||
<string name="maps_settings">Maps settings</string>
|
||||
<string name="maps_download_title">Download Maps</string>
|
||||
<string name="maps_download_summary">Click here to download map files. They should be saved to the folder selected in the preference below.</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<Preference
|
||||
android:icon="@drawable/ic_download"
|
||||
android:key="maps_download"
|
||||
android:persistent="false"
|
||||
android:summary="@string/maps_download_summary"
|
||||
android:title="@string/maps_download_title" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_folder"
|
||||
android:key="maps_folder"
|
||||
android:title="@string/folder" />
|
||||
</PreferenceScreen>
|
||||
@@ -326,6 +326,11 @@
|
||||
android:summary="@string/pref_summary_opentracks_packagename"
|
||||
android:title="@string/pref_title_opentracks_packagename" />
|
||||
|
||||
<Preference
|
||||
android:key="pref_category_maps"
|
||||
android:title="@string/maps"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_activity_sleep"
|
||||
android:key="pref_category_sleepasandroid"
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
"com.github.wax911.android-emojify:*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"groupName": "mapsforge",
|
||||
"matchPackagePrefixes": [
|
||||
"com.github.mapsforge.mapsforge"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "solarpositioning v2 needs java 17, we need to stay on v0",
|
||||
"allowedVersions": "<2.0",
|
||||
|
||||
Reference in New Issue
Block a user