From 1aa55cb6f2f94a615b482c9f0286720e37200a28 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Wed, 27 May 2026 16:56:23 +0100 Subject: [PATCH 01/21] Deprecate MapFragment methods --- maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt b/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt index 6c8af62df40..df186bf3924 100644 --- a/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt +++ b/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt @@ -31,9 +31,11 @@ interface MapConfigurator { /** Constructs any preference widgets that are specific to this map implementation. */ fun createPrefs(context: Context, settings: Settings): List + @Deprecated("Pref keys should be constants owned by the MapFragment modules") /** Gets the set of keys for preferences that should be watched for changes. */ val prefKeys: Collection + @Deprecated("This should be handled by MapFragment implementations") /** Packs map-related preferences into a Bundle for MapFragment.applyConfig(). */ fun buildConfig(prefs: Settings): Bundle From e18fe4203e8e1bedf343e9daf2120ad76dee34cb Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 11:55:01 +0100 Subject: [PATCH 02/21] Remove reference layer key from GoogleMapConfigurator --- .../googlemaps/GoogleMapConfigurator.java | 6 +- .../collect/googlemaps/GoogleMapFragment.java | 135 ++++++++++++------ .../odk/collect/mapbox/MapboxMapFragment.kt | 3 +- .../java/org/odk/collect/maps/MapViewModel.kt | 20 ++- .../layers/MapFragmentReferenceLayerUtils.kt | 7 + .../collect/osmdroid/OsmDroidMapFragment.java | 3 +- 6 files changed, 119 insertions(+), 55 deletions(-) diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java index 6893db939e0..e884d2596a5 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java @@ -2,6 +2,7 @@ import static org.odk.collect.androidshared.ui.PrefUtils.createListPref; import static org.odk.collect.androidshared.ui.PrefUtils.getInt; +import static java.util.Collections.emptySet; import static kotlin.collections.SetsKt.setOf; import android.content.Context; @@ -89,16 +90,13 @@ private static boolean isGoogleMapsSdkAvailable(Context context) { } @Override public Set getPrefKeys() { - return prefKey.isEmpty() ? setOf(ProjectKeys.KEY_REFERENCE_LAYER) : - setOf(prefKey, ProjectKeys.KEY_REFERENCE_LAYER); + return prefKey.isEmpty() ? emptySet() : setOf(prefKey); } @Override public Bundle buildConfig(Settings prefs) { Bundle config = new Bundle(); config.putInt(GoogleMapFragment.KEY_MAP_TYPE, getInt(ProjectKeys.KEY_GOOGLE_MAP_STYLE, GoogleMap.MAP_TYPE_NORMAL, prefs)); - config.putString(GoogleMapFragment.KEY_REFERENCE_LAYER, - prefs.getString(ProjectKeys.KEY_REFERENCE_LAYER)); return config; } diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java index 92d3611a509..c5adfd8bbf7 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java @@ -62,7 +62,6 @@ import org.odk.collect.maps.Zoom; import org.odk.collect.maps.ZoomObserver; import org.odk.collect.maps.circles.CircleDescription; -import org.odk.collect.maps.layers.MapFragmentReferenceLayerUtils; import org.odk.collect.maps.layers.ReferenceLayerRepository; import org.odk.collect.maps.markers.MarkerDescription; import org.odk.collect.maps.markers.MarkerIconDescription; @@ -179,6 +178,13 @@ void onCameraMoveFinished(boolean isUser, boolean isZoom) { onConfigChanged(newConfig); }); + getMapViewModel().getReferenceLayer().observe(getViewLifecycleOwner(), referenceLayer -> { + referenceLayerFile = referenceLayer; + if (map != null) { + loadReferenceOverlay(); + } + }); + getMapViewModel().getZoom().observe(getViewLifecycleOwner(), new ZoomObserver() { @Override public void onZoomToPoint(@NonNull Zoom.Point zoom) { @@ -228,18 +234,21 @@ public void onZoomToBox(@NonNull Zoom.Box zoom) { return view; } - @Override public void onAttach(@NonNull Context context) { + @Override + public void onAttach(@NonNull Context context) { super.onAttach(context); GoogleMapsDependencyComponent component = ((GoogleMapsDependencyComponentProvider) context.getApplicationContext()).getGoogleMapsDependencyComponent(); component.inject(this); } - @Override public void onDestroy() { + @Override + public void onDestroy() { BitmapDescriptorCache.clearCache(); super.onDestroy(); } - @Override public @NonNull MapPoint getCenter() { + @Override + public @NonNull MapPoint getCenter() { if (map == null) { // during Robolectric tests, map will be null return MapFragment.Companion.getINITIAL_CENTER(); } @@ -247,7 +256,8 @@ public void onZoomToBox(@NonNull Zoom.Box zoom) { return new MapPoint(target.latitude, target.longitude); } - @Override public double getZoom() { + @Override + public double getZoom() { if (map == null) { // during Robolectric tests, map will be null return INITIAL_ZOOM; } @@ -266,19 +276,22 @@ public List addMarkers(List markers) { return featureIds; } - @Override public void setMarkerIcon(int featureId, MarkerIconDescription markerIconDescription) { + @Override + public void setMarkerIcon(int featureId, MarkerIconDescription markerIconDescription) { MapFeature feature = features.get(featureId); if (feature instanceof MarkerFeature) { ((MarkerFeature) feature).setIcon(markerIconDescription); } } - @Override public @Nullable MapPoint getMarkerPoint(int featureId) { + @Override + public @Nullable MapPoint getMarkerPoint(int featureId) { MapFeature feature = features.get(featureId); return feature instanceof MarkerFeature ? ((MarkerFeature) feature).getPoint() : null; } - @Override public int addPolyLine(LineDescription lineDescription) { + @Override + public int addPolyLine(LineDescription lineDescription) { int featureId = nextFeatureId++; addPolyLine(featureId, lineDescription); return featureId; @@ -319,7 +332,8 @@ public void updatePolygon(int featureId, @NotNull PolygonDescription polygonDesc addPolygon(featureId, polygonDescription); } - @Override public @NonNull List getPolyPoints(int featureId) { + @Override + public @NonNull List getPolyPoints(int featureId) { MapFeature feature = features.get(featureId); if (feature instanceof LineFeature) { return ((LineFeature) feature).getPoints(); @@ -328,7 +342,8 @@ public void updatePolygon(int featureId, @NotNull PolygonDescription polygonDesc return new ArrayList<>(); } - @Override public void clearFeatures() { + @Override + public void clearFeatures() { if (map != null) { // during Robolectric tests, map will be null for (MapFeature feature : features.values()) { feature.dispose(); @@ -345,35 +360,42 @@ public void clearFeatures(@NotNull List<@NotNull Integer> ids) { } } - @Override public void setClickListener(@Nullable PointListener listener) { + @Override + public void setClickListener(@Nullable PointListener listener) { clickListener = listener; } - @Override public void setLongPressListener(@Nullable PointListener listener) { + @Override + public void setLongPressListener(@Nullable PointListener listener) { longPressListener = listener; } - @Override public void setFeatureClickListener(@Nullable FeatureListener listener) { + @Override + public void setFeatureClickListener(@Nullable FeatureListener listener) { featureClickListener = listener; } - @Override public void setDragEndListener(@Nullable FeatureListener listener) { + @Override + public void setDragEndListener(@Nullable FeatureListener listener) { dragEndListener = listener; } - @Override public void onMapClick(LatLng latLng) { + @Override + public void onMapClick(LatLng latLng) { if (clickListener != null) { clickListener.onPoint(fromLatLng(latLng)); } } - @Override public void onMapLongClick(LatLng latLng) { + @Override + public void onMapLongClick(LatLng latLng) { if (longPressListener != null) { longPressListener.onPoint(fromLatLng(latLng)); } } - @Override public boolean onMarkerClick(Marker marker) { + @Override + public boolean onMarkerClick(Marker marker) { if (featureClickListener != null) { // FormMapActivity featureClickListener.onFeature(findFeature(marker)); } else { // GeoWidget @@ -382,26 +404,30 @@ public void clearFeatures(@NotNull List<@NotNull Integer> ids) { return true; // consume the event (no default zoom and popup behaviour) } - @Override public void onPolylineClick(Polyline polyline) { + @Override + public void onPolylineClick(Polyline polyline) { if (featureClickListener != null) { featureClickListener.onFeature(findFeature(polyline)); } } - @Override public void onPolygonClick(@NonNull Polygon polygon) { + @Override + public void onPolygonClick(@NonNull Polygon polygon) { if (featureClickListener != null) { featureClickListener.onFeature(findFeature(polygon)); } } - @Override public void onMarkerDragStart(Marker marker) { + @Override + public void onMarkerDragStart(Marker marker) { // When dragging starts, GoogleMap makes the marker jump up to move it // out from under the user's finger; whenever a marker moves, we have // to update its corresponding feature. updateFeature(findFeature(marker)); } - @Override public void onMarkerDrag(Marker marker) { + @Override + public void onMarkerDrag(Marker marker) { // When a marker is manually dragged, the position is no longer // obtained from a GPS reading, so the altitude and standard deviation // fields are no longer meaningful; reset them to zero. @@ -409,7 +435,8 @@ public void clearFeatures(@NotNull List<@NotNull Integer> ids) { updateFeature(findFeature(marker)); } - @Override public void onMarkerDragEnd(Marker marker) { + @Override + public void onMarkerDragEnd(Marker marker) { int featureId = findFeature(marker); updateFeature(featureId); if (dragEndListener != null && featureId != -1) { @@ -426,7 +453,7 @@ public void clearFeatures(@NotNull List<@NotNull Integer> ids) { return null; } return new MapPoint(location.getLatitude(), location.getLongitude(), - location.getAltitude(), location.getAccuracy()); + location.getAltitude(), location.getAccuracy()); } private static @NonNull MapPoint fromMarker(@NonNull Marker marker) { @@ -449,8 +476,9 @@ public void clearFeatures(@NotNull List<@NotNull Integer> ids) { } - - /** Updates the map to reflect the value of referenceLayerFile. */ + /** + * Updates the map to reflect the value of referenceLayerFile. + */ private void loadReferenceOverlay() { if (referenceOverlay != null) { referenceOverlay.remove(); @@ -458,7 +486,7 @@ private void loadReferenceOverlay() { } if (referenceLayerFile != null) { referenceOverlay = this.map.addTileOverlay(new TileOverlayOptions().tileProvider( - new GoogleMapsMapBoxOfflineTileProvider(referenceLayerFile) + new GoogleMapsMapBoxOfflineTileProvider(referenceLayerFile) )); setLabelsVisibility("off"); } else { @@ -500,7 +528,9 @@ private void moveOrAnimateCamera(CameraUpdate movement, boolean animate) { } } - /** Finds the feature to which the given marker belongs. */ + /** + * Finds the feature to which the given marker belongs. + */ private int findFeature(Marker marker) { for (int featureId : features.keySet()) { if (features.get(featureId).ownsMarker(marker)) { @@ -510,7 +540,9 @@ private int findFeature(Marker marker) { return -1; // not found } - /** Finds the feature to which the given polyline belongs. */ + /** + * Finds the feature to which the given polyline belongs. + */ private int findFeature(Polyline polyline) { for (int featureId : features.keySet()) { if (features.get(featureId).ownsPolyline(polyline)) { @@ -545,12 +577,12 @@ private static Marker createMarker(Context context, MarkerDescription markerDesc // fields. We need to store the point's altitude and standard // deviation values somewhere, so they go in the marker's snippet. return map.addMarker(new MarkerOptions() - .position(toLatLng(markerDescription.getPoint())) - .snippet(markerDescription.getPoint().altitude + ";" + markerDescription.getPoint().accuracy) - .draggable(markerDescription.isDraggable()) - .icon(getBitmapDescriptor(context, markerDescription.getIconDescription())) - .anchor(getIconAnchorValueX(markerDescription.getIconAnchor()), getIconAnchorValueY(markerDescription.getIconAnchor())) // center the icon on the position - .zIndex(getZIndex(markerDescription.getIconDescription().getBackground())) + .position(toLatLng(markerDescription.getPoint())) + .snippet(markerDescription.getPoint().altitude + ";" + markerDescription.getPoint().accuracy) + .draggable(markerDescription.isDraggable()) + .icon(getBitmapDescriptor(context, markerDescription.getIconDescription())) + .anchor(getIconAnchorValueX(markerDescription.getIconAnchor()), getIconAnchorValueY(markerDescription.getIconAnchor())) // center the icon on the position + .zIndex(getZIndex(markerDescription.getIconDescription().getBackground())) ); } @@ -587,10 +619,8 @@ private static BitmapDescriptor getBitmapDescriptor(Context context, MarkerIconD private void onConfigChanged(Bundle config) { mapType = config.getInt(KEY_MAP_TYPE, GoogleMap.MAP_TYPE_NORMAL); - referenceLayerFile = MapFragmentReferenceLayerUtils.getReferenceLayerFile(config, referenceLayerRepository); if (map != null) { map.setMapType(mapType); - loadReferenceOverlay(); } } @@ -611,7 +641,7 @@ public MapViewModel getMapViewModel() { @NonNull @Override public T create(@NonNull Class modelClass) { - return (T) new MapViewModel(settingsProvider.getUnprotectedSettings(), settingsProvider.getMetaSettings()); + return (T) new MapViewModel(settingsProvider.getUnprotectedSettings(), settingsProvider.getMetaSettings(), referenceLayerRepository); } }).get(MapViewModel.class); } @@ -646,18 +676,26 @@ public void updateCircle(int featureId, @NotNull CircleDescription circleDescrip * (e.g. geometric elements, handles for manipulation, etc.). */ public interface MapFeature { - /** Returns true if the given marker belongs to this feature. */ + /** + * Returns true if the given marker belongs to this feature. + */ boolean ownsMarker(Marker marker); - /** Returns true if the given polyline belongs to this feature. */ + /** + * Returns true if the given polyline belongs to this feature. + */ boolean ownsPolyline(Polyline polyline); boolean ownsPolygon(Polygon polygon); - /** Updates the feature's geometry after any UI handles have moved. */ + /** + * Updates the feature's geometry after any UI handles have moved. + */ void update(); - /** Removes the feature from the map, leaving it no longer usable. */ + /** + * Removes the feature from the map, leaving it no longer usable. + */ void dispose(); } @@ -691,7 +729,8 @@ public boolean ownsPolygon(Polygon polygon) { return false; } - public void update() { } + public void update() { + } public void dispose() { marker.remove(); @@ -703,7 +742,9 @@ private interface LineFeature extends MapFeature { List getPoints(); } - /** A polyline or polygon that can not be manipulated by dragging markers at its vertices. */ + /** + * A polyline or polygon that can not be manipulated by dragging markers at its vertices. + */ private static class StaticPolyLineFeature implements LineFeature { private List points; @@ -818,10 +859,10 @@ public void update() { clearPolyline(); } else if (polyline == null) { polyline = map.addPolyline(new PolylineOptions() - .color(lineDescription.getStrokeColor()) - .zIndex(getZIndex(lineDescription.getBackground())) - .width(lineDescription.getStrokeWidth()) - .addAll(latLngs) + .color(lineDescription.getStrokeColor()) + .zIndex(getZIndex(lineDescription.getBackground())) + .width(lineDescription.getStrokeWidth()) + .addAll(latLngs) .clickable(lineDescription.getClickable()) ); } else { diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index fea8c9a3939..45287718d2f 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -105,7 +105,8 @@ class MapboxMapFragment : addInitializer(MapViewModel::class) { MapViewModel( settingsProvider.getUnprotectedSettings(), - settingsProvider.getMetaSettings() + settingsProvider.getMetaSettings(), + referenceLayerRepository ) } } diff --git a/maps/src/main/java/org/odk/collect/maps/MapViewModel.kt b/maps/src/main/java/org/odk/collect/maps/MapViewModel.kt index 5a9d7bd5286..77fbac5b6f7 100644 --- a/maps/src/main/java/org/odk/collect/maps/MapViewModel.kt +++ b/maps/src/main/java/org/odk/collect/maps/MapViewModel.kt @@ -4,12 +4,18 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MediatorLiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.map +import org.odk.collect.maps.layers.MapFragmentReferenceLayerUtils +import org.odk.collect.maps.layers.ReferenceLayerRepository import org.odk.collect.settings.keys.MetaKeys.LAST_KNOWN_ZOOM_LEVEL +import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.shared.settings.Settings +import java.io.File class MapViewModel( private val unprotectedSettings: Settings, - private val metaSettings: Settings + private val metaSettings: Settings, + private val referenceLayerRepository: ReferenceLayerRepository ) : ViewModel(), Settings.OnSettingChangeListener { @@ -31,7 +37,8 @@ class MapViewModel( } fun zoomTo(boundingBox: List, scaleFactor: Double, animate: Boolean) { - _zoom.value = Zoom.Box(boundingBox, scaleFactor, _zoom.value?.level ?: DEFAULT_ZOOM, animate) + _zoom.value = + Zoom.Box(boundingBox, scaleFactor, _zoom.value?.level ?: DEFAULT_ZOOM, animate) } fun zoomToCurrentLocation(location: MapPoint?) { @@ -75,6 +82,15 @@ class MapViewModel( } } + fun getReferenceLayer(): LiveData { + return getSettings(listOf(ProjectKeys.KEY_REFERENCE_LAYER)).map { + MapFragmentReferenceLayerUtils.getReferenceLayerFile( + it.getString(ProjectKeys.KEY_REFERENCE_LAYER), + referenceLayerRepository + ) + } + } + override fun onCleared() { userZoomLevel?.let { metaSettings.save(LAST_KNOWN_ZOOM_LEVEL, it.toFloat()) diff --git a/maps/src/main/java/org/odk/collect/maps/layers/MapFragmentReferenceLayerUtils.kt b/maps/src/main/java/org/odk/collect/maps/layers/MapFragmentReferenceLayerUtils.kt index 03d75ce1680..98eeb88b524 100644 --- a/maps/src/main/java/org/odk/collect/maps/layers/MapFragmentReferenceLayerUtils.kt +++ b/maps/src/main/java/org/odk/collect/maps/layers/MapFragmentReferenceLayerUtils.kt @@ -12,6 +12,13 @@ object MapFragmentReferenceLayerUtils { layerRepository: ReferenceLayerRepository ): File? { val filePath = config.getString(MapFragment.KEY_REFERENCE_LAYER) + return getReferenceLayerFile(filePath, layerRepository) + } + + fun getReferenceLayerFile( + filePath: String?, + layerRepository: ReferenceLayerRepository + ): File? { return if (filePath != null) { val referenceLayer = layerRepository.get(filePath) referenceLayer?.file diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java index 74ee8c3f124..335e3ab31bf 100644 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java +++ b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java @@ -134,7 +134,8 @@ public void onAttach(@NonNull Context context) { public T create(@NonNull Class modelClass) { return (T) new MapViewModel( settingsProvider.getUnprotectedSettings(), - settingsProvider.getMetaSettings() + settingsProvider.getMetaSettings(), + referenceLayerRepository ); } }; From fccb958066a0f552a14d356698411a10c8d301b7 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 12:04:02 +0100 Subject: [PATCH 03/21] Remove reference layer key from Mapbox --- .../odk/collect/mapbox/MapboxMapConfigurator.java | 6 ++---- .../org/odk/collect/mapbox/MapboxMapFragment.kt | 13 ++++++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java index 1c02a1a33ad..5530dbf736f 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -69,16 +70,13 @@ public MapboxMapConfigurator() { } @Override public Set getPrefKeys() { - return prefKey.isEmpty() ? setOf(ProjectKeys.KEY_REFERENCE_LAYER) : - setOf(prefKey, ProjectKeys.KEY_REFERENCE_LAYER); + return prefKey.isEmpty() ? new HashSet<>() : setOf(prefKey); } @Override public Bundle buildConfig(Settings prefs) { Bundle config = new Bundle(); config.putString(MapboxMapFragment.KEY_STYLE_URL, prefs.getString(ProjectKeys.KEY_MAPBOX_MAP_STYLE)); - config.putString(MapboxMapFragment.KEY_REFERENCE_LAYER, - prefs.getString(ProjectKeys.KEY_REFERENCE_LAYER)); return config; } diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 45287718d2f..0ec80e25981 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -62,7 +62,6 @@ import org.odk.collect.maps.MapViewModelMapFragment import org.odk.collect.maps.Zoom import org.odk.collect.maps.ZoomObserver import org.odk.collect.maps.circles.CircleDescription -import org.odk.collect.maps.layers.MapFragmentReferenceLayerUtils.getReferenceLayerFile import org.odk.collect.maps.layers.MbtilesFile import org.odk.collect.maps.layers.ReferenceLayerRepository import org.odk.collect.maps.markers.MarkerDescription @@ -81,6 +80,7 @@ class MapboxMapFragment : OnMapClickListener, OnMapLongClickListener { + private lateinit var styleUrl: String private lateinit var mapView: MapView private lateinit var mapboxMap: MapboxMap @@ -204,6 +204,10 @@ class MapboxMapFragment : onConfigChanged(newConfig) } + getMapViewModel().getReferenceLayer().observe(viewLifecycleOwner) { + loadStyle() + } + getMapViewModel().zoom.observe(viewLifecycleOwner, object : ZoomObserver() { override fun onZoomToPoint(zoom: Zoom.Point) { moveOrAnimateCamera(zoom.point, zoom.animate, zoom.level) @@ -244,8 +248,11 @@ class MapboxMapFragment : } private fun onConfigChanged(config: Bundle) { - val styleUrl = config.getString(KEY_STYLE_URL) ?: Style.MAPBOX_STREETS - referenceLayerFile = getReferenceLayerFile(config, referenceLayerRepository) + styleUrl = config.getString(KEY_STYLE_URL) ?: Style.MAPBOX_STREETS + loadStyle() + } + + private fun loadStyle() { mapboxMap.loadStyleUri(styleUrl) { if (topStyleLayerId == null) { // remember the id of the top style layer From b2e4882829a4f56976523b3cfaf5cfad4c32c514 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 12:11:46 +0100 Subject: [PATCH 04/21] Remove deprecated methods from MapboxMapConfigurator --- .../collect/mapbox/MapboxMapConfigurator.java | 44 +++++-------------- .../odk/collect/mapbox/MapboxMapFragment.kt | 13 ++---- .../org/odk/collect/maps/MapConfigurator.kt | 7 ++- 3 files changed, 20 insertions(+), 44 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java index 5530dbf736f..e2999ec6a34 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java @@ -1,10 +1,6 @@ package org.odk.collect.mapbox; -import static org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE; -import static kotlin.collections.SetsKt.setOf; - import android.content.Context; -import android.os.Bundle; import androidx.preference.Preference; @@ -20,28 +16,18 @@ import java.io.File; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class MapboxMapConfigurator implements MapConfigurator { - private final String prefKey; - private final int sourceLabelId; - private final MapboxUrlOption[] options; - - /** Constructs a configurator with a few Mapbox style URL options to choose from. */ - public MapboxMapConfigurator() { - this.prefKey = KEY_MAPBOX_MAP_STYLE; - this.sourceLabelId = org.odk.collect.strings.R.string.basemap_source_mapbox; - this.options = new MapboxUrlOption[]{ - new MapboxUrlOption(Style.MAPBOX_STREETS, org.odk.collect.strings.R.string.streets), - new MapboxUrlOption(Style.LIGHT, org.odk.collect.strings.R.string.light), - new MapboxUrlOption(Style.DARK, org.odk.collect.strings.R.string.dark), - new MapboxUrlOption(Style.SATELLITE, org.odk.collect.strings.R.string.satellite), - new MapboxUrlOption(Style.SATELLITE_STREETS, org.odk.collect.strings.R.string.hybrid), - new MapboxUrlOption(Style.OUTDOORS, org.odk.collect.strings.R.string.outdoors) - }; - } + private final int sourceLabelId = org.odk.collect.strings.R.string.basemap_source_mapbox; + private final MapboxUrlOption[] options = { + new MapboxUrlOption(Style.MAPBOX_STREETS, org.odk.collect.strings.R.string.streets), + new MapboxUrlOption(Style.LIGHT, org.odk.collect.strings.R.string.light), + new MapboxUrlOption(Style.DARK, org.odk.collect.strings.R.string.dark), + new MapboxUrlOption(Style.SATELLITE, org.odk.collect.strings.R.string.satellite), + new MapboxUrlOption(Style.SATELLITE_STREETS, org.odk.collect.strings.R.string.hybrid), + new MapboxUrlOption(Style.OUTDOORS, org.odk.collect.strings.R.string.outdoors) + }; @Override public boolean isAvailable(Context context) { /* @@ -65,20 +51,10 @@ public MapboxMapConfigurator() { String prefTitle = context.getString( org.odk.collect.strings.R.string.map_style_label, context.getString(sourceLabelId)); return Collections.singletonList(PrefUtils.createListPref( - context, prefKey, prefTitle, labelIds, values, settings + context, ProjectKeys.KEY_MAPBOX_MAP_STYLE, prefTitle, labelIds, values, settings )); } - @Override public Set getPrefKeys() { - return prefKey.isEmpty() ? new HashSet<>() : setOf(prefKey); - } - - @Override public Bundle buildConfig(Settings prefs) { - Bundle config = new Bundle(); - config.putString(MapboxMapFragment.KEY_STYLE_URL, - prefs.getString(ProjectKeys.KEY_MAPBOX_MAP_STYLE)); - return config; - } @Override public boolean supportsLayer(File file) { // MapboxMapFragment supports any file that MbtilesFile can read. diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 0ec80e25981..80c19863b63 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -70,6 +70,7 @@ import org.odk.collect.maps.markers.MarkerIconDescription import org.odk.collect.maps.traces.LineDescription import org.odk.collect.maps.traces.PolygonDescription import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE import org.odk.collect.shared.injection.ObjectProviderHost import timber.log.Timber import java.io.File @@ -198,10 +199,9 @@ class MapboxMapFragment : mapReadyListener!!.onReady(this) } - val mapConfigurator = MapboxMapConfigurator() - getMapViewModel().getSettings(mapConfigurator.prefKeys).observe(viewLifecycleOwner) { - val newConfig = mapConfigurator.buildConfig(it) - onConfigChanged(newConfig) + getMapViewModel().getSettings(setOf(KEY_MAPBOX_MAP_STYLE)).observe(viewLifecycleOwner) { + styleUrl = it.getString(KEY_MAPBOX_MAP_STYLE) ?: Style.MAPBOX_STREETS + loadStyle() } getMapViewModel().getReferenceLayer().observe(viewLifecycleOwner) { @@ -247,11 +247,6 @@ class MapboxMapFragment : super.onDestroy() } - private fun onConfigChanged(config: Bundle) { - styleUrl = config.getString(KEY_STYLE_URL) ?: Style.MAPBOX_STREETS - loadStyle() - } - private fun loadStyle() { mapboxMap.loadStyleUri(styleUrl) { if (topStyleLayerId == null) { diff --git a/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt b/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt index df186bf3924..6333e9f771c 100644 --- a/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt +++ b/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt @@ -34,10 +34,15 @@ interface MapConfigurator { @Deprecated("Pref keys should be constants owned by the MapFragment modules") /** Gets the set of keys for preferences that should be watched for changes. */ val prefKeys: Collection + get() { + TODO() + } @Deprecated("This should be handled by MapFragment implementations") /** Packs map-related preferences into a Bundle for MapFragment.applyConfig(). */ - fun buildConfig(prefs: Settings): Bundle + fun buildConfig(prefs: Settings): Bundle { + TODO() + } /** * Returns true if map fragments obtained from this MapConfigurator are From af9ed2e3178170ddcf1bc87f4a1bbe6fbff7ba0e Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 12:55:05 +0100 Subject: [PATCH 05/21] Remove deprecated methods from GoogleMapConfigurator --- .../android/geo/MapConfiguratorProvider.java | 13 +------ .../googlemaps/GoogleMapConfigurator.java | 37 +++++-------------- .../collect/googlemaps/GoogleMapFragment.java | 32 ++++------------ 3 files changed, 17 insertions(+), 65 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java index b35a4293074..827426d3984 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java @@ -7,7 +7,6 @@ import static org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_USGS; import static org.odk.collect.settings.keys.ProjectKeys.KEY_BASEMAP_SOURCE; import static org.odk.collect.settings.keys.ProjectKeys.KEY_CARTO_MAP_STYLE; -import static org.odk.collect.settings.keys.ProjectKeys.KEY_GOOGLE_MAP_STYLE; import static org.odk.collect.settings.keys.ProjectKeys.KEY_USGS_MAP_STYLE; import static org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString; @@ -15,12 +14,9 @@ import androidx.annotation.NonNull; -import com.google.android.gms.maps.GoogleMap; - import org.odk.collect.android.application.Collect; import org.odk.collect.android.application.MapboxClassInstanceCreator; import org.odk.collect.googlemaps.GoogleMapConfigurator; -import org.odk.collect.googlemaps.GoogleMapConfigurator.GoogleMapTypeOption; import org.odk.collect.android.injection.DaggerUtils; import org.odk.collect.maps.MapConfigurator; import org.odk.collect.osmdroid.OsmDroidMapConfigurator; @@ -55,14 +51,7 @@ public static void initOptions(Context context) { ArrayList sourceOptions = new ArrayList<>(); - GoogleMapConfigurator googleMapsConfigurator = new GoogleMapConfigurator( - KEY_GOOGLE_MAP_STYLE, org.odk.collect.strings.R.string.basemap_source_google, - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_NORMAL, org.odk.collect.strings.R.string.streets), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_TERRAIN, org.odk.collect.strings.R.string.terrain), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_HYBRID, org.odk.collect.strings.R.string.hybrid), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_SATELLITE, org.odk.collect.strings.R.string.satellite) - ); - + GoogleMapConfigurator googleMapsConfigurator = new GoogleMapConfigurator(); if (googleMapsConfigurator.isAvailable(context)) { sourceOptions.add(new SourceOption(BASEMAP_SOURCE_GOOGLE, org.odk.collect.strings.R.string.basemap_source_google, googleMapsConfigurator diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java index e884d2596a5..2477822283f 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapConfigurator.java @@ -1,14 +1,11 @@ package org.odk.collect.googlemaps; import static org.odk.collect.androidshared.ui.PrefUtils.createListPref; -import static org.odk.collect.androidshared.ui.PrefUtils.getInt; -import static java.util.Collections.emptySet; -import static kotlin.collections.SetsKt.setOf; +import static org.odk.collect.settings.keys.ProjectKeys.KEY_GOOGLE_MAP_STYLE; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.os.Bundle; import androidx.preference.Preference; @@ -20,25 +17,20 @@ import org.odk.collect.maps.MapConfigurator; import org.odk.collect.maps.layers.MbtilesFile; import org.odk.collect.maps.layers.MbtilesFile.LayerType; -import org.odk.collect.settings.keys.ProjectKeys; import org.odk.collect.shared.settings.Settings; import java.io.File; import java.util.Collections; import java.util.List; -import java.util.Set; public class GoogleMapConfigurator implements MapConfigurator { - private final String prefKey; - private final int sourceLabelId; - private final GoogleMapTypeOption[] options; - - /** Constructs a configurator with a few Google map type options to choose from. */ - public GoogleMapConfigurator(String prefKey, int sourceLabelId, GoogleMapTypeOption... options) { - this.prefKey = prefKey; - this.sourceLabelId = sourceLabelId; - this.options = options; - } + private final int sourceLabelId = org.odk.collect.strings.R.string.basemap_source_google; + private final GoogleMapTypeOption[] options = { + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_NORMAL, org.odk.collect.strings.R.string.streets), + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_TERRAIN, org.odk.collect.strings.R.string.terrain), + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_HYBRID, org.odk.collect.strings.R.string.hybrid), + new GoogleMapTypeOption(GoogleMap.MAP_TYPE_SATELLITE, org.odk.collect.strings.R.string.satellite) + }; @Override public boolean isAvailable(Context context) { try { @@ -85,21 +77,10 @@ private static boolean isGoogleMapsSdkAvailable(Context context) { String prefTitle = context.getString( org.odk.collect.strings.R.string.map_style_label, context.getString(sourceLabelId)); return Collections.singletonList(createListPref( - context, prefKey, prefTitle, labelIds, values, settings + context, KEY_GOOGLE_MAP_STYLE, prefTitle, labelIds, values, settings )); } - @Override public Set getPrefKeys() { - return prefKey.isEmpty() ? emptySet() : setOf(prefKey); - } - - @Override public Bundle buildConfig(Settings prefs) { - Bundle config = new Bundle(); - config.putInt(GoogleMapFragment.KEY_MAP_TYPE, - getInt(ProjectKeys.KEY_GOOGLE_MAP_STYLE, GoogleMap.MAP_TYPE_NORMAL, prefs)); - return config; - } - @Override public boolean supportsLayer(File file) { // GoogleMapFragment supports only raster tiles. return MbtilesFile.readLayerType(file) == LayerType.RASTER; diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java index c5adfd8bbf7..0a273a11317 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/GoogleMapFragment.java @@ -14,6 +14,8 @@ package org.odk.collect.googlemaps; +import static com.google.android.gms.common.util.CollectionUtils.setOf; +import static org.odk.collect.androidshared.ui.PrefUtils.getInt; import static org.odk.collect.googlemaps.MapPointExt.toLatLng; import static org.odk.collect.maps.traces.TraceDescriptionKt.getMarkersForPoints; @@ -51,10 +53,8 @@ import org.jetbrains.annotations.NotNull; import org.odk.collect.androidshared.ui.ToastUtils; -import org.odk.collect.googlemaps.GoogleMapConfigurator.GoogleMapTypeOption; import org.odk.collect.googlemaps.circles.CircleFeature; import org.odk.collect.googlemaps.scaleview.MapScaleView; -import org.odk.collect.maps.MapConfigurator; import org.odk.collect.maps.MapFragment; import org.odk.collect.maps.MapPoint; import org.odk.collect.maps.MapViewModel; @@ -171,11 +171,11 @@ void onCameraMoveFinished(boolean isUser, boolean isZoom) { loadReferenceOverlay(); - - MapConfigurator configurator = createConfigurator(); - getMapViewModel().getSettings(configurator.getPrefKeys()).observe(getViewLifecycleOwner(), settings -> { - Bundle newConfig = configurator.buildConfig(settings); - onConfigChanged(newConfig); + getMapViewModel().getSettings(setOf(ProjectKeys.KEY_GOOGLE_MAP_STYLE)).observe(getViewLifecycleOwner(), settings -> { + mapType = getInt(ProjectKeys.KEY_GOOGLE_MAP_STYLE, GoogleMap.MAP_TYPE_NORMAL, settings); + if (map != null) { + map.setMapType(mapType); + } }); getMapViewModel().getReferenceLayer().observe(getViewLifecycleOwner(), referenceLayer -> { @@ -617,24 +617,6 @@ private static BitmapDescriptor getBitmapDescriptor(Context context, MarkerIconD return BitmapDescriptorCache.getBitmapDescriptor(context, markerIconDescription); } - private void onConfigChanged(Bundle config) { - mapType = config.getInt(KEY_MAP_TYPE, GoogleMap.MAP_TYPE_NORMAL); - if (map != null) { - map.setMapType(mapType); - } - } - - private MapConfigurator createConfigurator() { - return new GoogleMapConfigurator( - ProjectKeys.KEY_GOOGLE_MAP_STYLE, org.odk.collect.strings.R.string.basemap_source_google, - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_NORMAL, org.odk.collect.strings.R.string.streets), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_TERRAIN, org.odk.collect.strings.R.string.terrain), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_HYBRID, org.odk.collect.strings.R.string.hybrid), - new GoogleMapTypeOption(GoogleMap.MAP_TYPE_SATELLITE, org.odk.collect.strings.R.string.satellite) - ); - } - - @NonNull @Override public MapViewModel getMapViewModel() { return new ViewModelProvider(this, new ViewModelProvider.Factory() { From 056696b5d117c8d2e3d91645e7541b7f03f334b8 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 13:38:00 +0100 Subject: [PATCH 06/21] Use Mapbox for all-non Google Maps --- .../application/MapboxClassInstanceCreator.kt | 23 ++-- .../android/geo/MapConfiguratorProvider.java | 59 ++-------- .../android/geo/MapFragmentFactoryImpl.kt | 21 +--- .../collect/mapbox/MapboxMapConfigurator.java | 78 ------------- .../collect/mapbox/MapboxMapConfigurator.kt | 109 ++++++++++++++++++ .../odk/collect/mapbox/MapboxMapFragment.kt | 2 +- 6 files changed, 140 insertions(+), 152 deletions(-) delete mode 100644 mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java create mode 100644 mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt diff --git a/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt b/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt index 4b5edc49973..21dcd0eee61 100644 --- a/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt +++ b/collect_app/src/main/java/org/odk/collect/android/application/MapboxClassInstanceCreator.kt @@ -11,7 +11,7 @@ object MapboxClassInstanceCreator { @JvmStatic fun isMapboxAvailable(): Boolean { return try { - getClass(MAP_FRAGMENT) + Class.forName(MAP_FRAGMENT) System.loadLibrary("mapbox-common") true } catch (e: Throwable) { @@ -19,23 +19,24 @@ object MapboxClassInstanceCreator { } } - fun createMapboxMapFragment(): MapFragment { - return createClassInstance(MAP_FRAGMENT) + fun createMapboxMapFragment(configuration: String): MapFragment { + return Class.forName(MAP_FRAGMENT) + .getConstructor(String::class.java) + .newInstance(configuration) as MapFragment } @JvmStatic fun createMapBoxInitializationFragment(): Fragment { - return createClassInstance("org.odk.collect.mapbox.MapBoxInitializationFragment") + return Class.forName("org.odk.collect.mapbox.MapBoxInitializationFragment") + .getConstructor() + .newInstance() as Fragment } @JvmStatic - fun createMapboxMapConfigurator(): MapConfigurator { - return createClassInstance("org.odk.collect.mapbox.MapboxMapConfigurator") + fun createMapboxMapConfigurator(configuration: String): MapConfigurator { + return Class.forName("org.odk.collect.mapbox.MapboxMapConfigurator") + .getConstructor(String::class.java) + .newInstance(configuration) as MapConfigurator } - private fun createClassInstance(className: String): T { - return getClass(className).newInstance() as T - } - - private fun getClass(className: String): Class<*> = Class.forName(className) } diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java index 827426d3984..2fbcd67431f 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java @@ -6,9 +6,6 @@ import static org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_OSM; import static org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_USGS; import static org.odk.collect.settings.keys.ProjectKeys.KEY_BASEMAP_SOURCE; -import static org.odk.collect.settings.keys.ProjectKeys.KEY_CARTO_MAP_STYLE; -import static org.odk.collect.settings.keys.ProjectKeys.KEY_USGS_MAP_STYLE; -import static org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString; import android.content.Context; @@ -16,12 +13,9 @@ import org.odk.collect.android.application.Collect; import org.odk.collect.android.application.MapboxClassInstanceCreator; -import org.odk.collect.googlemaps.GoogleMapConfigurator; import org.odk.collect.android.injection.DaggerUtils; +import org.odk.collect.googlemaps.GoogleMapConfigurator; import org.odk.collect.maps.MapConfigurator; -import org.odk.collect.osmdroid.OsmDroidMapConfigurator; -import org.odk.collect.osmdroid.OsmDroidMapConfigurator.WmsOption; -import org.odk.collect.osmdroid.WebMapService; import java.util.ArrayList; @@ -60,48 +54,19 @@ public static void initOptions(Context context) { if (isMapboxSupported()) { sourceOptions.add(new SourceOption(BASEMAP_SOURCE_MAPBOX, org.odk.collect.strings.R.string.basemap_source_mapbox, - MapboxClassInstanceCreator.createMapboxMapConfigurator() + MapboxClassInstanceCreator.createMapboxMapConfigurator(BASEMAP_SOURCE_MAPBOX) )); - } - sourceOptions.add(new SourceOption(BASEMAP_SOURCE_OSM, org.odk.collect.strings.R.string.basemap_source_osm, - new OsmDroidMapConfigurator( - new WebMapService( - "Mapnik", 0, 19, 256, OSM_COPYRIGHT, - "https://tile.openstreetmap.org/{z}/{x}/{y}.png" - ) - ) - )); - sourceOptions.add(new SourceOption(BASEMAP_SOURCE_USGS, org.odk.collect.strings.R.string.basemap_source_usgs, - new OsmDroidMapConfigurator( - KEY_USGS_MAP_STYLE, org.odk.collect.strings.R.string.basemap_source_usgs, - new WmsOption("topographic", org.odk.collect.strings.R.string.topographic, new WebMapService( - getLocalizedString(getApplication(), org.odk.collect.strings.R.string.openmap_usgs_topo), 0, 18, 256, USGS_ATTRIBUTION, - USGS_URL_BASE + "/USGSTopo/MapServer/tile/{z}/{y}/{x}" - )), - new WmsOption("hybrid", org.odk.collect.strings.R.string.hybrid, new WebMapService( - getLocalizedString(getApplication(), org.odk.collect.strings.R.string.openmap_usgs_sat), 0, 18, 256, USGS_ATTRIBUTION, - USGS_URL_BASE + "/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}" - )), - new WmsOption("satellite", org.odk.collect.strings.R.string.satellite, new WebMapService( - getLocalizedString(getApplication(), org.odk.collect.strings.R.string.openmap_usgs_img), 0, 18, 256, USGS_ATTRIBUTION, - USGS_URL_BASE + "/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}" - )) - ) - )); - sourceOptions.add(new SourceOption(BASEMAP_SOURCE_CARTO, org.odk.collect.strings.R.string.basemap_source_carto, - new OsmDroidMapConfigurator( - KEY_CARTO_MAP_STYLE, org.odk.collect.strings.R.string.basemap_source_carto, - new WmsOption("positron", org.odk.collect.strings.R.string.carto_map_style_positron, new WebMapService( - getLocalizedString(getApplication(), org.odk.collect.strings.R.string.openmap_cartodb_positron), 0, 18, 256, CARTO_ATTRIBUTION, - "http://1.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png" - )), - new WmsOption("dark_matter", org.odk.collect.strings.R.string.carto_map_style_dark_matter, new WebMapService( - getLocalizedString(getApplication(), org.odk.collect.strings.R.string.openmap_cartodb_darkmatter), 0, 18, 256, CARTO_ATTRIBUTION, - "http://1.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png" - )) - ) - )); + sourceOptions.add(new SourceOption(BASEMAP_SOURCE_OSM, org.odk.collect.strings.R.string.basemap_source_osm, + MapboxClassInstanceCreator.createMapboxMapConfigurator(BASEMAP_SOURCE_OSM) + )); + sourceOptions.add(new SourceOption(BASEMAP_SOURCE_USGS, org.odk.collect.strings.R.string.basemap_source_usgs, + MapboxClassInstanceCreator.createMapboxMapConfigurator(BASEMAP_SOURCE_USGS) + )); + sourceOptions.add(new SourceOption(BASEMAP_SOURCE_CARTO, org.odk.collect.strings.R.string.basemap_source_carto, + MapboxClassInstanceCreator.createMapboxMapConfigurator(BASEMAP_SOURCE_CARTO) + )); + } MapConfiguratorProvider.sourceOptions = sourceOptions.toArray(new SourceOption[]{}); } diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt b/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt index 42a55b1340a..e7471b4ea43 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt @@ -4,29 +4,20 @@ import org.odk.collect.android.application.MapboxClassInstanceCreator import org.odk.collect.googlemaps.GoogleMapFragment import org.odk.collect.maps.MapFragment import org.odk.collect.maps.MapFragmentFactory -import org.odk.collect.osmdroid.OsmDroidMapFragment import org.odk.collect.settings.SettingsProvider -import org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_CARTO -import org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_MAPBOX -import org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_OSM -import org.odk.collect.settings.keys.ProjectKeys.BASEMAP_SOURCE_USGS +import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.settings.keys.ProjectKeys.KEY_BASEMAP_SOURCE class MapFragmentFactoryImpl(private val settingsProvider: SettingsProvider) : MapFragmentFactory { override fun createMapFragment(): MapFragment { val settings = settingsProvider.getUnprotectedSettings() - + val basemapSource = settings.getString(KEY_BASEMAP_SOURCE) return when { - isBasemapOSM(settings.getString(KEY_BASEMAP_SOURCE)) -> OsmDroidMapFragment() - settings.getString(KEY_BASEMAP_SOURCE) == BASEMAP_SOURCE_MAPBOX -> MapboxClassInstanceCreator.createMapboxMapFragment() - else -> GoogleMapFragment() + basemapSource == ProjectKeys.BASEMAP_SOURCE_GOOGLE -> GoogleMapFragment() + else -> MapboxClassInstanceCreator.createMapboxMapFragment( + basemapSource ?: ProjectKeys.BASEMAP_SOURCE_MAPBOX + ) } } - - private fun isBasemapOSM(basemap: String?): Boolean { - return basemap == BASEMAP_SOURCE_OSM || - basemap == BASEMAP_SOURCE_USGS || - basemap == BASEMAP_SOURCE_CARTO - } } diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java deleted file mode 100644 index e2999ec6a34..00000000000 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.odk.collect.mapbox; - -import android.content.Context; - -import androidx.preference.Preference; - -import com.mapbox.maps.Style; - -import org.odk.collect.androidshared.system.OpenGLVersionChecker; -import org.odk.collect.androidshared.ui.PrefUtils; -import org.odk.collect.androidshared.ui.ToastUtils; -import org.odk.collect.maps.MapConfigurator; -import org.odk.collect.maps.layers.MbtilesFile; -import org.odk.collect.settings.keys.ProjectKeys; -import org.odk.collect.shared.settings.Settings; - -import java.io.File; -import java.util.Collections; -import java.util.List; - -public class MapboxMapConfigurator implements MapConfigurator { - private final int sourceLabelId = org.odk.collect.strings.R.string.basemap_source_mapbox; - private final MapboxUrlOption[] options = { - new MapboxUrlOption(Style.MAPBOX_STREETS, org.odk.collect.strings.R.string.streets), - new MapboxUrlOption(Style.LIGHT, org.odk.collect.strings.R.string.light), - new MapboxUrlOption(Style.DARK, org.odk.collect.strings.R.string.dark), - new MapboxUrlOption(Style.SATELLITE, org.odk.collect.strings.R.string.satellite), - new MapboxUrlOption(Style.SATELLITE_STREETS, org.odk.collect.strings.R.string.hybrid), - new MapboxUrlOption(Style.OUTDOORS, org.odk.collect.strings.R.string.outdoors) - }; - - @Override public boolean isAvailable(Context context) { - /* - * The Mapbox SDK for Android requires OpenGL ES version 3. - * See: https://github.com/mapbox/mapbox-maps-android/blob/main/CHANGELOG.md#1100-november-29-2023 - */ - return OpenGLVersionChecker.isOpenGLv3Supported(context); - } - - @Override public void showUnavailableMessage(Context context) { - ToastUtils.showLongToast(context.getString(org.odk.collect.strings.R.string.basemap_source_unavailable, context.getString(sourceLabelId))); - } - - @Override public List createPrefs(Context context, Settings settings) { - int[] labelIds = new int[options.length]; - String[] values = new String[options.length]; - for (int i = 0; i < options.length; i++) { - labelIds[i] = options[i].labelId; - values[i] = options[i].url; - } - String prefTitle = context.getString( - org.odk.collect.strings.R.string.map_style_label, context.getString(sourceLabelId)); - return Collections.singletonList(PrefUtils.createListPref( - context, ProjectKeys.KEY_MAPBOX_MAP_STYLE, prefTitle, labelIds, values, settings - )); - } - - - @Override public boolean supportsLayer(File file) { - // MapboxMapFragment supports any file that MbtilesFile can read. - return MbtilesFile.readLayerType(file) != null; - } - - @Override public String getDisplayName(File file) { - String name = MbtilesFile.readName(file); - return name != null ? name : file.getName(); - } - - static class MapboxUrlOption { - final String url; - final int labelId; - - MapboxUrlOption(String url, int labelId) { - this.url = url; - this.labelId = labelId; - } - } -} diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt new file mode 100644 index 00000000000..1a044b772c8 --- /dev/null +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt @@ -0,0 +1,109 @@ +package org.odk.collect.mapbox + +import android.content.Context +import androidx.preference.Preference +import com.mapbox.maps.Style +import org.odk.collect.androidshared.system.OpenGLVersionChecker.isOpenGLv3Supported +import org.odk.collect.androidshared.ui.PrefUtils +import org.odk.collect.androidshared.ui.ToastUtils.showLongToast +import org.odk.collect.maps.MapConfigurator +import org.odk.collect.maps.layers.MbtilesFile +import org.odk.collect.settings.keys.ProjectKeys +import org.odk.collect.shared.settings.Settings +import org.odk.collect.strings.R +import java.io.File + +class MapboxMapConfigurator(configuration: String) : MapConfigurator { + + private val configuration = configurations.getValue(configuration) + + override fun isAvailable(context: Context): Boolean { + /* + * The Mapbox SDK for Android requires OpenGL ES version 3. + * See: https://github.com/mapbox/mapbox-maps-android/blob/main/CHANGELOG.md#1100-november-29-2023 + */ + return isOpenGLv3Supported(context) + } + + override fun showUnavailableMessage(context: Context) { + showLongToast( + context.getString( + R.string.basemap_source_unavailable, + context.getString(configuration.name) + ) + ) + } + + override fun createPrefs(context: Context, settings: Settings): List { + return if (configuration.settingsKey != null) { + listOf( + PrefUtils.createListPref( + context, + configuration.settingsKey, + context.getString( + R.string.map_style_label, + context.getString(configuration.name) + ), + configuration.options.map { it.labelId }.toIntArray(), + configuration.options.map { it.url }.toTypedArray(), + settings + ) + ) + } else { + emptyList() + } + } + + override fun supportsLayer(file: File): Boolean { + // MapboxMapFragment supports any file that MbtilesFile can read. + return MbtilesFile.readLayerType(file) != null + } + + override fun getDisplayName(file: File): String { + val name = MbtilesFile.readName(file) + return if (name != null) name else file.getName() + } + + private class MapboxConfiguration( + val name: Int, + val settingsKey: String?, + val options: Array = emptyArray() + ) + + private class MapboxUrlOption(val url: String, val labelId: Int) + + companion object { + private val configurations = mapOf( + ProjectKeys.BASEMAP_SOURCE_MAPBOX to MapboxConfiguration( + R.string.basemap_source_mapbox, + ProjectKeys.KEY_MAPBOX_MAP_STYLE, + arrayOf( + MapboxUrlOption(Style.MAPBOX_STREETS, R.string.streets), + MapboxUrlOption(Style.LIGHT, R.string.light), + MapboxUrlOption(Style.DARK, R.string.dark), + MapboxUrlOption(Style.SATELLITE, R.string.satellite), + MapboxUrlOption(Style.SATELLITE_STREETS, R.string.hybrid), + MapboxUrlOption(Style.OUTDOORS, R.string.outdoors) + ) + ), + ProjectKeys.BASEMAP_SOURCE_OSM to MapboxConfiguration(R.string.basemap_source_osm, null), + ProjectKeys.BASEMAP_SOURCE_USGS to MapboxConfiguration( + R.string.basemap_source_usgs, + ProjectKeys.KEY_USGS_MAP_STYLE, + arrayOf( + MapboxUrlOption("topographic", R.string.topographic), + MapboxUrlOption("hybrid", R.string.hybrid), + MapboxUrlOption("satellite", R.string.satellite), + ) + ), + ProjectKeys.BASEMAP_SOURCE_CARTO to MapboxConfiguration( + R.string.basemap_source_carto, + ProjectKeys.KEY_CARTO_MAP_STYLE, + arrayOf( + MapboxUrlOption("positron", R.string.openmap_cartodb_positron), + MapboxUrlOption("dark_matter", R.string.openmap_cartodb_darkmatter), + ) + ) + ) + } +} diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 80c19863b63..faa980bd706 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -76,7 +76,7 @@ import timber.log.Timber import java.io.File import java.io.IOException -class MapboxMapFragment : +class MapboxMapFragment(private val configuration: String) : MapViewModelMapFragment(), OnMapClickListener, OnMapLongClickListener { From b1b37e346c0ce5b4cc7f2f2d721e06933a56f72a Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 15:34:02 +0100 Subject: [PATCH 07/21] Add attribution to different Mapbox configurations --- .../android/geo/MapConfiguratorProvider.java | 4 -- mapbox/build.gradle.kts | 4 ++ .../mapbox/MapBoxInitializationFragment.kt | 2 +- .../odk/collect/mapbox/MapboxMapFragment.kt | 41 +++++++++++++++---- ...mapbox_initialization_fragment_layout.xml} | 0 .../res/layout/mapbox_map_fragment_layout.xml | 20 +++++++++ 6 files changed, 57 insertions(+), 14 deletions(-) rename mapbox/src/main/res/layout/{mapbox_fragment_layout.xml => mapbox_initialization_fragment_layout.xml} (100%) create mode 100644 mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java index 2fbcd67431f..0f72aeab1a3 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java @@ -24,10 +24,6 @@ public class MapConfiguratorProvider { private static SourceOption[] sourceOptions; private static final String USGS_URL_BASE = "https://basemap.nationalmap.gov/arcgis/rest/services"; - private static final String OSM_COPYRIGHT = "© OpenStreetMap contributors"; - private static final String CARTO_COPYRIGHT = "© CARTO"; - private static final String CARTO_ATTRIBUTION = OSM_COPYRIGHT + ", " + CARTO_COPYRIGHT; - private static final String USGS_ATTRIBUTION = "Map services and data available from U.S. Geological Survey,\nNational Geospatial Program."; private MapConfiguratorProvider() { diff --git a/mapbox/build.gradle.kts b/mapbox/build.gradle.kts index bf1f505702d..23156e75f26 100644 --- a/mapbox/build.gradle.kts +++ b/mapbox/build.gradle.kts @@ -26,6 +26,10 @@ android { targetCompatibility = JavaVersion.VERSION_17 } + buildFeatures { + viewBinding = true + } + namespace = "org.odk.collect.mapbox" } diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt index 9895300e904..652fb5df54f 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapBoxInitializationFragment.kt @@ -37,7 +37,7 @@ class MapBoxInitializationFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View { - val rootView = inflater.inflate(R.layout.mapbox_fragment_layout, container, false) + val rootView = inflater.inflate(R.layout.mapbox_initialization_fragment_layout, container, false) initMapBox(rootView) return rootView } diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index faa980bd706..11f6b2e9aca 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -51,6 +51,7 @@ import com.mapbox.maps.plugin.gestures.addOnScaleListener import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.odk.collect.androidshared.utils.ScreenUtils +import org.odk.collect.mapbox.databinding.MapboxMapFragmentLayoutBinding import org.odk.collect.maps.MapFragment import org.odk.collect.maps.MapFragment.ErrorListener import org.odk.collect.maps.MapFragment.FeatureListener @@ -70,13 +71,14 @@ import org.odk.collect.maps.markers.MarkerIconDescription import org.odk.collect.maps.traces.LineDescription import org.odk.collect.maps.traces.PolygonDescription import org.odk.collect.settings.SettingsProvider +import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE import org.odk.collect.shared.injection.ObjectProviderHost import timber.log.Timber import java.io.File import java.io.IOException -class MapboxMapFragment(private val configuration: String) : +class MapboxMapFragment(configuration: String) : MapViewModelMapFragment(), OnMapClickListener, OnMapLongClickListener { @@ -114,13 +116,17 @@ class MapboxMapFragment(private val configuration: String) : } private val settingsProvider: SettingsProvider by lazy { - (requireActivity().applicationContext as ObjectProviderHost).getObjectProvider().provide(SettingsProvider::class.java) + (requireActivity().applicationContext as ObjectProviderHost).getObjectProvider() + .provide(SettingsProvider::class.java) } private val referenceLayerRepository: ReferenceLayerRepository by lazy { - (requireActivity().applicationContext as ObjectProviderHost).getObjectProvider().provide(ReferenceLayerRepository::class.java) + (requireActivity().applicationContext as ObjectProviderHost).getObjectProvider() + .provide(ReferenceLayerRepository::class.java) } + private val configuration = configurations.getValue(configuration) + override fun init(readyListener: ReadyListener?, errorListener: ErrorListener?) { mapReadyListener = readyListener @@ -137,7 +143,8 @@ class MapboxMapFragment(private val configuration: String) : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - AppInitializer.getInstance(requireContext()).initializeComponent(MapboxMapsInitializer::class.java) + AppInitializer.getInstance(requireContext()) + .initializeComponent(MapboxMapsInitializer::class.java) } override fun onCreateView( @@ -145,7 +152,9 @@ class MapboxMapFragment(private val configuration: String) : container: ViewGroup?, savedInstanceState: Bundle? ): View { - mapView = MapView(inflater.context).apply { + val binding = MapboxMapFragmentLayoutBinding.inflate(inflater, container, false) + + mapView = binding.map.apply { compass.position = Gravity.TOP or Gravity.START compass.marginTop = 36f compass.marginBottom = 36f @@ -164,7 +173,8 @@ class MapboxMapFragment(private val configuration: String) : override fun onScaleBegin(detector: StandardScaleGestureDetector) = Unit override fun onScaleEnd(detector: StandardScaleGestureDetector) { - val center = MapPoint(cameraState.center.latitude(), cameraState.center.longitude()) + val center = + MapPoint(cameraState.center.latitude(), cameraState.center.longitude()) getMapViewModel().onUserZoom(center, cameraState.zoom) } }) @@ -173,7 +183,8 @@ class MapboxMapFragment(private val configuration: String) : override fun onMoveBegin(detector: MoveGestureDetector) = Unit override fun onMoveEnd(detector: MoveGestureDetector) { - val center = MapPoint(cameraState.center.latitude(), cameraState.center.longitude()) + val center = + MapPoint(cameraState.center.latitude(), cameraState.center.longitude()) getMapViewModel().onUserMove(center, cameraState.zoom) } }) @@ -238,7 +249,12 @@ class MapboxMapFragment(private val configuration: String) : } }) - return mapView + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val binding = MapboxMapFragmentLayoutBinding.bind(view) + binding.attribution.text = configuration.attribution } override fun onDestroy() { @@ -590,6 +606,13 @@ class MapboxMapFragment(private val configuration: String) : } companion object { - const val KEY_STYLE_URL = "STYLE_URL" + private class Configuration(val attribution: String? = null) + + private val configurations = mapOf( + ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration(), + ProjectKeys.BASEMAP_SOURCE_OSM to Configuration("© OpenStreetMap contributors"), + ProjectKeys.BASEMAP_SOURCE_USGS to Configuration("Map services and data available from U.S. Geological Survey, National Geospatial Program."), + ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration("© OpenStreetMap contributors, © CARTO"), + ) } } diff --git a/mapbox/src/main/res/layout/mapbox_fragment_layout.xml b/mapbox/src/main/res/layout/mapbox_initialization_fragment_layout.xml similarity index 100% rename from mapbox/src/main/res/layout/mapbox_fragment_layout.xml rename to mapbox/src/main/res/layout/mapbox_initialization_fragment_layout.xml diff --git a/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml b/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml new file mode 100644 index 00000000000..d8928c011c3 --- /dev/null +++ b/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file From d8996a9782812d083730e95917273c0c8334ade5 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 17:08:18 +0100 Subject: [PATCH 08/21] Fix reference layer loading --- .../java/org/odk/collect/mapbox/MapboxMapFragment.kt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 11f6b2e9aca..d36f420b8f8 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -101,7 +101,7 @@ class MapboxMapFragment(configuration: String) : private var featureDragEndListener: FeatureListener? = null private var tileServer: TileHttpServer? = null private var referenceLayerFile: File? = null - private var topStyleLayerId: String? = null + private var styleLayer: String? = null private val _mapViewModel by viewModels { viewModelFactory { @@ -216,6 +216,7 @@ class MapboxMapFragment(configuration: String) : } getMapViewModel().getReferenceLayer().observe(viewLifecycleOwner) { + referenceLayerFile = it loadStyle() } @@ -265,9 +266,9 @@ class MapboxMapFragment(configuration: String) : private fun loadStyle() { mapboxMap.loadStyleUri(styleUrl) { - if (topStyleLayerId == null) { + if (styleLayer == null) { // remember the id of the top style layer - topStyleLayerId = it.styleLayers.last().id + styleLayer = it.styleLayers.last().id } loadReferenceOverlay() } @@ -594,8 +595,8 @@ class MapboxMapFragment(configuration: String) : } private fun addOverlayLayer(layer: Layer) { - topStyleLayerId?.let { - mapboxMap.getStyle()?.addLayerAbove(layer, topStyleLayerId) + styleLayer?.let { + mapboxMap.getStyle()?.addLayerAbove(layer, styleLayer) } } From 0b3c7033ed8fbd1a439db1fd4ff2592ffea1f24c Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 17:29:51 +0100 Subject: [PATCH 09/21] Add support for OSM basemaps in Mapbox --- .../odk/collect/mapbox/MapboxMapFragment.kt | 45 ++++++++++++++----- .../res/layout/mapbox_map_fragment_layout.xml | 9 ---- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index d36f420b8f8..8a4076ac42a 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -19,14 +19,20 @@ import com.mapbox.maps.MapboxMap import com.mapbox.maps.Style import com.mapbox.maps.dsl.cameraOptions import com.mapbox.maps.extension.style.layers.Layer +import com.mapbox.maps.extension.style.layers.addLayer import com.mapbox.maps.extension.style.layers.addLayerAbove +import com.mapbox.maps.extension.style.layers.addLayerAt import com.mapbox.maps.extension.style.layers.generated.LineLayer import com.mapbox.maps.extension.style.layers.generated.RasterLayer +import com.mapbox.maps.extension.style.layers.generated.rasterLayer +import com.mapbox.maps.extension.style.layers.getLayer import com.mapbox.maps.extension.style.sources.Source import com.mapbox.maps.extension.style.sources.TileSet import com.mapbox.maps.extension.style.sources.addSource import com.mapbox.maps.extension.style.sources.generated.RasterSource +import com.mapbox.maps.extension.style.sources.generated.Scheme import com.mapbox.maps.extension.style.sources.generated.VectorSource +import com.mapbox.maps.extension.style.sources.generated.rasterSource import com.mapbox.maps.extension.style.sources.getSource import com.mapbox.maps.loader.MapboxMapsInitializer import com.mapbox.maps.plugin.animation.MapAnimationOptions.Companion.mapAnimationOptions @@ -253,11 +259,6 @@ class MapboxMapFragment(configuration: String) : return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val binding = MapboxMapFragmentLayoutBinding.bind(view) - binding.attribution.text = configuration.attribution - } - override fun onDestroy() { tileServer?.destroy() MarkerIconCreator.clearCache() @@ -265,12 +266,34 @@ class MapboxMapFragment(configuration: String) : } private fun loadStyle() { - mapboxMap.loadStyleUri(styleUrl) { - if (styleLayer == null) { - // remember the id of the top style layer + if (configuration.rasterUrl != null) { + mapboxMap.loadStyleUri("") { style -> + val tileSet = TileSet.Builder("2.2.0", listOf(configuration.rasterUrl)) + .attribution(configuration.attribution ?: "") + .scheme(Scheme.XYZ) + .build() + + if (style.getSource("basemap_source") == null) { + style.addSource( + rasterSource("basemap_source") { + tileSet(tileSet) + tileSize(256) + } + ) + } + + if (style.getLayer("basemap_layer") == null) { + style.addLayer(rasterLayer("basemap_layer", "basemap_source") {}) + styleLayer = "basemap_layer" + } + + loadReferenceOverlay() + } + } else { + mapboxMap.loadStyleUri(styleUrl) { styleLayer = it.styleLayers.last().id + loadReferenceOverlay() } - loadReferenceOverlay() } } @@ -607,11 +630,11 @@ class MapboxMapFragment(configuration: String) : } companion object { - private class Configuration(val attribution: String? = null) + private class Configuration(val attribution: String? = null, val rasterUrl: String? = null) private val configurations = mapOf( ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration(), - ProjectKeys.BASEMAP_SOURCE_OSM to Configuration("© OpenStreetMap contributors"), + ProjectKeys.BASEMAP_SOURCE_OSM to Configuration("© OpenStreetMap contributors", "https://tile.openstreetmap.org/{z}/{x}/{y}.png"), ProjectKeys.BASEMAP_SOURCE_USGS to Configuration("Map services and data available from U.S. Geological Survey, National Geospatial Program."), ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration("© OpenStreetMap contributors, © CARTO"), ) diff --git a/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml b/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml index d8928c011c3..f1a3095dea1 100644 --- a/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml +++ b/mapbox/src/main/res/layout/mapbox_map_fragment_layout.xml @@ -8,13 +8,4 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> - - \ No newline at end of file From 7d6605f7c2d13a27512edeaea4d2f35c69c4467f Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 17:47:10 +0100 Subject: [PATCH 10/21] Add layer at bottom if there is already layers --- .../main/java/org/odk/collect/mapbox/MapboxMapFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 8a4076ac42a..f93afc8545a 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -283,7 +283,12 @@ class MapboxMapFragment(configuration: String) : } if (style.getLayer("basemap_layer") == null) { - style.addLayer(rasterLayer("basemap_layer", "basemap_source") {}) + if (style.styleLayers.isEmpty()) { + style.addLayer(rasterLayer("basemap_layer", "basemap_source") {}) + } else { + style.addLayerAt(rasterLayer("basemap_layer", "basemap_source") {}, 0) + } + styleLayer = "basemap_layer" } From 741e2b7ea690733684868cf89d58d3bf6e460073 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 18:10:03 +0100 Subject: [PATCH 11/21] Add support for carto and USGS maps back in --- .../android/geo/MapConfiguratorProvider.java | 2 - .../odk/collect/mapbox/MapboxMapFragment.kt | 121 +++++++++++++----- 2 files changed, 86 insertions(+), 37 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java index 0f72aeab1a3..5a4d6516f5b 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java @@ -22,8 +22,6 @@ public class MapConfiguratorProvider { private static SourceOption[] sourceOptions; - private static final String USGS_URL_BASE = - "https://basemap.nationalmap.gov/arcgis/rest/services"; private MapConfiguratorProvider() { diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index f93afc8545a..56e1590ff79 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -80,6 +80,7 @@ import org.odk.collect.settings.SettingsProvider import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE import org.odk.collect.shared.injection.ObjectProviderHost +import org.odk.collect.shared.settings.Settings import timber.log.Timber import java.io.File import java.io.IOException @@ -89,7 +90,7 @@ class MapboxMapFragment(configuration: String) : OnMapClickListener, OnMapLongClickListener { - private lateinit var styleUrl: String + private lateinit var settings: Settings private lateinit var mapView: MapView private lateinit var mapboxMap: MapboxMap @@ -217,13 +218,13 @@ class MapboxMapFragment(configuration: String) : } getMapViewModel().getSettings(setOf(KEY_MAPBOX_MAP_STYLE)).observe(viewLifecycleOwner) { - styleUrl = it.getString(KEY_MAPBOX_MAP_STYLE) ?: Style.MAPBOX_STREETS - loadStyle() + settings = it + loadStyle(settings) } getMapViewModel().getReferenceLayer().observe(viewLifecycleOwner) { referenceLayerFile = it - loadStyle() + loadStyle(settings) } getMapViewModel().zoom.observe(viewLifecycleOwner, object : ZoomObserver() { @@ -265,39 +266,51 @@ class MapboxMapFragment(configuration: String) : super.onDestroy() } - private fun loadStyle() { - if (configuration.rasterUrl != null) { - mapboxMap.loadStyleUri("") { style -> - val tileSet = TileSet.Builder("2.2.0", listOf(configuration.rasterUrl)) - .attribution(configuration.attribution ?: "") - .scheme(Scheme.XYZ) - .build() + private fun loadStyle(settings: Settings) { + val uri = if (configuration.uri != null) { + configuration.uri + } else if (configuration.setting != null) { + configuration.uris.getValue(settings.getString(configuration.setting)!!) + } else { + throw IllegalArgumentException("Invalid Configuration!") + } + + when (uri) { + is BasemapUri.Raster -> { + mapboxMap.loadStyleUri("") { style -> + val tileSet = TileSet.Builder("2.2.0", listOf(uri.value)) + .attribution(configuration.attribution ?: "") + .scheme(Scheme.XYZ) + .build() + + if (style.getSource("basemap_source") == null) { + style.addSource( + rasterSource("basemap_source") { + tileSet(tileSet) + tileSize(256) + } + ) + } - if (style.getSource("basemap_source") == null) { - style.addSource( - rasterSource("basemap_source") { - tileSet(tileSet) - tileSize(256) + if (style.getLayer("basemap_layer") == null) { + if (style.styleLayers.isEmpty()) { + style.addLayer(rasterLayer("basemap_layer", "basemap_source") {}) + } else { + style.addLayerAt(rasterLayer("basemap_layer", "basemap_source") {}, 0) } - ) - } - if (style.getLayer("basemap_layer") == null) { - if (style.styleLayers.isEmpty()) { - style.addLayer(rasterLayer("basemap_layer", "basemap_source") {}) - } else { - style.addLayerAt(rasterLayer("basemap_layer", "basemap_source") {}, 0) + styleLayer = "basemap_layer" } - styleLayer = "basemap_layer" + loadReferenceOverlay() } - - loadReferenceOverlay() } - } else { - mapboxMap.loadStyleUri(styleUrl) { - styleLayer = it.styleLayers.last().id - loadReferenceOverlay() + + is BasemapUri.Mapbox -> { + mapboxMap.loadStyleUri(uri.value) { + styleLayer = it.styleLayers.last().id + loadReferenceOverlay() + } } } } @@ -635,13 +648,51 @@ class MapboxMapFragment(configuration: String) : } companion object { - private class Configuration(val attribution: String? = null, val rasterUrl: String? = null) + private class Configuration( + val attribution: String? = null, + val uri: BasemapUri? = null, + val setting: String? = null, + val uris: Map = emptyMap() + ) + + private sealed class BasemapUri(val value: String) { + class Raster(uri: String) : BasemapUri(uri) + class Mapbox(uri: String) : BasemapUri(uri) + } private val configurations = mapOf( - ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration(), - ProjectKeys.BASEMAP_SOURCE_OSM to Configuration("© OpenStreetMap contributors", "https://tile.openstreetmap.org/{z}/{x}/{y}.png"), - ProjectKeys.BASEMAP_SOURCE_USGS to Configuration("Map services and data available from U.S. Geological Survey, National Geospatial Program."), - ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration("© OpenStreetMap contributors, © CARTO"), + ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration( + setting = KEY_MAPBOX_MAP_STYLE, + uris = mapOf( + Style.MAPBOX_STREETS to BasemapUri.Mapbox(Style.MAPBOX_STREETS), + Style.LIGHT to BasemapUri.Mapbox(Style.LIGHT), + Style.DARK to BasemapUri.Mapbox(Style.DARK), + Style.SATELLITE to BasemapUri.Mapbox(Style.SATELLITE), + Style.SATELLITE_STREETS to BasemapUri.Mapbox(Style.SATELLITE_STREETS), + Style.OUTDOORS to BasemapUri.Mapbox(Style.OUTDOORS) + ) + ), + ProjectKeys.BASEMAP_SOURCE_OSM to Configuration( + attribution = "© OpenStreetMap contributors", + uri = BasemapUri.Raster("https://tile.openstreetmap.org/{z}/{x}/{y}.png") + ), + ProjectKeys.BASEMAP_SOURCE_USGS to Configuration( + attribution = "Map services and data available from U.S. Geological Survey, National Geospatial Program.", + setting = ProjectKeys.KEY_USGS_MAP_STYLE, + uris = mapOf( + "topographic" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}"), + "hybrid" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}"), + "satellite" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}") + ) + ), + ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration( + attribution = "© OpenStreetMap contributors, © CARTO", + setting = ProjectKeys.KEY_CARTO_MAP_STYLE, + uris = mapOf( + "positron" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"), + "dark_matter" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png") + ) + ), ) } } From ba6dd33a7d23e2c7825331de19ed2f6acf2ef027 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 18:17:39 +0100 Subject: [PATCH 12/21] Delete OSMDroid --- collect_app/build.gradle | 1 - collect_app/proguard-rules.txt | 1 - .../collect/android/application/Collect.java | 18 - .../initialization/MapsInitializer.kt | 2 - .../config/CollectOsmDroidDependencyModule.kt | 26 - .../android/geo/MapFragmentFactoryImplTest.kt | 34 - osmdroid/.gitignore | 1 - osmdroid/build.gradle.kts | 50 - osmdroid/src/main/AndroidManifest.xml | 4 - .../org/odk/collect/osmdroid/DaggerSetup.kt | 38 - .../collect/osmdroid/OsmDroidInitializer.kt | 10 - .../osmdroid/OsmDroidMapConfigurator.java | 109 -- .../collect/osmdroid/OsmDroidMapFragment.java | 1061 ----------------- .../osmdroid/OsmMBTileModuleProvider.java | 120 -- .../collect/osmdroid/OsmMBTileProvider.java | 68 -- .../odk/collect/osmdroid/OsmMBTileSource.java | 155 --- .../odk/collect/osmdroid/WebMapService.java | 59 - .../src/main/res/layout/osm_map_layout.xml | 6 - settings.gradle | 1 - 19 files changed, 1764 deletions(-) delete mode 100644 collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt delete mode 100644 osmdroid/.gitignore delete mode 100644 osmdroid/build.gradle.kts delete mode 100644 osmdroid/src/main/AndroidManifest.xml delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidInitializer.kt delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapConfigurator.java delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileModuleProvider.java delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileProvider.java delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileSource.java delete mode 100644 osmdroid/src/main/java/org/odk/collect/osmdroid/WebMapService.java delete mode 100644 osmdroid/src/main/res/layout/osm_map_layout.xml diff --git a/collect_app/build.gradle b/collect_app/build.gradle index e3547e4cdcd..20e1fcab695 100644 --- a/collect_app/build.gradle +++ b/collect_app/build.gradle @@ -274,7 +274,6 @@ dependencies { } implementation project(':external-app') implementation project(':maps') - implementation project(':osmdroid') implementation project(':entities') implementation project(':crash-handler') implementation project(':selfie-camera') diff --git a/collect_app/proguard-rules.txt b/collect_app/proguard-rules.txt index 6ba574a9f3c..76ad16e90ad 100644 --- a/collect_app/proguard-rules.txt +++ b/collect_app/proguard-rules.txt @@ -1,7 +1,6 @@ -dontwarn com.google.** -dontwarn au.com.bytecode.** -dontwarn org.joda.time.** --dontwarn org.osmdroid.** -dontwarn org.xmlpull.v1.** -dontwarn org.hamcrest.** -dontwarn com.rarepebble.** diff --git a/collect_app/src/main/java/org/odk/collect/android/application/Collect.java b/collect_app/src/main/java/org/odk/collect/android/application/Collect.java index 13b155ea07f..bf133f3ec18 100644 --- a/collect_app/src/main/java/org/odk/collect/android/application/Collect.java +++ b/collect_app/src/main/java/org/odk/collect/android/application/Collect.java @@ -31,7 +31,6 @@ import org.odk.collect.android.injection.config.CollectEntitiesDependencyModule; import org.odk.collect.android.injection.config.CollectGeoDependencyModule; import org.odk.collect.android.injection.config.CollectGoogleMapsDependencyModule; -import org.odk.collect.android.injection.config.CollectOsmDroidDependencyModule; import org.odk.collect.android.injection.config.CollectProjectsDependencyModule; import org.odk.collect.android.injection.config.CollectSelfieCameraDependencyModule; import org.odk.collect.android.injection.config.DaggerAppDependencyComponent; @@ -66,9 +65,6 @@ import org.odk.collect.location.LocationDependencyComponentProvider; import org.odk.collect.location.LocationDependencyModule; import org.odk.collect.maps.layers.ReferenceLayerRepository; -import org.odk.collect.osmdroid.DaggerOsmDroidDependencyComponent; -import org.odk.collect.osmdroid.OsmDroidDependencyComponent; -import org.odk.collect.osmdroid.OsmDroidDependencyComponentProvider; import org.odk.collect.projects.DaggerProjectsDependencyComponent; import org.odk.collect.projects.ProjectsDependencyComponent; import org.odk.collect.projects.ProjectsDependencyComponentProvider; @@ -92,7 +88,6 @@ public class Collect extends Application implements AudioRecorderDependencyComponentProvider, ProjectsDependencyComponentProvider, GeoDependencyComponentProvider, - OsmDroidDependencyComponentProvider, StateStore, ObjectProviderHost, EntitiesDependencyComponentProvider, @@ -113,7 +108,6 @@ public class Collect extends Application implements private AudioRecorderDependencyComponent audioRecorderDependencyComponent; private ProjectsDependencyComponent projectsDependencyComponent; private GeoDependencyComponent geoDependencyComponent; - private OsmDroidDependencyComponent osmDroidDependencyComponent; private EntitiesDependencyComponent entitiesDependencyComponent; private SelfieCameraDependencyComponent selfieCameraDependencyComponent; private GoogleMapsDependencyComponent googleMapsDependencyComponent; @@ -272,18 +266,6 @@ public GeoDependencyComponent getGeoDependencyComponent() { return geoDependencyComponent; } - @NonNull - @Override - public OsmDroidDependencyComponent getOsmDroidDependencyComponent() { - if (osmDroidDependencyComponent == null) { - osmDroidDependencyComponent = DaggerOsmDroidDependencyComponent.builder() - .osmDroidDependencyModule(new CollectOsmDroidDependencyModule(applicationComponent)) - .build(); - } - - return osmDroidDependencyComponent; - } - @NonNull @Override public ObjectProvider getObjectProvider() { diff --git a/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt b/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt index f7e480ab44f..2502443bfb2 100644 --- a/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt +++ b/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt @@ -4,7 +4,6 @@ import android.content.Context import android.os.Handler import com.google.android.gms.maps.MapView import org.odk.collect.android.geo.MapConfiguratorProvider -import org.odk.collect.osmdroid.OsmDroidInitializer import org.odk.collect.settings.SettingsProvider import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.utilities.UserAgentProvider @@ -54,7 +53,6 @@ class MapsInitializer @Inject constructor( // This has to happen on the main thread but we might call `initialize` from tests MapView(context).onCreate(null) } - OsmDroidInitializer.initialize(userAgentProvider.userAgent) } catch (ignore: Exception) { // ignored } catch (ignore: Error) { diff --git a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt b/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt deleted file mode 100644 index 2887a64f1f5..00000000000 --- a/collect_app/src/main/java/org/odk/collect/android/injection/config/CollectOsmDroidDependencyModule.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.odk.collect.android.injection.config - -import org.odk.collect.android.geo.MapConfiguratorProvider -import org.odk.collect.maps.MapConfigurator -import org.odk.collect.maps.layers.ReferenceLayerRepository -import org.odk.collect.osmdroid.OsmDroidDependencyModule -import org.odk.collect.settings.SettingsProvider -import org.odk.collect.settings.keys.ProjectKeys - -class CollectOsmDroidDependencyModule( - private val appDependencyComponent: AppDependencyComponent -) : OsmDroidDependencyModule() { - override fun providesReferenceLayerRepository(): ReferenceLayerRepository { - return appDependencyComponent.referenceLayerRepository() - } - - override fun providesMapConfigurator(): MapConfigurator { - return MapConfiguratorProvider.getConfigurator( - appDependencyComponent.settingsProvider().getUnprotectedSettings().getString(ProjectKeys.KEY_BASEMAP_SOURCE) - ) - } - - override fun providesSettingsProvider(): SettingsProvider { - return appDependencyComponent.settingsProvider() - } -} diff --git a/collect_app/src/test/java/org/odk/collect/android/geo/MapFragmentFactoryImplTest.kt b/collect_app/src/test/java/org/odk/collect/android/geo/MapFragmentFactoryImplTest.kt index 062a6e6f2d3..d745e57f5ab 100644 --- a/collect_app/src/test/java/org/odk/collect/android/geo/MapFragmentFactoryImplTest.kt +++ b/collect_app/src/test/java/org/odk/collect/android/geo/MapFragmentFactoryImplTest.kt @@ -4,7 +4,6 @@ import org.hamcrest.CoreMatchers.instanceOf import org.hamcrest.MatcherAssert.assertThat import org.junit.Test import org.odk.collect.googlemaps.GoogleMapFragment -import org.odk.collect.osmdroid.OsmDroidMapFragment import org.odk.collect.settings.InMemSettingsProvider import org.odk.collect.settings.keys.ProjectKeys @@ -13,39 +12,6 @@ class MapFragmentFactoryImplTest { private val settingsProvider = InMemSettingsProvider() private val mapFragmentFactoryImpl = MapFragmentFactoryImpl(settingsProvider) - @Test - fun `OsmDroidMapFragment should be return if any of OSM options selected in settings`() { - // BASEMAP_SOURCE_OSM - settingsProvider - .getUnprotectedSettings() - .save(ProjectKeys.KEY_BASEMAP_SOURCE, ProjectKeys.BASEMAP_SOURCE_OSM) - - assertThat( - mapFragmentFactoryImpl.createMapFragment(), - instanceOf(OsmDroidMapFragment::class.java) - ) - - // BASEMAP_SOURCE_USGS - settingsProvider - .getUnprotectedSettings() - .save(ProjectKeys.KEY_BASEMAP_SOURCE, ProjectKeys.BASEMAP_SOURCE_USGS) - - assertThat( - mapFragmentFactoryImpl.createMapFragment(), - instanceOf(OsmDroidMapFragment::class.java) - ) - - // BASEMAP_SOURCE_CARTO - settingsProvider - .getUnprotectedSettings() - .save(ProjectKeys.KEY_BASEMAP_SOURCE, ProjectKeys.BASEMAP_SOURCE_CARTO) - - assertThat( - mapFragmentFactoryImpl.createMapFragment(), - instanceOf(OsmDroidMapFragment::class.java) - ) - } - @Test fun `GoogleMapFragment should be return if Google Maps selected in settings`() { settingsProvider diff --git a/osmdroid/.gitignore b/osmdroid/.gitignore deleted file mode 100644 index 42afabfd2ab..00000000000 --- a/osmdroid/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/osmdroid/build.gradle.kts b/osmdroid/build.gradle.kts deleted file mode 100644 index da1c296553c..00000000000 --- a/osmdroid/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - alias(libs.plugins.androidLibrary) - alias(libs.plugins.kotlinKsp) -} - -apply(from = "../config/quality.gradle") - -android { - compileSdk = libs.versions.compileSdk.get().toInt() - - defaultConfig { - minSdk = libs.versions.minSdk.get().toInt() - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - isMinifyEnabled = false - } - } - - compileOptions { - isCoreLibraryDesugaringEnabled = true - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - } - - namespace = "org.odk.collect.osmdroid" -} - -dependencies { - coreLibraryDesugaring(libs.desugar) - - implementation(project(":shared")) - implementation(project(":androidshared")) - implementation(project(":icons")) - implementation(project(":maps")) - implementation(project(":settings")) - implementation(project(":strings")) - - implementation(libs.osmdroid) - implementation(libs.androidxFragmentKtx) - implementation(libs.androidxPreferenceKtx) - implementation(libs.timber) - implementation(libs.playServicesLocation) - implementation(libs.androidMaterial) - implementation(libs.dagger) - ksp(libs.daggerCompiler) -} diff --git a/osmdroid/src/main/AndroidManifest.xml b/osmdroid/src/main/AndroidManifest.xml deleted file mode 100644 index 8bdb7e14b38..00000000000 --- a/osmdroid/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt b/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt deleted file mode 100644 index 93bd4000511..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/DaggerSetup.kt +++ /dev/null @@ -1,38 +0,0 @@ -package org.odk.collect.osmdroid - -import dagger.Component -import dagger.Module -import dagger.Provides -import org.odk.collect.maps.MapConfigurator -import org.odk.collect.maps.layers.ReferenceLayerRepository -import org.odk.collect.settings.SettingsProvider -import javax.inject.Singleton - -interface OsmDroidDependencyComponentProvider { - val osmDroidDependencyComponent: OsmDroidDependencyComponent -} - -@Component(modules = [OsmDroidDependencyModule::class]) -@Singleton -interface OsmDroidDependencyComponent { - fun inject(osmDroidMapFragment: OsmDroidMapFragment) -} - -@Module -open class OsmDroidDependencyModule { - - @Provides - open fun providesReferenceLayerRepository(): ReferenceLayerRepository { - throw UnsupportedOperationException("This should be overridden by dependent application") - } - - @Provides - open fun providesMapConfigurator(): MapConfigurator { - throw UnsupportedOperationException("This should be overridden by dependent application") - } - - @Provides - open fun providesSettingsProvider(): SettingsProvider { - throw UnsupportedOperationException("This should be overridden by dependent application") - } -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidInitializer.kt b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidInitializer.kt deleted file mode 100644 index 0b736ff176b..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidInitializer.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.odk.collect.osmdroid - -import org.osmdroid.config.Configuration - -object OsmDroidInitializer { - - fun initialize(userAgent: String) { - Configuration.getInstance().userAgentValue = userAgent - } -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapConfigurator.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapConfigurator.java deleted file mode 100644 index 7ae900ae9c5..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapConfigurator.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.odk.collect.osmdroid; - -import static org.odk.collect.settings.keys.ProjectKeys.KEY_REFERENCE_LAYER; -import static kotlin.collections.SetsKt.setOf; - -import android.content.Context; -import android.os.Bundle; - -import androidx.preference.Preference; - -import org.odk.collect.androidshared.ui.PrefUtils; -import org.odk.collect.maps.MapConfigurator; -import org.odk.collect.maps.layers.MbtilesFile; -import org.odk.collect.shared.settings.Settings; - -import java.io.File; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -public class OsmDroidMapConfigurator implements MapConfigurator { - private final String prefKey; - private final int sourceLabelId; - private final WmsOption[] options; - - /** Constructs a configurator that renders just one Web Map Service. */ - public OsmDroidMapConfigurator(WebMapService service) { - prefKey = ""; - sourceLabelId = 0; - options = new WmsOption[] {new WmsOption("", 0, service)}; - } - - /** - * Constructs a configurator that offers a few Web Map Services to choose from. - * The choice of which Web Map Service will be stored in a string preference. - */ - public OsmDroidMapConfigurator(String prefKey, int sourceLabelId, WmsOption... options) { - this.prefKey = prefKey; - this.sourceLabelId = sourceLabelId; - this.options = options; - } - - @Override public boolean isAvailable(Context context) { - // OSMdroid is always supported, as far as we know. - return true; - } - - @Override public void showUnavailableMessage(Context context) { } - - @Override public List createPrefs(Context context, Settings settings) { - if (options.length > 1) { - int[] labelIds = new int[options.length]; - String[] values = new String[options.length]; - for (int i = 0; i < options.length; i++) { - labelIds[i] = options[i].labelId; - values[i] = options[i].id; - } - String prefTitle = context.getString( - org.odk.collect.strings.R.string.map_style_label, context.getString(sourceLabelId)); - return Collections.singletonList(PrefUtils.createListPref( - context, prefKey, prefTitle, labelIds, values, settings - )); - } - return Collections.emptyList(); - } - - @Override public Collection getPrefKeys() { - return prefKey.isEmpty() ? setOf(KEY_REFERENCE_LAYER) : setOf(prefKey, KEY_REFERENCE_LAYER); - } - - @Override public Bundle buildConfig(Settings prefs) { - Bundle config = new Bundle(); - if (options.length == 1) { - config.putSerializable(OsmDroidMapFragment.KEY_WEB_MAP_SERVICE, options[0].service); - } else { - String value = prefs.getString(prefKey); - for (int i = 0; i < options.length; i++) { - if (options[i].id.equals(value)) { - config.putSerializable(OsmDroidMapFragment.KEY_WEB_MAP_SERVICE, options[i].service); - } - } - } - config.putString(OsmDroidMapFragment.KEY_REFERENCE_LAYER, - prefs.getString(KEY_REFERENCE_LAYER)); - return config; - } - - @Override public boolean supportsLayer(File file) { - // OSMdroid supports only raster tiles. - return MbtilesFile.readLayerType(file) == MbtilesFile.LayerType.RASTER; - } - - @Override public String getDisplayName(File file) { - String name = MbtilesFile.readName(file); - return name != null ? name : file.getName(); - } - - public static class WmsOption { - final String id; - final int labelId; - final WebMapService service; - - public WmsOption(String id, int labelId, WebMapService service) { - this.id = id; - this.labelId = labelId; - this.service = service; - } - } -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java deleted file mode 100644 index 335e3ab31bf..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java +++ /dev/null @@ -1,1061 +0,0 @@ -/* - * Copyright (C) 2018 Nafundi - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -package org.odk.collect.osmdroid; - -import static androidx.core.graphics.drawable.BitmapDrawableKt.toDrawable; -import static org.odk.collect.maps.markers.MarkerIconCreator.toBitmap; -import static org.odk.collect.maps.traces.TraceDescriptionKt.getMarkersForPoints; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.drawable.Drawable; -import android.os.Bundle; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.lifecycle.ViewModel; -import androidx.lifecycle.ViewModelProvider; - -import org.jetbrains.annotations.NotNull; -import org.odk.collect.androidshared.system.ContextExt; -import org.odk.collect.maps.MapConfigurator; -import org.odk.collect.maps.MapFragment; -import org.odk.collect.maps.MapPoint; -import org.odk.collect.maps.MapViewModel; -import org.odk.collect.maps.MapViewModelMapFragment; -import org.odk.collect.maps.Zoom; -import org.odk.collect.maps.ZoomObserver; -import org.odk.collect.maps.circles.CircleDescription; -import org.odk.collect.maps.layers.MapFragmentReferenceLayerUtils; -import org.odk.collect.maps.layers.ReferenceLayerRepository; -import org.odk.collect.maps.markers.MarkerDescription; -import org.odk.collect.maps.markers.MarkerIconCreator; -import org.odk.collect.maps.markers.MarkerIconDescription; -import org.odk.collect.maps.traces.LineDescription; -import org.odk.collect.maps.traces.PolygonDescription; -import org.odk.collect.settings.SettingsProvider; -import org.osmdroid.api.IGeoPoint; -import org.osmdroid.events.MapListener; -import org.osmdroid.events.ScrollEvent; -import org.osmdroid.events.ZoomEvent; -import org.osmdroid.tileprovider.IRegisterReceiver; -import org.osmdroid.util.BoundingBox; -import org.osmdroid.util.GeoPoint; -import org.osmdroid.views.CustomZoomButtonsController; -import org.osmdroid.views.MapView; -import org.osmdroid.views.overlay.MapEventsOverlay; -import org.osmdroid.views.overlay.Marker; -import org.osmdroid.views.overlay.Overlay; -import org.osmdroid.views.overlay.Polygon; -import org.osmdroid.views.overlay.Polyline; -import org.osmdroid.views.overlay.ScaleBarOverlay; -import org.osmdroid.views.overlay.TilesOverlay; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.StreamSupport; - -import javax.inject.Inject; - -import timber.log.Timber; - -/** - * A MapFragment drawn by OSMDroid. - */ -public class OsmDroidMapFragment extends MapViewModelMapFragment { - - // Bundle keys understood by applyConfig(). - public static final String KEY_WEB_MAP_SERVICE = "WEB_MAP_SERVICE"; - - @Inject - ReferenceLayerRepository referenceLayerRepository; - - @Inject - MapConfigurator mapConfigurator; - - @Inject - SettingsProvider settingsProvider; - - private MapView map; - private ReadyListener readyListener; - private PointListener clickListener; - private PointListener longPressListener; - private FeatureListener featureClickListener; - private FeatureListener dragEndListener; - private int nextFeatureId = 1; - private final Map features = new HashMap<>(); - private IGeoPoint lastMapCenter; - private WebMapService webMapService; - private File referenceLayerFile; - private TilesOverlay referenceOverlay; - private boolean isSystemZooming; - private MapViewModel mapViewModel; - - @Override - public void init(@Nullable ReadyListener readyListener, @Nullable ErrorListener errorListener) { - this.readyListener = readyListener; - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - OsmDroidDependencyComponent component = ((OsmDroidDependencyComponentProvider) context.getApplicationContext()).getOsmDroidDependencyComponent(); - component.inject(this); - - ViewModelProvider.Factory viewModelFactory = new ViewModelProvider.Factory() { - @NonNull - @Override - public T create(@NonNull Class modelClass) { - return (T) new MapViewModel( - settingsProvider.getUnprotectedSettings(), - settingsProvider.getMetaSettings(), - referenceLayerRepository - ); - } - }; - - mapViewModel = new ViewModelProvider(this, viewModelFactory).get(MapViewModel.class); - } - - @Override - public void onDestroy() { - clearFeatures(); // prevent a memory leak due to refs held by markers - MarkerIconCreator.clearCache(); - super.onDestroy(); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.osm_map_layout, container, false); - map = view.findViewById(R.id.osm_map_view); - if (webMapService != null) { - map.setTileSource(webMapService.asOnlineTileSource()); - } - map.setMultiTouchControls(true); - map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); - map.setMinZoomLevel(2.0); - map.getController().setCenter(toGeoPoint(MapFragment.Companion.getINITIAL_CENTER())); - map.getController().setZoom((int) INITIAL_ZOOM); - map.setTilesScaledToDpi(true); - map.setFlingEnabled(false); - map.getOverlays().add(new ScaleBarOverlay(map)); - map.addMapListener(new MapListener() { - - private boolean initialized; - - @Override - public boolean onZoom(ZoomEvent event) { - if (!isSystemZooming) { - mapViewModel.onUserZoom(getCenter(), event.getZoomLevel()); - } - return false; - } - - @Override - public boolean onScroll(ScrollEvent event) { - // Ignore initial scroll event that we get when the map loads - if (initialized) { - mapViewModel.onUserMove(getCenter(), event.getSource().getZoomLevelDouble()); - } else { - initialized = true; - } - - return false; - } - }); - addAttributionAndMapEventsOverlays(); - loadReferenceOverlay(); - addMapLayoutChangeListener(map); - - new Handler().postDelayed(() -> { - // If the screen is rotated before the map is ready, this fragment - // could already be detached, which makes it unsafe to use. Only - // call the ReadyListener if this fragment is still attached. - if (readyListener != null && getActivity() != null) { - readyListener.onReady(this); - } - }, 100); - - mapViewModel.getSettings(mapConfigurator.getPrefKeys()).observe(getViewLifecycleOwner(), settings -> { - Bundle newConfig = mapConfigurator.buildConfig(settings); - onConfigChanged(newConfig); - }); - - mapViewModel.getZoom().observe(getViewLifecycleOwner(), new ZoomObserver() { - @Override - public void onZoomToPoint(@NonNull Zoom.Point zoom) { - isSystemZooming = true; - map.getController().setZoom((int) Math.round(zoom.getLevel())); - map.getController().setCenter(toGeoPoint(zoom.getPoint())); - isSystemZooming = false; - } - - @Override - public void onZoomToBox(@NonNull Zoom.Box zoom) { - List points = zoom.getBox(); - boolean animate = zoom.getAnimate(); - Double scaleFactor = zoom.getScaleFactor(); - - int count = 0; - List geoPoints = new ArrayList<>(); - MapPoint lastPoint = null; - for (MapPoint point : points) { - lastPoint = point; - geoPoints.add(toGeoPoint(point)); - count++; - } - if (count == 1) { - zoomToPoint(lastPoint, zoom.getAnimate()); - } else if (count > 1) { - // TODO(ping): Find a better solution. - // zoomToBoundingBox sometimes fails to zoom correctly, either - // zooming by the correct amount but leaving the bounding box - // off-center, or centering correctly but not zooming in enough. - // Adding a 100-ms delay avoids the problem most of the time, but - // not always; it's here because the old GeoShapeOsmMapActivity - // did it, not because it's known to be the best solution. - final BoundingBox box = BoundingBox.fromGeoPoints(geoPoints) - .increaseByScale((float) (1 / scaleFactor)); - new Handler().postDelayed(() -> { - isSystemZooming = true; - map.zoomToBoundingBox(box, animate); - isSystemZooming = false; - }, 100); - } - } - }); - - return view; - } - - @Override - public @NonNull - MapPoint getCenter() { - return fromGeoPoint(map.getMapCenter()); - } - - @Override - public double getZoom() { - return map.getZoomLevel(); - } - - @Override - public List addMarkers(List markers) { - List featureIds = new ArrayList<>(); - for (MarkerDescription markerDescription : markers) { - int featureId = nextFeatureId++; - features.put(featureId, new MarkerFeature(map, markerDescription)); - featureIds.add(featureId); - } - - map.invalidate(); - return featureIds; - } - - @Override - public void setMarkerIcon(int featureId, MarkerIconDescription markerIconDescription) { - MapFeature feature = features.get(featureId); - if (feature instanceof MarkerFeature) { - ((MarkerFeature) feature).setIcon(markerIconDescription); - map.invalidate(); - } - } - - @Override - public @Nullable - MapPoint getMarkerPoint(int featureId) { - MapFeature feature = features.get(featureId); - return feature instanceof MarkerFeature ? ((MarkerFeature) feature).getPoint() : null; - } - - @Override - public int addPolyLine(LineDescription lineDescription) { - int featureId = nextFeatureId++; - addPolyLine(featureId, lineDescription); - return featureId; - } - - private void addPolyLine(int featureId, LineDescription lineDescription) { - if (lineDescription.getDraggable()) { - features.put(featureId, new DynamicPolyLineFeature(map, lineDescription)); - } else { - features.put(featureId, new StaticPolyLineFeature(map, lineDescription)); - } - } - - @Override - public void updatePolyLine(int featureId, @NotNull LineDescription lineDescription) { - features.get(featureId).dispose(); - addPolyLine(featureId, lineDescription); - } - - @Override - public int addPolygon(PolygonDescription polygonDescription) { - int featureId = nextFeatureId++; - addPolygon(featureId, polygonDescription); - return featureId; - } - - private void addPolygon(int featureId, PolygonDescription polygonDescription) { - if (polygonDescription.getDraggable()) { - features.put(featureId, new DynamicPolygonFeature(map, polygonDescription)); - } else { - features.put(featureId, new StaticPolygonFeature(map, polygonDescription)); - } - } - - @Override - public void updatePolygon(int featureId, @NotNull PolygonDescription polygonDescription) { - features.get(featureId).dispose(); - addPolygon(featureId, polygonDescription); - } - - @Override - public @NonNull - List getPolyPoints(int featureId) { - MapFeature feature = features.get(featureId); - if (feature instanceof LineFeature) { - return ((LineFeature) feature).getPoints(); - } - return new ArrayList<>(); - } - - @Override - public void clearFeatures() { - for (MapFeature feature : features.values()) { - feature.dispose(); - } - features.clear(); - - if (map != null) { - map.invalidate(); - } - - nextFeatureId = 1; - } - @Override - public void clearFeatures(@NotNull List<@NotNull Integer> ids) { - for (Integer id : ids) { - features.remove(id).dispose(); - } - - if (map != null) { - map.invalidate(); - } - } - - @Override - public void setClickListener(@Nullable PointListener listener) { - clickListener = listener; - } - - @Override - public void setLongPressListener(@Nullable PointListener listener) { - longPressListener = listener; - } - - @Override - public void setFeatureClickListener(@Nullable FeatureListener listener) { - featureClickListener = listener; - } - - @Override - public void setDragEndListener(@Nullable FeatureListener listener) { - dragEndListener = listener; - } - - - - private static @NonNull - MapPoint fromGeoPoint(@NonNull IGeoPoint geoPoint) { - return new MapPoint(geoPoint.getLatitude(), geoPoint.getLongitude()); - } - - private static @NonNull - MapPoint fromGeoPoint(@NonNull GeoPoint geoPoint) { - return new MapPoint(geoPoint.getLatitude(), geoPoint.getLongitude(), geoPoint.getAltitude()); - } - - private static @NonNull - MapPoint fromMarker(@NonNull Marker marker) { - GeoPoint geoPoint = marker.getPosition(); - double sd = 0; - try { - sd = Double.parseDouble(marker.getSubDescription()); - } catch (NumberFormatException e) { - Timber.w("Marker.getSubDescription() did not contain a number"); - } - return new MapPoint( - geoPoint.getLatitude(), geoPoint.getLongitude(), geoPoint.getAltitude(), sd - ); - } - - private static @NonNull - GeoPoint toGeoPoint(@NonNull MapPoint point) { - return new GeoPoint(point.latitude, point.longitude, point.altitude); - } - - /** - * Updates the map to reflect the value of referenceLayerFile. - */ - private void loadReferenceOverlay() { - if (referenceOverlay != null) { - map.getOverlays().remove(referenceOverlay); - referenceOverlay.onDetach(map); - referenceOverlay = null; - } - if (referenceLayerFile != null) { - OsmMBTileProvider mbprovider = new OsmMBTileProvider(new RegisterReceiver(requireActivity()), referenceLayerFile); - referenceOverlay = new TilesOverlay(mbprovider, getContext()); - referenceOverlay.setLoadingBackgroundColor(Color.TRANSPARENT); - map.getOverlays().add(0, referenceOverlay); - } - map.invalidate(); - } - - /** - * Adds a listener that keeps track of the map center, and another - * listener that restores the map center when the MapView's layout changes. - * We have to do this because the MapView is buggy and fails to preserve its - * view on a layout change, causing the map viewport to jump around when the - * screen is resized or rotated in a way that doesn't restart the activity. - */ - private void addMapLayoutChangeListener(MapView map) { - lastMapCenter = map.getMapCenter(); - map.setMapListener(new MapListener() { - @Override - public boolean onScroll(ScrollEvent event) { - lastMapCenter = map.getMapCenter(); - return false; - } - - @Override - public boolean onZoom(ZoomEvent event) { - lastMapCenter = map.getMapCenter(); - return false; - } - }); - map.addOnLayoutChangeListener( - (view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - map.getController().setCenter(lastMapCenter)); - } - - private Marker createMarker(MapView map, MarkerDescription markerDescription) { - // A Marker's position is a GeoPoint with latitude, longitude, and - // altitude fields. We need to store the standard deviation value - // somewhere, so it goes in the marker's sub-description field. - Marker marker = new Marker(map); - marker.setPosition(toGeoPoint(markerDescription.getPoint())); - marker.setSubDescription(Double.toString(markerDescription.getPoint().accuracy)); - marker.setDraggable(markerDescription.isDraggable()); - Bitmap iconBitmap = toBitmap(markerDescription.getIconDescription(), requireContext()); - marker.setIcon(toDrawable(iconBitmap, requireContext().getResources())); - marker.setAnchor(getIconAnchorValueX(markerDescription.getIconAnchor()), getIconAnchorValueY(markerDescription.getIconAnchor())); - marker.setOnMarkerClickListener((clickedMarker, mapView) -> { - int featureId = findFeature(clickedMarker); - if (featureClickListener != null && featureId != -1) { - featureClickListener.onFeature(featureId); - return true; // consume the event - } - return false; - }); - marker.setOnMarkerDragListener(new Marker.OnMarkerDragListener() { - @Override - public void onMarkerDragStart(Marker marker) { - } - - @Override - public void onMarkerDrag(Marker marker) { - // When a marker is manually dragged, the position is no longer - // obtained from a GPS reading, so the standard deviation field - // is no longer meaningful; reset it to zero. - marker.setSubDescription("0"); - updateFeature(findFeature(marker)); - } - - @Override - public void onMarkerDragEnd(Marker marker) { - int featureId = findFeature(marker); - updateFeature(featureId); - if (dragEndListener != null && featureId != -1) { - dragEndListener.onFeature(featureId); - } - } - }); - - map.getOverlays().add(marker); - return marker; - } - - private float getIconAnchorValueX(MapFragment.IconAnchor iconAnchor) { - switch (iconAnchor) { - case BOTTOM: - default: - return Marker.ANCHOR_CENTER; - } - } - - private float getIconAnchorValueY(MapFragment.IconAnchor iconAnchor) { - switch (iconAnchor) { - case BOTTOM: - return Marker.ANCHOR_BOTTOM; - default: - return Marker.ANCHOR_CENTER; - } - } - - /** - * Finds the feature to which the given marker belongs. - */ - private int findFeature(Marker marker) { - for (int featureId : features.keySet()) { - if (features.get(featureId).ownsMarker(marker)) { - return featureId; - } - } - return -1; // not found - } - - /** - * Finds the feature to which the given polyline belongs. - */ - private int findFeature(Polyline polyline) { - for (int featureId : features.keySet()) { - if (features.get(featureId).ownsPolyline(polyline)) { - return featureId; - } - } - return -1; // not found - } - - private int findFeature(Polygon polygon) { - for (int featureId : features.keySet()) { - if (features.get(featureId).ownsPolygon(polygon)) { - return featureId; - } - } - return -1; - } - - private void updateFeature(int featureId) { - MapFeature feature = features.get(featureId); - if (feature != null) { - feature.update(); - } - } - - private void addAttributionAndMapEventsOverlays() { - map.getOverlays().add(new AttributionOverlay(getContext())); - map.getOverlays().add( - new MapEventsOverlay( - new MapEventsReceiver( - point -> { - if (clickListener != null) { - clickListener.onPoint(point); - } - }, - point -> { - if (longPressListener != null) { - longPressListener.onPoint(point); - } - } - ) - ) - ); - } - - private void onConfigChanged(Bundle config) { - webMapService = (WebMapService) config.getSerializable(KEY_WEB_MAP_SERVICE); - referenceLayerFile = MapFragmentReferenceLayerUtils.getReferenceLayerFile(config, referenceLayerRepository); - if (map != null) { - map.setTileSource(webMapService.asOnlineTileSource()); - loadReferenceOverlay(); - } - } - - @NonNull - @Override - public MapViewModel getMapViewModel() { - return mapViewModel; - } - - @Override - public void updateMarker(int featureId, @NotNull MarkerDescription markerDescription) { - features.get(featureId).dispose(); - features.put(featureId, new MarkerFeature(map, markerDescription)); - map.invalidate(); - } - - @Override - public int addCircle(@NotNull CircleDescription circleDescription) { - return -1; - } - - @Override - public void updateCircle(int featureId, @NotNull CircleDescription circleDescription) { - - } - - /** - * A MapFeature is a physical feature on a map, such as a point, a road, - * a building, a region, etc. It is presented to the user as one editable - * object, though its appearance may be constructed from multiple overlays - * (e.g. geometric elements, handles for manipulation, etc.). - */ - interface MapFeature { - /** - * Returns true if the given marker belongs to this feature. - */ - boolean ownsMarker(Marker marker); - - /** - * Returns true if the given polyline belongs to this feature. - */ - boolean ownsPolyline(Polyline polyline); - - boolean ownsPolygon(Polygon polygon); - - /** - * Updates the feature's geometry after any UI handles have moved. - */ - void update(); - - /** - * Removes the feature from the map, leaving it no longer usable. - */ - void dispose(); - } - - /** - * A marker that can optionally be dragged by the user. - */ - private class MarkerFeature implements MapFeature { - final MapView map; - Marker marker; - - MarkerFeature(MapView map, MarkerDescription markerDescription) { - this.map = map; - this.marker = createMarker(map, markerDescription); - } - - public void setIcon(MarkerIconDescription markerIconDescription) { - Context context = requireContext(); - Bitmap bitmap = toBitmap(markerIconDescription, context); - Drawable drawable = toDrawable(bitmap, context.getResources()); - - marker.setIcon(drawable); - } - - public MapPoint getPoint() { - return fromMarker(marker); - } - - public boolean ownsMarker(Marker givenMarker) { - return marker.equals(givenMarker); - } - - public boolean ownsPolyline(Polyline polyline) { - return false; - } - - @Override - public boolean ownsPolygon(Polygon polygon) { - return false; - } - - public void update() { - } - - public void dispose() { - map.getOverlays().remove(marker); - marker = null; - } - } - - private interface LineFeature extends MapFeature { - - List getPoints(); - } - - private class StaticPolyLineFeature implements LineFeature { - final MapView map; - final Polyline polyline; - private final List points; - - StaticPolyLineFeature(MapView map, LineDescription lineDescription) { - this.map = map; - polyline = new Polyline(); - polyline.setColor(lineDescription.getStrokeColor()); - polyline.setOnClickListener((clickedPolyline, mapView, eventPos) -> { - int featureId = findFeature(clickedPolyline); - if (featureClickListener != null && featureId != -1) { - featureClickListener.onFeature(featureId); - return true; // consume the event - } - return false; - }); - Paint paint = polyline.getPaint(); - paint.setStrokeWidth(lineDescription.getStrokeWidth()); - map.getOverlays().add(polyline); - - points = lineDescription.getPoints(); - List geoPoints = StreamSupport.stream(points.spliterator(), false).map(mapPoint -> new GeoPoint(mapPoint.latitude, mapPoint.longitude, mapPoint.altitude)).collect(Collectors.toList()); - polyline.setPoints(geoPoints); - map.invalidate(); - } - - @Override - public boolean ownsMarker(Marker givenMarker) { - return false; - } - - @Override - public boolean ownsPolyline(Polyline givenPolyline) { - return polyline.equals(givenPolyline); - } - - @Override - public boolean ownsPolygon(Polygon polygon) { - return false; - } - - @Override - public void update() { - } - - @Override - public void dispose() { - map.getOverlays().remove(polyline); - } - - @Override - public List getPoints() { - return points; - } - } - - private class DynamicPolyLineFeature implements LineFeature { - final MapView map; - final List markers = new ArrayList<>(); - final Polyline polyline; - - DynamicPolyLineFeature(MapView map, LineDescription lineDescription) { - this.map = map; - polyline = new Polyline(); - polyline.setColor(lineDescription.getStrokeColor()); - polyline.setOnClickListener((clickedPolyline, mapView, eventPos) -> { - int featureId = findFeature(clickedPolyline); - if (featureClickListener != null && featureId != -1) { - featureClickListener.onFeature(featureId); - return true; // consume the event - } - return false; - }); - Paint paint = polyline.getPaint(); - paint.setStrokeWidth(lineDescription.getStrokeWidth()); - map.getOverlays().add(polyline); - - List markerDescriptions = getMarkersForPoints(lineDescription); - for (MarkerDescription markerDescription : markerDescriptions) { - markers.add(createMarker(map, markerDescription)); - } - update(); - } - - @Override - public boolean ownsMarker(Marker givenMarker) { - return markers.contains(givenMarker); - } - - @Override - public boolean ownsPolyline(Polyline givenPolyline) { - return polyline.equals(givenPolyline); - } - - @Override - public boolean ownsPolygon(Polygon polygon) { - return false; - } - - @Override - public void update() { - List geoPoints = new ArrayList<>(); - for (Marker marker : markers) { - geoPoints.add(marker.getPosition()); - } - - polyline.setPoints(geoPoints); - map.invalidate(); - } - - @Override - public void dispose() { - for (Marker marker : markers) { - map.getOverlays().remove(marker); - } - markers.clear(); - map.getOverlays().remove(polyline); - } - - @Override - public List getPoints() { - List points = new ArrayList<>(); - for (Marker marker : markers) { - points.add(fromMarker(marker)); - } - return points; - } - } - - private class DynamicPolygonFeature implements LineFeature { - - final MapView map; - final List markers = new ArrayList<>(); - final Polygon polygon; - - DynamicPolygonFeature(MapView map, PolygonDescription polygonDescription) { - this.map = map; - polygon = new Polygon(); - polygon.setStrokeColor(polygonDescription.getStrokeColor()); - polygon.setStrokeWidth(polygonDescription.getStrokeWidth()); - polygon.getFillPaint().setColor(polygonDescription.getFillColor()); - polygon.setOnClickListener((clickedPolygon, mapView, eventPos) -> { - int featureId = findFeature(clickedPolygon); - if (featureClickListener != null && featureId != -1) { - featureClickListener.onFeature(featureId); - return true; // consume the event - } - return false; - }); - - map.getOverlays().add(polygon); - - List markerDescriptions = getMarkersForPoints(polygonDescription); - for (MarkerDescription markerDescription : markerDescriptions) { - markers.add(createMarker(map, markerDescription)); - } - update(); - } - - @Override - public boolean ownsMarker(Marker givenMarker) { - return markers.contains(givenMarker); - } - - @Override - public boolean ownsPolyline(Polyline other) { - return false; - } - - @Override - public boolean ownsPolygon(Polygon other) { - return polygon.equals(other); - } - - @Override - public void update() { - List geoPoints = new ArrayList<>(); - for (Marker marker : markers) { - geoPoints.add(marker.getPosition()); - } - - polygon.setPoints(geoPoints); - map.invalidate(); - } - - @Override - public void dispose() { - for (Marker marker : markers) { - map.getOverlays().remove(marker); - } - markers.clear(); - map.getOverlays().remove(polygon); - } - - @Override - public List getPoints() { - List points = new ArrayList<>(); - for (Marker marker : markers) { - points.add(fromMarker(marker)); - } - return points; - } - } - - private class StaticPolygonFeature implements LineFeature { - private final MapView map; - @NonNull - private final PolygonDescription polygonDescription; - private final Polygon polygon = new Polygon(); - - StaticPolygonFeature(MapView map, PolygonDescription polygonDescription) { - this.map = map; - this.polygonDescription = polygonDescription; - - map.getOverlays().add(polygon); - polygon.getOutlinePaint().setColor(polygonDescription.getStrokeColor()); - polygon.setStrokeWidth(polygonDescription.getStrokeWidth()); - polygon.getFillPaint().setColor(polygonDescription.getFillColor()); - polygon.setPoints(StreamSupport.stream(polygonDescription.getPoints().spliterator(), false).map(point -> new GeoPoint(point.latitude, point.longitude)).collect(Collectors.toList())); - polygon.setOnClickListener((polygon, mapView, eventPos) -> { - int featureId = findFeature(polygon); - if (featureClickListener != null && featureId != -1) { - featureClickListener.onFeature(featureId); - return true; // consume the event - } - - return false; - }); - } - - @Override - public boolean ownsMarker(Marker marker) { - return false; - } - - @Override - public boolean ownsPolyline(Polyline polyline) { - return false; - } - - @Override - public boolean ownsPolygon(Polygon polygon) { - return polygon.equals(this.polygon); - } - - @Override - public void update() { - } - - @Override - public void dispose() { - map.getOverlays().remove(polygon); - } - - @Override - public List getPoints() { - return polygonDescription.getPoints(); - } - } - - /** - * An overlay that draws an attribution message in the lower-right corner. - */ - private static class AttributionOverlay extends Overlay { - public static final int FONT_SIZE_DP = 12; - public static final int MARGIN_DP = 10; - - private final Paint paint; - - AttributionOverlay(Context context) { - super(); - - paint = new Paint(); - paint.setAntiAlias(true); - paint.setColor(ContextExt.getThemeAttributeValue(context, com.google.android.material.R.attr.colorOnSurface)); - paint.setTextSize(FONT_SIZE_DP * - context.getResources().getDisplayMetrics().density); - paint.setTextAlign(Paint.Align.RIGHT); - } - - @Override - public void draw(Canvas canvas, MapView map, boolean shadow) { - String attribution = map.getTileProvider().getTileSource().getCopyrightNotice(); - if (!shadow && !map.isAnimating() && attribution != null && !attribution.isEmpty()) { - String[] lines = attribution.split("\n"); - float lineHeight = paint.getFontSpacing(); - float x = canvas.getWidth() - MARGIN_DP; - float y = canvas.getHeight() - MARGIN_DP - lineHeight * lines.length; - - canvas.save(); - canvas.concat(map.getProjection().getInvertedScaleRotateCanvasMatrix()); - for (String line : lines) { - y += lineHeight; - canvas.drawText(line, x, y, paint); - } - canvas.restore(); - } - } - } - - private static class RegisterReceiver implements IRegisterReceiver { - - private final Context context; - - RegisterReceiver(Context context) { - this.context = context; - } - - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { - return context != null ? context.registerReceiver(receiver, filter) : null; - } - - @Override - public void unregisterReceiver(BroadcastReceiver receiver) { - if (context != null) { - context.unregisterReceiver(receiver); - } - } - - @Override - public void destroy() { - } - } - - private static class MapEventsReceiver implements org.osmdroid.events.MapEventsReceiver { - - private final PointListener clickListener; - private final PointListener longPressListener; - - MapEventsReceiver(PointListener clickListener, PointListener longPressListener) { - this.clickListener = clickListener; - this.longPressListener = longPressListener; - } - - @Override - public boolean singleTapConfirmedHelper(GeoPoint geoPoint) { - if (clickListener != null) { - clickListener.onPoint(fromGeoPoint(geoPoint)); - return true; - } - return false; - } - - @Override - public boolean longPressHelper(GeoPoint geoPoint) { - if (longPressListener != null) { - longPressListener.onPoint(fromGeoPoint(geoPoint)); - return true; - } - return false; - } - } -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileModuleProvider.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileModuleProvider.java deleted file mode 100644 index 772c0b1019f..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileModuleProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2014 GeoODK - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -/** - * @author Jon Nordling (jonnordling@gmail.com) - */ - -package org.odk.collect.osmdroid; - -import android.graphics.drawable.Drawable; - -import org.osmdroid.config.Configuration; -import org.osmdroid.tileprovider.IRegisterReceiver; -import org.osmdroid.tileprovider.modules.MapTileFileStorageProviderBase; -import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase; -import org.osmdroid.tileprovider.tilesource.ITileSource; -import org.osmdroid.tileprovider.util.StreamUtils; - -import java.io.InputStream; - -import timber.log.Timber; - -class OsmMBTileModuleProvider extends MapTileFileStorageProviderBase { - - protected OsmMBTileSource tileSource; - - OsmMBTileModuleProvider(IRegisterReceiver receiverRegistrar, OsmMBTileSource tileSource) { - - // Call the super constructor - super(receiverRegistrar, - Configuration.getInstance().getTileFileSystemThreads(), - Configuration.getInstance().getTileFileSystemMaxQueueSize()); - - // Initialize fields - this.tileSource = tileSource; - - } - - @Override - protected String getName() { - return "MBTiles File Archive Provider"; - } - - @Override - protected String getThreadGroupName() { - return "mbtilesarchive"; - } - - @Override - public MapTileModuleProviderBase.TileLoader getTileLoader() { - return new TileLoader(); - } - - @Override - public boolean getUsesDataConnection() { - return false; - } - - @Override - public int getMinimumZoomLevel() { - return tileSource.getMinimumZoomLevel(); - } - - @Override - public int getMaximumZoomLevel() { - return tileSource.getMaximumZoomLevel(); - } - - @Override - public void setTileSource(ITileSource tileSource) { - Timber.w("*** Warning: someone's trying to reassign MBTileModuleProvider's tileSource!"); - if (tileSource instanceof OsmMBTileSource) { - this.tileSource = (OsmMBTileSource) tileSource; - } else { - // logger.warn("*** Warning: and it wasn't even an MBTileSource! That's just rude!"); - - } - } - - private class TileLoader extends MapTileModuleProviderBase.TileLoader { - - @Override - public Drawable loadTile(long mapTileIndex) { - InputStream inputStream = null; - - try { - inputStream = tileSource.getInputStream(mapTileIndex); - - if (inputStream != null) { - - // Note that the finally clause will be called before - // the value is returned! - return tileSource.getDrawable(inputStream); - } - - } catch (Throwable e) { - Timber.e(e, "Error loading tile"); - - } finally { - if (inputStream != null) { - StreamUtils.closeStream(inputStream); - } - } - - return null; - } - } - -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileProvider.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileProvider.java deleted file mode 100644 index adc46de9b5a..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2014 GeoODK - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -/** - * @author Jon Nordling (jonnordling@gmail.com) - */ - -package org.odk.collect.osmdroid; - -import org.osmdroid.tileprovider.IRegisterReceiver; -import org.osmdroid.tileprovider.MapTileProviderArray; -import org.osmdroid.tileprovider.modules.MapTileModuleProviderBase; - -import java.io.File; -import java.util.Collections; - -/** - * This class is a simplification of the the MapTileProviderArray: it only - * allows a single provider. - */ -class OsmMBTileProvider extends MapTileProviderArray { - - OsmMBTileProvider(IRegisterReceiver receiverRegistrar, File file) { - - /** - * Call the super-constructor. - * - * MapTileProviderBase requires a TileSource. As far as I can tell it is - * only used in its method rescaleCache(...) to get the pixel size of a - * tile. It seems to me that this is inappropriate, as a MapTileProvider - * can have multiple sources (like the module array defined below) and - * therefore multiple tileSources which might return different values!! - * - * If the requirement is that the tile size is equal across tile - * sources, then the parameter should be obtained from a different - * location, From TileSystem for example. - */ - super(OsmMBTileSource.createFromFile(file), receiverRegistrar); - - // Create the module provider; this class provides a TileLoader that - // actually loads the tile from the DB. - OsmMBTileModuleProvider moduleProvider; - moduleProvider = new OsmMBTileModuleProvider(receiverRegistrar, (OsmMBTileSource) getTileSource()); - - MapTileModuleProviderBase[] tileProviderArray; - tileProviderArray = new MapTileModuleProviderBase[]{moduleProvider}; - - // Add the module provider to the array of providers; mTileProviderList - // is defined by the superclass. - Collections.addAll(mTileProviderList, tileProviderArray); - } - - // TODO: implement public Drawable getMapTile(final MapTile pTile) {} - // The current implementation is needlessly complex because it uses - // MapTileProviderArray as a basis. - -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileSource.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileSource.java deleted file mode 100644 index ee4d9cf5b91..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmMBTileSource.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (C) 2014 GeoODK - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License - * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express - * or implied. See the License for the specific language governing permissions and limitations under - * the License. - */ - -/** - * @author Jon Nordling (jonnordling@gmail.com) - */ - -package org.odk.collect.osmdroid; - -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; - -import org.osmdroid.tileprovider.tilesource.BitmapTileSourceBase; -import org.osmdroid.util.MapTileIndex; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; - -import timber.log.Timber; - -class OsmMBTileSource extends BitmapTileSourceBase { - - // Log log log log ... - // private static final Logger logger = LoggerFactory.getLogger(MBTileSource.class); - // Database related fields - public static final String TABLE_TILES = "tiles"; - public static final String COL_TILES_ZOOM_LEVEL = "zoom_level"; - public static final String COL_TILES_TILE_COLUMN = "tile_column"; - public static final String COL_TILES_TILE_ROW = "tile_row"; - public static final String COL_TILES_TILE_DATA = "tile_data"; - - protected SQLiteDatabase database; - - // Reasonable defaults .. - public static final int MIN_ZOOM = 8; - public static final int MAX_ZOOM = 15; - public static final int TILE_SIZE_PIXELS = 256; - - /** - * The reason this constructor is protected is because all parameters, - * except file should be determined from the archive file. Therefore a - * factory method is necessary. - */ - protected OsmMBTileSource(int minZoom, - int maxZoom, - int tileSizePixels, - SQLiteDatabase db) { - super("MBTiles", minZoom, maxZoom, tileSizePixels, ".png"); - - database = db; - } - - /** - * Creates a new MBTileSource from file. - *

- * Parameters minZoom, maxZoom en tileSizePixels are obtained from the - * database. If they cannot be obtained from the DB, the default values as - * defined by this class are used. - */ - public static OsmMBTileSource createFromFile(File file) { - int flags = SQLiteDatabase.NO_LOCALIZED_COLLATORS | SQLiteDatabase.OPEN_READONLY; - int tileSize = TILE_SIZE_PIXELS; - - // Open the database - SQLiteDatabase db = SQLiteDatabase.openDatabase(file.getAbsolutePath(), null, flags); - - // Get the tile size - Cursor cursor = db.rawQuery("SELECT tile_data FROM tiles LIMIT 0,1", - new String[]{}); - - if (cursor.getCount() != 0) { - cursor.moveToFirst(); - InputStream is = new ByteArrayInputStream(cursor.getBlob(0)); - - Bitmap bitmap = BitmapFactory.decodeStream(is); - if (bitmap != null) { - tileSize = bitmap.getHeight(); - } - Timber.w("Found a tile size of %d", tileSize); - } - cursor.close(); - - // Get the minimum zoomlevel from the MBTiles file - int value = getInt(db, "SELECT MIN(zoom_level) FROM tiles;"); - int minZoomLevel = value > -1 ? value : MIN_ZOOM; - - // Get the maximum zoomlevel from the MBTiles file - value = getInt(db, "SELECT MAX(zoom_level) FROM tiles;"); - int maxZoomLevel = value > -1 ? value : MAX_ZOOM; - - return new OsmMBTileSource(minZoomLevel, maxZoomLevel, tileSize, db); - } - - protected static int getInt(SQLiteDatabase db, String sql) { - Cursor cursor = db.rawQuery(sql, new String[]{}); - int value = -1; - - if (cursor.getCount() != 0) { - cursor.moveToFirst(); - value = cursor.getInt(0); - Timber.e(new Error("Found a minimum zoomlevel of " + value)); - } - - cursor.close(); - return value; - } - - public InputStream getInputStream(long tileIndex) { - - try { - InputStream ret = null; - final String[] tile = {COL_TILES_TILE_DATA}; - final String[] xyz = {Integer.toString(MapTileIndex.getX(tileIndex)), - Double.toString(Math.pow(2, MapTileIndex.getZoom(tileIndex)) - MapTileIndex.getY(tileIndex) - 1), - Integer.toString(MapTileIndex.getZoom(tileIndex))}; - - final Cursor cur = database.query(TABLE_TILES, - tile, - "tile_column=? and tile_row=? and zoom_level=?", - xyz, - null, - null, - null); - - if (cur.getCount() != 0) { - cur.moveToFirst(); - ret = new ByteArrayInputStream(cur.getBlob(0)); - } - - cur.close(); - - if (ret != null) { - return ret; - } - - } catch (final Throwable e) { - Timber.w(e, "Error getting db stream: %s", tileIndex); - } - return null; - } -} diff --git a/osmdroid/src/main/java/org/odk/collect/osmdroid/WebMapService.java b/osmdroid/src/main/java/org/odk/collect/osmdroid/WebMapService.java deleted file mode 100644 index e40eb8d252e..00000000000 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/WebMapService.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.odk.collect.osmdroid; - -import org.osmdroid.tileprovider.tilesource.OnlineTileSourceBase; -import org.osmdroid.util.MapTileIndex; - -import java.io.Serializable; - -/** - * A serializable definition of a Web Map Service in terms of its URL structure - * and the parameters for fetching tiles from it. - */ -public class WebMapService implements Serializable { - public final String cacheName; - public final int minZoomLevel; - public final int maxZoomLevel; - public final int tileSize; - public final String copyright; - public final String[] urlTemplates; - - private OnlineTileSourceBase onlineTileSource; - - public WebMapService(String cacheName, int minZoomLevel, int maxZoomLevel, - int tileSize, String copyright, String... urlTemplates) { - this.cacheName = cacheName; - this.minZoomLevel = minZoomLevel; - this.maxZoomLevel = maxZoomLevel; - this.tileSize = tileSize; - this.copyright = copyright; - this.urlTemplates = urlTemplates; - } - - // Note: org.osmdroid.views.MapView.setTileSource takes an ITileSource, - // but really it requires an instance of OnlineTileSourceBase. - public OnlineTileSourceBase asOnlineTileSource() { - if (onlineTileSource == null) { - String extension = getExtension(urlTemplates[0]); - onlineTileSource = new OnlineTileSourceBase(cacheName, minZoomLevel, - maxZoomLevel, tileSize, extension, urlTemplates, copyright) { - public String getTileURLString(long tileIndex) { - String urlTemplate = urlTemplates[random.nextInt(urlTemplates.length)]; - return urlTemplate.replace("{x}", String.valueOf(MapTileIndex.getX(tileIndex))) - .replace("{y}", String.valueOf(MapTileIndex.getY(tileIndex))) - .replace("{z}", String.valueOf(MapTileIndex.getZoom(tileIndex))); - } - }; - } - return onlineTileSource; - } - - private String getExtension(String urlTemplate) { - String[] parts = urlTemplate.split("/"); - String lastPart = parts[parts.length - 1]; - if (lastPart.contains(".")) { - String[] subparts = lastPart.split("\\."); - return "." + subparts[subparts.length - 1]; - } - return ""; - } -} diff --git a/osmdroid/src/main/res/layout/osm_map_layout.xml b/osmdroid/src/main/res/layout/osm_map_layout.xml deleted file mode 100644 index d98c2dc967c..00000000000 --- a/osmdroid/src/main/res/layout/osm_map_layout.xml +++ /dev/null @@ -1,6 +0,0 @@ - - \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 6505a1a7f4b..cb25c3ad9df 100644 --- a/settings.gradle +++ b/settings.gradle @@ -24,7 +24,6 @@ include ':androidtest' include ':settings' include ':service-test' include ':maps' -include ':osmdroid' include ':icons' include ':crash-handler' include ':entities' From 28e1498233f83428c6018a5be4b3acffc8cdeabf Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Thu, 28 May 2026 18:29:02 +0100 Subject: [PATCH 13/21] Clean up strings --- .../org/odk/collect/mapbox/MapboxMapConfigurator.kt | 4 ++-- strings/src/main/res/values-ar/strings.xml | 7 +------ strings/src/main/res/values-cs/strings.xml | 7 +------ strings/src/main/res/values-da/strings.xml | 3 +-- strings/src/main/res/values-de/strings.xml | 7 +------ strings/src/main/res/values-es/strings.xml | 7 +------ strings/src/main/res/values-et/strings.xml | 5 +---- strings/src/main/res/values-fa-rAF/strings.xml | 3 +-- strings/src/main/res/values-fa/strings.xml | 3 +-- strings/src/main/res/values-fi/strings.xml | 7 +------ strings/src/main/res/values-fr/strings.xml | 7 +------ strings/src/main/res/values-hi/strings.xml | 5 +---- strings/src/main/res/values-in/strings.xml | 7 +------ strings/src/main/res/values-it/strings.xml | 7 +------ strings/src/main/res/values-ja/strings.xml | 7 +------ strings/src/main/res/values-km/strings.xml | 7 +------ strings/src/main/res/values-mr/strings.xml | 5 +---- strings/src/main/res/values-ne-rNP/strings.xml | 7 +------ strings/src/main/res/values-pl/strings.xml | 7 +------ strings/src/main/res/values-pt/strings.xml | 7 +------ strings/src/main/res/values-ro/strings.xml | 3 +-- strings/src/main/res/values-ru/strings.xml | 6 +----- strings/src/main/res/values-rw/strings.xml | 7 +------ strings/src/main/res/values-sl/strings.xml | 7 +------ strings/src/main/res/values-sr/strings.xml | 7 +------ strings/src/main/res/values-sv-rSE/strings.xml | 7 +------ strings/src/main/res/values-sw/strings.xml | 7 +------ strings/src/main/res/values-te/strings.xml | 7 +------ strings/src/main/res/values-th-rTH/strings.xml | 5 +---- strings/src/main/res/values-uk/strings.xml | 7 +------ strings/src/main/res/values-ur/strings.xml | 7 +------ strings/src/main/res/values-zh-rTW/strings.xml | 7 +------ strings/src/main/res/values-zh/strings.xml | 7 +------ strings/src/main/res/values/strings.xml | 11 ----------- 34 files changed, 34 insertions(+), 180 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt index 1a044b772c8..45dcca5afd8 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt @@ -100,8 +100,8 @@ class MapboxMapConfigurator(configuration: String) : MapConfigurator { R.string.basemap_source_carto, ProjectKeys.KEY_CARTO_MAP_STYLE, arrayOf( - MapboxUrlOption("positron", R.string.openmap_cartodb_positron), - MapboxUrlOption("dark_matter", R.string.openmap_cartodb_darkmatter), + MapboxUrlOption("positron", R.string.carto_map_style_positron), + MapboxUrlOption("dark_matter", R.string.carto_map_style_dark_matter), ) ) ) diff --git a/strings/src/main/res/values-ar/strings.xml b/strings/src/main/res/values-ar/strings.xml index d16127c0f40..990c11d2d90 100644 --- a/strings/src/main/res/values-ar/strings.xml +++ b/strings/src/main/res/values-ar/strings.xml @@ -382,12 +382,7 @@ - خرطة البنية - خريطة وطنية هجين USGS - صور الخريطة الوطنية USGS - خريطة CartoDB - خريطة CartoDB - الخريطة الأساسية + الخريطة الأساسية المصدر %s نمط الخريطة الطرق diff --git a/strings/src/main/res/values-cs/strings.xml b/strings/src/main/res/values-cs/strings.xml index 32e64f8044a..35329644bd6 100644 --- a/strings/src/main/res/values-cs/strings.xml +++ b/strings/src/main/res/values-cs/strings.xml @@ -411,12 +411,7 @@ - Topologická mapa USGS National Map - Hybridní mapa USGS National Map - Snímková mapa USGS National Map - CartoDB Positron - CartoDB Dark Matter - Základní mapa + Základní mapa Zdroj Je nám líto, ale v tomto zařízení nejsou k dispozici základní mapy %s. Vyberte prosím jinou podkladovou mapu v Nastavení > Mapy. %s styl mapy diff --git a/strings/src/main/res/values-da/strings.xml b/strings/src/main/res/values-da/strings.xml index d0635b6af44..4d2fd55f3fe 100644 --- a/strings/src/main/res/values-da/strings.xml +++ b/strings/src/main/res/values-da/strings.xml @@ -234,8 +234,7 @@ - CartoDB Dark Matter - Basemap + Basemap Kilde Gader Terræn diff --git a/strings/src/main/res/values-de/strings.xml b/strings/src/main/res/values-de/strings.xml index f12fb4a5352..42e82c936cb 100644 --- a/strings/src/main/res/values-de/strings.xml +++ b/strings/src/main/res/values-de/strings.xml @@ -413,12 +413,7 @@ - USGS Topografische Karte - USGS National Karte Hybrid - USGS National Karte Imagery - CartoDB Positron - CartoDB Dark Matter - Basiskarte + Basiskarte Quelle %s Basiskarten sind leider nicht auf diesem Gerät verfügbar. Bitte eine andere Basiskarte unter Einstellungen > Karten wählen. %s Kartentyp diff --git a/strings/src/main/res/values-es/strings.xml b/strings/src/main/res/values-es/strings.xml index 83becdec2ce..679673f8ffc 100644 --- a/strings/src/main/res/values-es/strings.xml +++ b/strings/src/main/res/values-es/strings.xml @@ -413,12 +413,7 @@ - USGS Mapa Topográfico Nacional - USGS Mapa Nacional Hibrido - USGS Mapa Nacional Satélite - Positron CartoDB - Materia Obscura CartoDB - Mapa Base + Mapa Base Fuente Lo sentimos, los mapas base de %s no están disponibles en este dispositivo. Elija otro mapa base en Configuración > Mapas. Estilo de Mapa %s diff --git a/strings/src/main/res/values-et/strings.xml b/strings/src/main/res/values-et/strings.xml index 846a89132cd..dc7a42b2e6a 100644 --- a/strings/src/main/res/values-et/strings.xml +++ b/strings/src/main/res/values-et/strings.xml @@ -225,10 +225,7 @@ - USGS National Map Topo - CartoDB Positron - CartoDB Dark Matter - Tänavad + Tänavad Maastik Hübriid Satelliit diff --git a/strings/src/main/res/values-fa-rAF/strings.xml b/strings/src/main/res/values-fa-rAF/strings.xml index ba95dcc1daa..ae934a45ae0 100644 --- a/strings/src/main/res/values-fa-rAF/strings.xml +++ b/strings/src/main/res/values-fa-rAF/strings.xml @@ -360,8 +360,7 @@ - نقشه ملی USGS Topo - نقشه اصلی + نقشه اصلی منبع متأسفیم، %s نقشه پایه در این دستگاه موجود نیست. لطفاً نقشه پایه دیگری را در تنظیمات > نقشه ها. %s سبک نقشه diff --git a/strings/src/main/res/values-fa/strings.xml b/strings/src/main/res/values-fa/strings.xml index ba95dcc1daa..ae934a45ae0 100644 --- a/strings/src/main/res/values-fa/strings.xml +++ b/strings/src/main/res/values-fa/strings.xml @@ -360,8 +360,7 @@ - نقشه ملی USGS Topo - نقشه اصلی + نقشه اصلی منبع متأسفیم، %s نقشه پایه در این دستگاه موجود نیست. لطفاً نقشه پایه دیگری را در تنظیمات > نقشه ها. %s سبک نقشه diff --git a/strings/src/main/res/values-fi/strings.xml b/strings/src/main/res/values-fi/strings.xml index 69fcd53340b..7c40c7b73b7 100644 --- a/strings/src/main/res/values-fi/strings.xml +++ b/strings/src/main/res/values-fi/strings.xml @@ -413,12 +413,7 @@ - USGS National Map topografinen - USGS National Map hybridi - USGS National Map kuvat - CartoDB Positron - CartoDB Dark Matter - Peruskartta + Peruskartta Lähde Pahoittelut, %s pohjakartat eivät ole käytettävissä tällä laitteella. Ole hyvä ja valitse toinen pohjakartta kohdassa Asetukset > Kartat. %s kartan tyyli diff --git a/strings/src/main/res/values-fr/strings.xml b/strings/src/main/res/values-fr/strings.xml index 1e7724e958a..7f145b1ca7c 100644 --- a/strings/src/main/res/values-fr/strings.xml +++ b/strings/src/main/res/values-fr/strings.xml @@ -413,12 +413,7 @@ - USGS National Map Topo - USGS Carte nationale hybride - Les images de la carte nationale USGS - CartoDB Positron - CartoDB Dark Matter - Fonds de carte + Fonds de carte Source Désolé, %s carte de base ne sont pas disponibles sur ce terminal. Merci d\'en choisir une autre dans Paramètres > Cartes. Style de carte %s diff --git a/strings/src/main/res/values-hi/strings.xml b/strings/src/main/res/values-hi/strings.xml index 4803cc7f367..35dfc2cb1fb 100644 --- a/strings/src/main/res/values-hi/strings.xml +++ b/strings/src/main/res/values-hi/strings.xml @@ -240,10 +240,7 @@ - USGS National Map Topo - CartoDB Positron - CartoDB डार्क मैटर - बेसमैप + बेसमैप स्ट्रीट भूभाग हाइब्रिड diff --git a/strings/src/main/res/values-in/strings.xml b/strings/src/main/res/values-in/strings.xml index e2a1f012f51..8f88f68167d 100644 --- a/strings/src/main/res/values-in/strings.xml +++ b/strings/src/main/res/values-in/strings.xml @@ -413,12 +413,7 @@ - USGS National Map Topo - Peta Nasional Gabungan USGS - Citra Peta Nasional USGS - CartoDB Positron - CartoDB Dark Matter - Peta dasar + Peta dasar Sumber Maaf, peta dasar %s tidak tersedia pada perangkat ini. Silakan pilih peta dasar lainnya di Pengaturan > Peta. Gaya peta %s diff --git a/strings/src/main/res/values-it/strings.xml b/strings/src/main/res/values-it/strings.xml index 752842280e6..415d88c8727 100644 --- a/strings/src/main/res/values-it/strings.xml +++ b/strings/src/main/res/values-it/strings.xml @@ -413,12 +413,7 @@ - Mappa Topologia Nazionale USGS - Mappa Ibrida Nazionale USGS - Immagini di Mappe Nazionali USGS - CartoDB Positron - CartoDB Dark Matter - Mappa Base + Mappa Base Origine Spiacente, %s le mappe di base non sono disponibili su questo dispositivo. Scegli un\'altra mappa di base in Impostazioni > Mappe. %sstile mappa diff --git a/strings/src/main/res/values-ja/strings.xml b/strings/src/main/res/values-ja/strings.xml index a83153b0004..2f6db639f89 100644 --- a/strings/src/main/res/values-ja/strings.xml +++ b/strings/src/main/res/values-ja/strings.xml @@ -353,12 +353,7 @@ - USGS National Map Topo - USGS ナショナルマップ ハイブリッド - USGS ナショナルマップ画像 - CartoDB ポジトロン - CartoDB ダークマター - 基本地図 + 基本地図 ソース %s 地図スタイル 道路 diff --git a/strings/src/main/res/values-km/strings.xml b/strings/src/main/res/values-km/strings.xml index ff344047c97..012f5be79eb 100644 --- a/strings/src/main/res/values-km/strings.xml +++ b/strings/src/main/res/values-km/strings.xml @@ -337,12 +337,7 @@ - USGS National Map Topo - ផែនទីជាតិ Hybrid USGS - ផែនទីជាតិរូបភាព USGS - CartoDB Positron - CartoDB Dark Matter - ផែនទីមូលដ្ឋាន + ផែនទីមូលដ្ឋាន ប្រភព %s រចនាបទផែនទី ផ្លូវ diff --git a/strings/src/main/res/values-mr/strings.xml b/strings/src/main/res/values-mr/strings.xml index f137b89f3fe..7c16cec59e0 100644 --- a/strings/src/main/res/values-mr/strings.xml +++ b/strings/src/main/res/values-mr/strings.xml @@ -234,10 +234,7 @@ - यूएसजीएस राष्ट्रीय नकाशा टोपो - कार्टो डीबी पॉझिट्रॉन - कार्टो डीबी गडद बाब - बेसमॅप + बेसमॅप रस्ते भूप्रदेश संकरित diff --git a/strings/src/main/res/values-ne-rNP/strings.xml b/strings/src/main/res/values-ne-rNP/strings.xml index 2bb2c1a4ce2..47c736cc18e 100644 --- a/strings/src/main/res/values-ne-rNP/strings.xml +++ b/strings/src/main/res/values-ne-rNP/strings.xml @@ -227,12 +227,7 @@ - अमेरिकी भूगर्भीय सर्वेक्षण राष्ट्रिय नक्शा Topo - अमेरिकी भूगर्भीय सर्वेक्षण राष्ट्रिय नक्शा Hybrid - अमेरिकी भूगर्भीय सर्वेक्षण राष्ट्रिय नक्शा Imagery - CartoDB Positron - CartoDB Dark Matter - टोलहरु + टोलहरु भूभाग हाइव्रिड उपग्रह diff --git a/strings/src/main/res/values-pl/strings.xml b/strings/src/main/res/values-pl/strings.xml index 35371a744b2..553c386a0b4 100644 --- a/strings/src/main/res/values-pl/strings.xml +++ b/strings/src/main/res/values-pl/strings.xml @@ -333,12 +333,7 @@ - USGS National Map Topo - USGS National Map Hybrid - USGS National Map Imagery - CartoDB Positron - CartoDB Dark Matter - Mapa bazowa + Mapa bazowa Źródło %s styl mapy Ulice diff --git a/strings/src/main/res/values-pt/strings.xml b/strings/src/main/res/values-pt/strings.xml index 04c2db71776..9d07b20b793 100644 --- a/strings/src/main/res/values-pt/strings.xml +++ b/strings/src/main/res/values-pt/strings.xml @@ -414,12 +414,7 @@ - Mapa Topográfico Nacional USGS - Mapa Híbrido Nacional USGS - Mapa de Imagem Nacional USGS - Positron CartoDB - Matéria Escura CartoDB - Mapa base + Mapa base Fonte Desculpe, %s mapas base não estão disponíveis nesse dispositivo. Por favor, escolha outro mapa base em Configurações > Mapas. estilo do mapa %s diff --git a/strings/src/main/res/values-ro/strings.xml b/strings/src/main/res/values-ro/strings.xml index 8f89337b4af..a3164ac2655 100644 --- a/strings/src/main/res/values-ro/strings.xml +++ b/strings/src/main/res/values-ro/strings.xml @@ -208,8 +208,7 @@ - USGS National Map Topo - Străzi + Străzi Teren Hibrid Satelit diff --git a/strings/src/main/res/values-ru/strings.xml b/strings/src/main/res/values-ru/strings.xml index f2910dc5612..7a797ac43e6 100644 --- a/strings/src/main/res/values-ru/strings.xml +++ b/strings/src/main/res/values-ru/strings.xml @@ -378,11 +378,7 @@ - Национальная карта USGS: топографическая - Национальная карта USGS: спутниковая - Национальная карта USGS: съёмка с воздуха - CartoDB Тёмная материя - Подложка + Подложка Источник К сожалению, базовые карты %s недоступны на этом устройстве. Выберите другую базовую карту в разделе Настройки > Карты. %s стиль карты diff --git a/strings/src/main/res/values-rw/strings.xml b/strings/src/main/res/values-rw/strings.xml index 9d0ade9b793..bae4b14f73b 100644 --- a/strings/src/main/res/values-rw/strings.xml +++ b/strings/src/main/res/values-rw/strings.xml @@ -355,12 +355,7 @@ N\'ukomea guhura n\'iki kibazo, wakigeza kuwa gusabye gukusanya amakuru. - USGS National Map Topo - USGS National Map Hybrid - USGS National Map Imagery - CartoDB Position - CartoDB Dark Matter - Indangamimerere igaragaza amakuru. + Indangamimerere igaragaza amakuru. Aho byatangiriye 1%s imiterere ya ikarita Imihanda diff --git a/strings/src/main/res/values-sl/strings.xml b/strings/src/main/res/values-sl/strings.xml index 62bce184bd5..a7a649753d3 100644 --- a/strings/src/main/res/values-sl/strings.xml +++ b/strings/src/main/res/values-sl/strings.xml @@ -388,12 +388,7 @@ - USGS National Map Topo - USGS National Map Hybrid - USGS National Map Imagery - CartoDB Positron - CartoDB Dark Matter - Basemap + Basemap Vir Oprostite, %s temeljne karte niso na voljo na tem napravi. Prosimo, izberite drugo temeljno karto v Nastavitve > Zemljevidi. %s slog zemljevida diff --git a/strings/src/main/res/values-sr/strings.xml b/strings/src/main/res/values-sr/strings.xml index 1b721b52509..f6f96e61c86 100644 --- a/strings/src/main/res/values-sr/strings.xml +++ b/strings/src/main/res/values-sr/strings.xml @@ -312,12 +312,7 @@ - USGS National Map Topo - USCG National Map Hybrid - USGC National Map Imagery - CartoDB Positron - CartoDB Dark Matter - Osnovna mapa + Osnovna mapa Ulice Teren Hibrid diff --git a/strings/src/main/res/values-sv-rSE/strings.xml b/strings/src/main/res/values-sv-rSE/strings.xml index 60258238f8d..7b76ee1b7b1 100644 --- a/strings/src/main/res/values-sv-rSE/strings.xml +++ b/strings/src/main/res/values-sv-rSE/strings.xml @@ -312,12 +312,7 @@ - USGS National Map Topo - USGS nationell hybridkarta - USGS nationell satellitkarta - CartoDB Positron - CartoDB Dark Matter - Grundkarta + Grundkarta Källa %s kartstil Streets diff --git a/strings/src/main/res/values-sw/strings.xml b/strings/src/main/res/values-sw/strings.xml index 53bd109c398..0a3d9d14011 100644 --- a/strings/src/main/res/values-sw/strings.xml +++ b/strings/src/main/res/values-sw/strings.xml @@ -345,12 +345,7 @@ - USGS ramani za kitaifa za topo - USGS National Map Hybrid - USGS National Map Imagery - CartoDB Positron - CartoDB Dark Matter - Ramani msingi + Ramani msingi Chanzo %s aina ya ramani Mitaa diff --git a/strings/src/main/res/values-te/strings.xml b/strings/src/main/res/values-te/strings.xml index 0a04fdf5026..d1d48a7dfd3 100644 --- a/strings/src/main/res/values-te/strings.xml +++ b/strings/src/main/res/values-te/strings.xml @@ -333,12 +333,7 @@ - USGS నేషనల్ మ్యాప్ టోపో - USGS నేషనల్ మ్యాప్ హైబ్రిడ్ - USGS నేషనల్ మ్యాప్ ఇమేజరీ - కార్టోడిబి పోసిట్రాన్ - కార్టోడిబి డార్క్ మేటర్ - బేస్ మ్యాప్ + బేస్ మ్యాప్ ఆధారం %s మ్యాప్ శైలి స్ట్రీట్స్ diff --git a/strings/src/main/res/values-th-rTH/strings.xml b/strings/src/main/res/values-th-rTH/strings.xml index 0c8ef730f54..d6f54f6c42e 100644 --- a/strings/src/main/res/values-th-rTH/strings.xml +++ b/strings/src/main/res/values-th-rTH/strings.xml @@ -230,10 +230,7 @@ - USGS National Map Topo - CartoDB Positron - CartoDB Dark Matter - Streets + Streets Terrain Hybrid Satellite diff --git a/strings/src/main/res/values-uk/strings.xml b/strings/src/main/res/values-uk/strings.xml index 9a3ade9e6ee..bfb8739ad80 100644 --- a/strings/src/main/res/values-uk/strings.xml +++ b/strings/src/main/res/values-uk/strings.xml @@ -309,12 +309,7 @@ - USGS топографічна національна карта - USGS Національна мапа Гібрид - USGS Національна мапа Картинки - CartoDB Positron - CartoDB Dark Matter - Базова мапа + Базова мапа Вулиці Ландшафт Змішана diff --git a/strings/src/main/res/values-ur/strings.xml b/strings/src/main/res/values-ur/strings.xml index 3b4df3d8114..c41dc61cf79 100644 --- a/strings/src/main/res/values-ur/strings.xml +++ b/strings/src/main/res/values-ur/strings.xml @@ -386,12 +386,7 @@ - یو ایس جی ایس نیشنل میپ ٹوپو - یو ایس جی ایس نیشنل میپ ہائبرڈ - یو ایس جی ایس نیشنل میپ امیجری - کارٹو ڈی بی پوزٹران - کارٹو ڈی ڈی ڈارک میٹر - بیس میپ + بیس میپ ذریعہ ہم معذرت خواہ ہیں کہ اس فون پہ basemaps %s موجود نہیں ہیں۔ براہ مہربانی سیٹنگز > Maps میں سے کوئی اور basemap منتخب کریں۔ %s میپ کا سٹائل diff --git a/strings/src/main/res/values-zh-rTW/strings.xml b/strings/src/main/res/values-zh-rTW/strings.xml index e446b930efc..fd4ce30c8d5 100644 --- a/strings/src/main/res/values-zh-rTW/strings.xml +++ b/strings/src/main/res/values-zh-rTW/strings.xml @@ -401,12 +401,7 @@ - USGS地形圖 - USGS混合地圖 - USGS影像地圖 - CartoDB Positron - CartoDB Dark Matter - 底圖 + 底圖 來源 抱歉, %s 此設備上沒有底圖。請在「設置」中選擇另一個底圖 > 地圖. %s 地圖樣式 diff --git a/strings/src/main/res/values-zh/strings.xml b/strings/src/main/res/values-zh/strings.xml index 8954ec3f089..1adf64c27e2 100644 --- a/strings/src/main/res/values-zh/strings.xml +++ b/strings/src/main/res/values-zh/strings.xml @@ -413,12 +413,7 @@ - USGS地形图 - USGS混合地图 - USGS影像地图 - CartoDB Positron - CartoDB Dark Matter - 底图 + 底图 抱歉, %s 此设备上没有底图。请在“设置”中选择另一个底图 > 地图. %s 地图样式 diff --git a/strings/src/main/res/values/strings.xml b/strings/src/main/res/values/strings.xml index b536a8fac06..99eb448e3a3 100644 --- a/strings/src/main/res/values/strings.xml +++ b/strings/src/main/res/values/strings.xml @@ -509,17 +509,6 @@ # GEO ############################################## --> - - USGS National Map Topo - USGS National Map Hybrid - USGS National Map Imagery - CartoDB Positron - CartoDB Dark Matter - Basemap Source Sorry, %s basemaps are not available on this device. Please choose another basemap in Settings > Maps. From b40825286f5385e21f37f47a3413d52ded1cafa9 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 09:27:31 +0100 Subject: [PATCH 14/21] Fix case where app runs with no available maps --- .../android/application/initialization/MapsInitializer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt b/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt index 2502443bfb2..b71e9db3a6c 100644 --- a/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt +++ b/collect_app/src/main/java/org/odk/collect/android/application/initialization/MapsInitializer.kt @@ -29,7 +29,7 @@ class MapsInitializer @Inject constructor( val availableBaseMaps = MapConfiguratorProvider.getIds() val baseMapSetting = settingsProvider.getUnprotectedSettings().getString(ProjectKeys.KEY_BASEMAP_SOURCE) - if (!availableBaseMaps.contains(baseMapSetting)) { + if (!availableBaseMaps.contains(baseMapSetting) && availableBaseMaps.isNotEmpty()) { settingsProvider.getUnprotectedSettings().save( ProjectKeys.KEY_BASEMAP_SOURCE, availableBaseMaps[0] From 5fce5e4e346e470aff3696787ad34b2d1492f9d4 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 09:45:41 +0100 Subject: [PATCH 15/21] Move fragment configurations to shared object --- .../org/odk/collect/mapbox/Configurations.kt | 54 ++++++++++++++++++ .../odk/collect/mapbox/MapboxMapFragment.kt | 57 +------------------ 2 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt b/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt new file mode 100644 index 00000000000..d2bdbb7b459 --- /dev/null +++ b/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt @@ -0,0 +1,54 @@ +package org.odk.collect.mapbox + +import com.mapbox.maps.Style +import org.odk.collect.settings.keys.ProjectKeys +import org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE + +object Configurations { + val all = mapOf( + ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration( + setting = KEY_MAPBOX_MAP_STYLE, + uris = mapOf( + Style.MAPBOX_STREETS to BasemapUri.Mapbox(Style.MAPBOX_STREETS), + Style.LIGHT to BasemapUri.Mapbox(Style.LIGHT), + Style.DARK to BasemapUri.Mapbox(Style.DARK), + Style.SATELLITE to BasemapUri.Mapbox(Style.SATELLITE), + Style.SATELLITE_STREETS to BasemapUri.Mapbox(Style.SATELLITE_STREETS), + Style.OUTDOORS to BasemapUri.Mapbox(Style.OUTDOORS) + ) + ), + ProjectKeys.BASEMAP_SOURCE_OSM to Configuration( + attribution = "© OpenStreetMap contributors", + uri = BasemapUri.Raster("https://tile.openstreetmap.org/{z}/{x}/{y}.png") + ), + ProjectKeys.BASEMAP_SOURCE_USGS to Configuration( + attribution = "Map services and data available from U.S. Geological Survey, National Geospatial Program.", + setting = ProjectKeys.KEY_USGS_MAP_STYLE, + uris = mapOf( + "topographic" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}"), + "hybrid" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}"), + "satellite" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}") + ) + ), + ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration( + attribution = "© OpenStreetMap contributors, © CARTO", + setting = ProjectKeys.KEY_CARTO_MAP_STYLE, + uris = mapOf( + "positron" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"), + "dark_matter" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png") + ) + ), + ) +} + +class Configuration( + val attribution: String? = null, + val uri: BasemapUri? = null, + val setting: String? = null, + val uris: Map = emptyMap() +) + +sealed class BasemapUri(val value: String) { + class Raster(uri: String) : BasemapUri(uri) + class Mapbox(uri: String) : BasemapUri(uri) +} \ No newline at end of file diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 56e1590ff79..dc19559175b 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -16,7 +16,6 @@ import com.mapbox.geojson.Point import com.mapbox.maps.EdgeInsets import com.mapbox.maps.MapView import com.mapbox.maps.MapboxMap -import com.mapbox.maps.Style import com.mapbox.maps.dsl.cameraOptions import com.mapbox.maps.extension.style.layers.Layer import com.mapbox.maps.extension.style.layers.addLayer @@ -77,7 +76,6 @@ import org.odk.collect.maps.markers.MarkerIconDescription import org.odk.collect.maps.traces.LineDescription import org.odk.collect.maps.traces.PolygonDescription import org.odk.collect.settings.SettingsProvider -import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE import org.odk.collect.shared.injection.ObjectProviderHost import org.odk.collect.shared.settings.Settings @@ -85,11 +83,13 @@ import timber.log.Timber import java.io.File import java.io.IOException -class MapboxMapFragment(configuration: String) : +class MapboxMapFragment(private val configuration: Configuration) : MapViewModelMapFragment(), OnMapClickListener, OnMapLongClickListener { + constructor(configuration: String) : this(Configurations.all.getValue(configuration)) + private lateinit var settings: Settings private lateinit var mapView: MapView private lateinit var mapboxMap: MapboxMap @@ -132,8 +132,6 @@ class MapboxMapFragment(configuration: String) : .provide(ReferenceLayerRepository::class.java) } - private val configuration = configurations.getValue(configuration) - override fun init(readyListener: ReadyListener?, errorListener: ErrorListener?) { mapReadyListener = readyListener @@ -646,53 +644,4 @@ class MapboxMapFragment(configuration: String) : mapboxMap.getStyle()?.addSource(source) } } - - companion object { - private class Configuration( - val attribution: String? = null, - val uri: BasemapUri? = null, - val setting: String? = null, - val uris: Map = emptyMap() - ) - - private sealed class BasemapUri(val value: String) { - class Raster(uri: String) : BasemapUri(uri) - class Mapbox(uri: String) : BasemapUri(uri) - } - - private val configurations = mapOf( - ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration( - setting = KEY_MAPBOX_MAP_STYLE, - uris = mapOf( - Style.MAPBOX_STREETS to BasemapUri.Mapbox(Style.MAPBOX_STREETS), - Style.LIGHT to BasemapUri.Mapbox(Style.LIGHT), - Style.DARK to BasemapUri.Mapbox(Style.DARK), - Style.SATELLITE to BasemapUri.Mapbox(Style.SATELLITE), - Style.SATELLITE_STREETS to BasemapUri.Mapbox(Style.SATELLITE_STREETS), - Style.OUTDOORS to BasemapUri.Mapbox(Style.OUTDOORS) - ) - ), - ProjectKeys.BASEMAP_SOURCE_OSM to Configuration( - attribution = "© OpenStreetMap contributors", - uri = BasemapUri.Raster("https://tile.openstreetmap.org/{z}/{x}/{y}.png") - ), - ProjectKeys.BASEMAP_SOURCE_USGS to Configuration( - attribution = "Map services and data available from U.S. Geological Survey, National Geospatial Program.", - setting = ProjectKeys.KEY_USGS_MAP_STYLE, - uris = mapOf( - "topographic" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}"), - "hybrid" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}"), - "satellite" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}") - ) - ), - ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration( - attribution = "© OpenStreetMap contributors, © CARTO", - setting = ProjectKeys.KEY_CARTO_MAP_STYLE, - uris = mapOf( - "positron" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"), - "dark_matter" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png") - ) - ), - ) - } } From c9e1640c467cde8172917a8a896754abc9a6e3ba Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 10:03:39 +0100 Subject: [PATCH 16/21] Share configurations between fragment and configurator --- .../org/odk/collect/mapbox/Configurations.kt | 79 ++++++++++++++----- .../collect/mapbox/MapboxMapConfigurator.kt | 57 ++----------- .../odk/collect/mapbox/MapboxMapFragment.kt | 4 +- 3 files changed, 70 insertions(+), 70 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt b/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt index d2bdbb7b459..be3f0063651 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/Configurations.kt @@ -3,51 +3,92 @@ package org.odk.collect.mapbox import com.mapbox.maps.Style import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.settings.keys.ProjectKeys.KEY_MAPBOX_MAP_STYLE +import org.odk.collect.strings.R object Configurations { val all = mapOf( ProjectKeys.BASEMAP_SOURCE_MAPBOX to Configuration( - setting = KEY_MAPBOX_MAP_STYLE, - uris = mapOf( - Style.MAPBOX_STREETS to BasemapUri.Mapbox(Style.MAPBOX_STREETS), - Style.LIGHT to BasemapUri.Mapbox(Style.LIGHT), - Style.DARK to BasemapUri.Mapbox(Style.DARK), - Style.SATELLITE to BasemapUri.Mapbox(Style.SATELLITE), - Style.SATELLITE_STREETS to BasemapUri.Mapbox(Style.SATELLITE_STREETS), - Style.OUTDOORS to BasemapUri.Mapbox(Style.OUTDOORS) + name = R.string.basemap_source_mapbox, + styleSetting = KEY_MAPBOX_MAP_STYLE, + styleOptions = mapOf( + Style.MAPBOX_STREETS to StyleOption( + name = R.string.streets, + BasemapUri.Mapbox(Style.MAPBOX_STREETS) + ), + Style.LIGHT to StyleOption( + name = R.string.light, + BasemapUri.Mapbox(Style.LIGHT) + ), + Style.DARK to StyleOption( + name = R.string.dark, + BasemapUri.Mapbox(Style.DARK) + ), + Style.SATELLITE to StyleOption( + name = R.string.satellite, + BasemapUri.Mapbox(Style.SATELLITE) + ), + Style.SATELLITE_STREETS to StyleOption( + name = R.string.hybrid, + BasemapUri.Mapbox(Style.SATELLITE_STREETS) + ), + Style.OUTDOORS to StyleOption( + name = R.string.outdoors, + BasemapUri.Mapbox(Style.OUTDOORS) + ) ) ), ProjectKeys.BASEMAP_SOURCE_OSM to Configuration( + name = R.string.basemap_source_osm, attribution = "© OpenStreetMap contributors", uri = BasemapUri.Raster("https://tile.openstreetmap.org/{z}/{x}/{y}.png") ), ProjectKeys.BASEMAP_SOURCE_USGS to Configuration( + name = R.string.basemap_source_usgs, attribution = "Map services and data available from U.S. Geological Survey, National Geospatial Program.", - setting = ProjectKeys.KEY_USGS_MAP_STYLE, - uris = mapOf( - "topographic" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}"), - "hybrid" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}"), - "satellite" to BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}") + styleSetting = ProjectKeys.KEY_USGS_MAP_STYLE, + styleOptions = mapOf( + "topographic" to StyleOption( + name = R.string.topographic, + BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}") + ), + "hybrid" to StyleOption( + name = R.string.hybrid, + BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryTopo/MapServer/tile/{z}/{y}/{x}") + ), + "satellite" to StyleOption( + name = R.string.satellite, + BasemapUri.Raster("https://basemap.nationalmap.gov/arcgis/rest/services/USGSImageryOnly/MapServer/tile/{z}/{y}/{x}") + ) ) ), ProjectKeys.BASEMAP_SOURCE_CARTO to Configuration( + name = R.string.basemap_source_carto, attribution = "© OpenStreetMap contributors, © CARTO", - setting = ProjectKeys.KEY_CARTO_MAP_STYLE, - uris = mapOf( - "positron" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png"), - "dark_matter" to BasemapUri.Raster("http://1.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png") + styleSetting = ProjectKeys.KEY_CARTO_MAP_STYLE, + styleOptions = mapOf( + "positron" to StyleOption( + name = R.string.carto_map_style_positron, + BasemapUri.Raster("http://1.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png") + ), + "dark_matter" to StyleOption( + name = R.string.carto_map_style_dark_matter, + BasemapUri.Raster("http://1.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png") + ) ) ), ) } class Configuration( + val name: Int, val attribution: String? = null, val uri: BasemapUri? = null, - val setting: String? = null, - val uris: Map = emptyMap() + val styleSetting: String? = null, + val styleOptions: Map = emptyMap() ) +class StyleOption(val name: Int, uri: BasemapUri) + sealed class BasemapUri(val value: String) { class Raster(uri: String) : BasemapUri(uri) class Mapbox(uri: String) : BasemapUri(uri) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt index 45dcca5afd8..49aa7bb6c28 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapConfigurator.kt @@ -6,16 +6,18 @@ import com.mapbox.maps.Style import org.odk.collect.androidshared.system.OpenGLVersionChecker.isOpenGLv3Supported import org.odk.collect.androidshared.ui.PrefUtils import org.odk.collect.androidshared.ui.ToastUtils.showLongToast +import org.odk.collect.mapbox.MapboxMapFragment import org.odk.collect.maps.MapConfigurator import org.odk.collect.maps.layers.MbtilesFile import org.odk.collect.settings.keys.ProjectKeys import org.odk.collect.shared.settings.Settings import org.odk.collect.strings.R import java.io.File +import kotlin.collections.toIntArray -class MapboxMapConfigurator(configuration: String) : MapConfigurator { +class MapboxMapConfigurator(private val configuration: Configuration) : MapConfigurator { - private val configuration = configurations.getValue(configuration) + constructor(configuration: String) : this(Configurations.all.getValue(configuration)) override fun isAvailable(context: Context): Boolean { /* @@ -35,17 +37,17 @@ class MapboxMapConfigurator(configuration: String) : MapConfigurator { } override fun createPrefs(context: Context, settings: Settings): List { - return if (configuration.settingsKey != null) { + return if (configuration.styleSetting != null) { listOf( PrefUtils.createListPref( context, - configuration.settingsKey, + configuration.styleSetting, context.getString( R.string.map_style_label, context.getString(configuration.name) ), - configuration.options.map { it.labelId }.toIntArray(), - configuration.options.map { it.url }.toTypedArray(), + configuration.styleOptions.map { it.value.name }.toIntArray(), + configuration.styleOptions.map { it.key }.toTypedArray(), settings ) ) @@ -63,47 +65,4 @@ class MapboxMapConfigurator(configuration: String) : MapConfigurator { val name = MbtilesFile.readName(file) return if (name != null) name else file.getName() } - - private class MapboxConfiguration( - val name: Int, - val settingsKey: String?, - val options: Array = emptyArray() - ) - - private class MapboxUrlOption(val url: String, val labelId: Int) - - companion object { - private val configurations = mapOf( - ProjectKeys.BASEMAP_SOURCE_MAPBOX to MapboxConfiguration( - R.string.basemap_source_mapbox, - ProjectKeys.KEY_MAPBOX_MAP_STYLE, - arrayOf( - MapboxUrlOption(Style.MAPBOX_STREETS, R.string.streets), - MapboxUrlOption(Style.LIGHT, R.string.light), - MapboxUrlOption(Style.DARK, R.string.dark), - MapboxUrlOption(Style.SATELLITE, R.string.satellite), - MapboxUrlOption(Style.SATELLITE_STREETS, R.string.hybrid), - MapboxUrlOption(Style.OUTDOORS, R.string.outdoors) - ) - ), - ProjectKeys.BASEMAP_SOURCE_OSM to MapboxConfiguration(R.string.basemap_source_osm, null), - ProjectKeys.BASEMAP_SOURCE_USGS to MapboxConfiguration( - R.string.basemap_source_usgs, - ProjectKeys.KEY_USGS_MAP_STYLE, - arrayOf( - MapboxUrlOption("topographic", R.string.topographic), - MapboxUrlOption("hybrid", R.string.hybrid), - MapboxUrlOption("satellite", R.string.satellite), - ) - ), - ProjectKeys.BASEMAP_SOURCE_CARTO to MapboxConfiguration( - R.string.basemap_source_carto, - ProjectKeys.KEY_CARTO_MAP_STYLE, - arrayOf( - MapboxUrlOption("positron", R.string.carto_map_style_positron), - MapboxUrlOption("dark_matter", R.string.carto_map_style_dark_matter), - ) - ) - ) - } } diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index dc19559175b..81bf9f0f2d7 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -267,8 +267,8 @@ class MapboxMapFragment(private val configuration: Configuration) : private fun loadStyle(settings: Settings) { val uri = if (configuration.uri != null) { configuration.uri - } else if (configuration.setting != null) { - configuration.uris.getValue(settings.getString(configuration.setting)!!) + } else if (configuration.styleSetting != null) { + configuration.styleOptions.getValue(settings.getString(configuration.styleSetting)!!) } else { throw IllegalArgumentException("Invalid Configuration!") } From 1e9988aa6d1335155a6046b24d9448321a075762 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 10:34:30 +0100 Subject: [PATCH 17/21] Remove deprecated methods --- .../java/org/odk/collect/maps/MapConfigurator.kt | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt b/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt index 6333e9f771c..46971aecd18 100644 --- a/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt +++ b/maps/src/main/java/org/odk/collect/maps/MapConfigurator.kt @@ -1,7 +1,6 @@ package org.odk.collect.maps import android.content.Context -import android.os.Bundle import androidx.preference.Preference import org.odk.collect.shared.settings.Settings import java.io.File @@ -31,19 +30,6 @@ interface MapConfigurator { /** Constructs any preference widgets that are specific to this map implementation. */ fun createPrefs(context: Context, settings: Settings): List - @Deprecated("Pref keys should be constants owned by the MapFragment modules") - /** Gets the set of keys for preferences that should be watched for changes. */ - val prefKeys: Collection - get() { - TODO() - } - - @Deprecated("This should be handled by MapFragment implementations") - /** Packs map-related preferences into a Bundle for MapFragment.applyConfig(). */ - fun buildConfig(prefs: Settings): Bundle { - TODO() - } - /** * Returns true if map fragments obtained from this MapConfigurator are * expected to be able to render the given file as an overlay. This From b50be62c277651eacdd285886859947e3afd1dbf Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 10:39:41 +0100 Subject: [PATCH 18/21] Fix strings that don't appear when Mapbox not part of the build --- strings/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/strings/src/main/res/values/strings.xml b/strings/src/main/res/values/strings.xml index 99eb448e3a3..30f1e25535e 100644 --- a/strings/src/main/res/values/strings.xml +++ b/strings/src/main/res/values/strings.xml @@ -521,11 +521,11 @@ Light Dark Outdoors - Topographic + Topographic Unable to access Google Maps. Is Google Play Services installed? - Positron - Dark Matter + Positron + Dark Matter Offline layers From f5c47c6adce89822bf0f8085989829440001d767 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 16:37:02 +0100 Subject: [PATCH 19/21] Fix tests --- .../android/geo/MapFragmentFactoryImpl.kt | 17 ++++++++++++++--- .../DirectoryReferenceLayerRepositoryTest.kt | 8 -------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt b/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt index e7471b4ea43..f7eec943595 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapFragmentFactoryImpl.kt @@ -13,11 +13,22 @@ class MapFragmentFactoryImpl(private val settingsProvider: SettingsProvider) : M override fun createMapFragment(): MapFragment { val settings = settingsProvider.getUnprotectedSettings() val basemapSource = settings.getString(KEY_BASEMAP_SOURCE) - return when { - basemapSource == ProjectKeys.BASEMAP_SOURCE_GOOGLE -> GoogleMapFragment() - else -> MapboxClassInstanceCreator.createMapboxMapFragment( + return if (isMapbox(basemapSource)) { + MapboxClassInstanceCreator.createMapboxMapFragment( basemapSource ?: ProjectKeys.BASEMAP_SOURCE_MAPBOX ) + } else { + GoogleMapFragment() + } + } + + private fun isMapbox(source: String?): Boolean { + return when (source) { + ProjectKeys.BASEMAP_SOURCE_MAPBOX -> true + ProjectKeys.BASEMAP_SOURCE_OSM -> true + ProjectKeys.BASEMAP_SOURCE_USGS -> true + ProjectKeys.BASEMAP_SOURCE_CARTO -> true + else -> false } } } diff --git a/maps/src/test/java/org/odk/collect/maps/layers/DirectoryReferenceLayerRepositoryTest.kt b/maps/src/test/java/org/odk/collect/maps/layers/DirectoryReferenceLayerRepositoryTest.kt index 0a7a3049213..49a5d814076 100644 --- a/maps/src/test/java/org/odk/collect/maps/layers/DirectoryReferenceLayerRepositoryTest.kt +++ b/maps/src/test/java/org/odk/collect/maps/layers/DirectoryReferenceLayerRepositoryTest.kt @@ -1,7 +1,6 @@ package org.odk.collect.maps.layers import android.content.Context -import android.os.Bundle import androidx.preference.Preference import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.contains @@ -266,12 +265,5 @@ class DirectoryReferenceLayerRepositoryTest { override fun createPrefs(context: Context, settings: Settings): MutableList { TODO("Not yet implemented") } - - override val prefKeys: Collection - get() = TODO("Not yet implemented") - - override fun buildConfig(prefs: Settings): Bundle { - TODO("Not yet implemented") - } } } From 9062d7adae5831b25ee30d1ce9cf50ea531f4a07 Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Fri, 29 May 2026 16:48:16 +0100 Subject: [PATCH 20/21] Use default values for tileset --- .../src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt index 81bf9f0f2d7..98710594f2a 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -276,7 +276,7 @@ class MapboxMapFragment(private val configuration: Configuration) : when (uri) { is BasemapUri.Raster -> { mapboxMap.loadStyleUri("") { style -> - val tileSet = TileSet.Builder("2.2.0", listOf(uri.value)) + val tileSet = TileSet.Builder("2.1.0", listOf(uri.value)) .attribution(configuration.attribution ?: "") .scheme(Scheme.XYZ) .build() @@ -285,7 +285,6 @@ class MapboxMapFragment(private val configuration: Configuration) : style.addSource( rasterSource("basemap_source") { tileSet(tileSet) - tileSize(256) } ) } From 3e57fef920b2c5f06ee59e5be8a23c5e0162ea6d Mon Sep 17 00:00:00 2001 From: Callum Stott Date: Mon, 1 Jun 2026 09:44:45 +0100 Subject: [PATCH 21/21] Configure fake MapConfigurator for Robolectric tests --- .../android/geo/MapConfiguratorProvider.java | 9 +++- .../application/RobolectricApplication.java | 41 ++++++++++++++++++- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java index 5a4d6516f5b..b65bc45a2d6 100644 --- a/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java +++ b/collect_app/src/main/java/org/odk/collect/android/geo/MapConfiguratorProvider.java @@ -18,6 +18,7 @@ import org.odk.collect.maps.MapConfigurator; import java.util.ArrayList; +import java.util.List; public class MapConfiguratorProvider { @@ -62,6 +63,10 @@ public static void initOptions(Context context) { )); } + initOptions(sourceOptions); + } + + public static void initOptions(List sourceOptions) { MapConfiguratorProvider.sourceOptions = sourceOptions.toArray(new SourceOption[]{}); } @@ -122,12 +127,12 @@ private static Collect getApplication() { return Collect.getInstance(); } - private static class SourceOption { + public static class SourceOption { private final String id; // preference value to store private final int labelId; // string resource ID private final MapConfigurator cftor; - private SourceOption(String id, int labelId, MapConfigurator cftor) { + public SourceOption(String id, int labelId, MapConfigurator cftor) { this.id = id; this.labelId = labelId; this.cftor = cftor; diff --git a/collect_app/src/test/java/org/odk/collect/android/application/RobolectricApplication.java b/collect_app/src/test/java/org/odk/collect/android/application/RobolectricApplication.java index 95ddabf02eb..2403155e225 100644 --- a/collect_app/src/test/java/org/odk/collect/android/application/RobolectricApplication.java +++ b/collect_app/src/test/java/org/odk/collect/android/application/RobolectricApplication.java @@ -2,19 +2,31 @@ import static android.os.Environment.MEDIA_MOUNTED; import static org.robolectric.Shadows.shadowOf; +import static java.util.Arrays.asList; +import android.content.Context; import android.os.StrictMode; +import androidx.preference.Preference; import androidx.test.core.app.ApplicationProvider; import androidx.work.Configuration; import androidx.work.WorkManager; -import org.odk.collect.db.sqlite.DatabaseConnection; +import org.jetbrains.annotations.NotNull; +import org.odk.collect.android.geo.MapConfiguratorProvider; import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard; import org.odk.collect.crashhandler.CrashHandler; +import org.odk.collect.db.sqlite.DatabaseConnection; +import org.odk.collect.maps.MapConfigurator; +import org.odk.collect.shared.settings.Settings; +import org.odk.collect.strings.R.string; import org.robolectric.shadows.ShadowApplication; import org.robolectric.shadows.ShadowEnvironment; +import java.io.File; +import java.util.Collections; +import java.util.List; + /** * @author James Knight */ @@ -61,5 +73,32 @@ public void onCreate() { .permitCustomSlowCalls() .build() ); + + MapConfiguratorProvider.initOptions(asList(new MapConfiguratorProvider.SourceOption("fake-map", string.maps, new MapConfigurator() { + @Override + public boolean isAvailable(@NotNull Context context) { + return true; + } + + @Override + public void showUnavailableMessage(@NotNull Context context) { + + } + + @Override + public @NotNull List<@NotNull Preference> createPrefs(@NotNull Context context, @NotNull Settings settings) { + return Collections.emptyList(); + } + + @Override + public boolean supportsLayer(@NotNull File file) { + return true; + } + + @Override + public @NotNull String getDisplayName(@NotNull File file) { + return ""; + } + }))); } }