From 4cb8ae101f2efc8e9e3525d3d7b4111eb4d26504 Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Wed, 28 Aug 2024 09:18:17 +0200 Subject: [PATCH 1/7] Enabled scale bar in Mapbox --- .../src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt | 2 -- 1 file changed, 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 f7ee7e55a05..207b6e7e7d2 100644 --- a/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt +++ b/mapbox/src/main/java/org/odk/collect/mapbox/MapboxMapFragment.kt @@ -44,7 +44,6 @@ import com.mapbox.maps.plugin.gestures.OnMapLongClickListener import com.mapbox.maps.plugin.gestures.addOnMapClickListener import com.mapbox.maps.plugin.gestures.addOnMapLongClickListener import com.mapbox.maps.plugin.locationcomponent.location -import com.mapbox.maps.plugin.scalebar.scalebar import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.odk.collect.androidshared.utils.ScreenUtils @@ -152,7 +151,6 @@ class MapboxMapFragment : savedInstanceState: Bundle? ): View { mapView = MapView(inflater.context).apply { - scalebar.enabled = false compass.position = Gravity.TOP or Gravity.START compass.marginTop = 36f compass.marginBottom = 36f From 0779d95ec80c58960b5b6334858100bb2131687c Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Wed, 28 Aug 2024 09:20:49 +0200 Subject: [PATCH 2/7] Enabled scale bar in OSM --- .../main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java | 2 ++ 1 file changed, 2 insertions(+) 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 eb081b9cc70..63154965de9 100644 --- a/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java +++ b/osmdroid/src/main/java/org/odk/collect/osmdroid/OsmDroidMapFragment.java @@ -67,6 +67,7 @@ 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 org.osmdroid.views.overlay.mylocation.IMyLocationConsumer; import org.osmdroid.views.overlay.mylocation.IMyLocationProvider; @@ -201,6 +202,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, map.getController().setZoom((int) INITIAL_ZOOM); map.setTilesScaledToDpi(true); map.setFlingEnabled(false); + map.getOverlays().add(new ScaleBarOverlay(map)); addAttributionAndMapEventsOverlays(); loadReferenceOverlay(); addMapLayoutChangeListener(map); From 6c5b4403b58b181eb628686e0549813c12fa2403 Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Wed, 28 Aug 2024 10:36:41 +0200 Subject: [PATCH 3/7] Reworked GoogleMapFragment to use custom layout with SupportMapFragment instead of extending it --- .../collect/googlemaps/GoogleMapFragment.java | 36 ++++++++++++++----- .../src/main/res/layout/map_layout.xml | 12 +++++++ 2 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 google-maps/src/main/res/layout/map_layout.xml 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 e2c62e6cc59..09e4c931440 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 @@ -19,9 +19,13 @@ import android.location.Location; 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.fragment.app.Fragment; import com.google.android.gms.location.LocationListener; import com.google.android.gms.maps.CameraUpdate; @@ -72,7 +76,7 @@ import timber.log.Timber; -public class GoogleMapFragment extends SupportMapFragment implements +public class GoogleMapFragment extends Fragment implements MapFragment, LocationListener, LocationClient.LocationClientListener, GoogleMap.OnMapClickListener, GoogleMap.OnMapLongClickListener, GoogleMap.OnMarkerClickListener, GoogleMap.OnMarkerDragListener, @@ -100,6 +104,8 @@ public class GoogleMapFragment extends SupportMapFragment implements ); private GoogleMap map; + private ReadyListener readyListener; + private ErrorListener errorListener; private Marker locationCrosshairs; private Circle accuracyCircle; private final List gpsLocationReadyListeners = new ArrayList<>(); @@ -121,9 +127,25 @@ public class GoogleMapFragment extends SupportMapFragment implements private boolean hasCenter; @Override - @SuppressLint("MissingPermission") // Permission checks for location services handled in widgets public void init(@Nullable ReadyListener readyListener, @Nullable ErrorListener errorListener) { - getMapAsync((GoogleMap googleMap) -> { + this.readyListener = readyListener; + this.errorListener = errorListener; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mapFragmentDelegate.onCreate(savedInstanceState); + } + + @Nullable + @Override + @SuppressLint("MissingPermission") // Permission checks for location services handled in widgets + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.map_layout, container, false); + + SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map); + mapFragment.getMapAsync((GoogleMap googleMap) -> { if (googleMap == null) { ToastUtils.showShortToast(requireContext(), org.odk.collect.strings.R.string.google_play_services_error_occured); if (errorListener != null) { @@ -144,7 +166,7 @@ public void init(@Nullable ReadyListener readyListener, @Nullable ErrorListener googleMap.setMyLocationEnabled(false); googleMap.setMinZoomPreference(1); googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom( - toLatLng(INITIAL_CENTER), INITIAL_ZOOM)); + toLatLng(INITIAL_CENTER), INITIAL_ZOOM)); loadReferenceOverlay(); // If the screen is rotated before the map is ready, this fragment @@ -155,12 +177,8 @@ public void init(@Nullable ReadyListener readyListener, @Nullable ErrorListener readyListener.onReady(this); } }); - } - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mapFragmentDelegate.onCreate(savedInstanceState); + return view; } @Override public void onAttach(@NonNull Context context) { diff --git a/google-maps/src/main/res/layout/map_layout.xml b/google-maps/src/main/res/layout/map_layout.xml new file mode 100644 index 00000000000..e9650afff45 --- /dev/null +++ b/google-maps/src/main/res/layout/map_layout.xml @@ -0,0 +1,12 @@ + + + + + + From a73860b9357b4255576976e28ecadf9e4ab26f0f Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Wed, 28 Aug 2024 11:04:16 +0200 Subject: [PATCH 4/7] Added scale bar to google maps --- buildSrc/src/main/java/dependencies/Dependencies.kt | 1 + google-maps/build.gradle.kts | 1 + .../odk/collect/googlemaps/GoogleMapFragment.java | 6 ++++++ google-maps/src/main/res/layout/map_layout.xml | 12 +++++++++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/dependencies/Dependencies.kt b/buildSrc/src/main/java/dependencies/Dependencies.kt index 78357f1cc9b..62c52640df9 100644 --- a/buildSrc/src/main/java/dependencies/Dependencies.kt +++ b/buildSrc/src/main/java/dependencies/Dependencies.kt @@ -22,6 +22,7 @@ object Dependencies { const val android_flexbox = "com.google.android.flexbox:flexbox:3.0.0" const val play_services_maps = "com.google.android.gms:play-services-maps:19.0.0" const val play_services_location = "com.google.android.gms:play-services-location:20.0.0" // Check if map screens still work when upgrading and location works as expected https://github.com/getodk/collect/issues/6027, especially after moving to FusedLocationProviderClient. + const val mapscaleview = "com.github.pengrad:mapscaleview:1.6.0" const val play_services_oss_licenses = "com.google.android.gms:play-services-oss-licenses:17.1.0" const val mapbox_android_sdk = "com.mapbox.maps:android:11.4.1" const val osmdroid = "org.osmdroid:osmdroid-android:6.1.18" diff --git a/google-maps/build.gradle.kts b/google-maps/build.gradle.kts index 4f26ae708a3..6fa2bbc3ecd 100644 --- a/google-maps/build.gradle.kts +++ b/google-maps/build.gradle.kts @@ -58,6 +58,7 @@ dependencies { implementation(Dependencies.androidx_preference_ktx) implementation(Dependencies.play_services_maps) implementation(Dependencies.play_services_location) + implementation(Dependencies.mapscaleview) implementation(Dependencies.timber) implementation(Dependencies.android_material) 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 09e4c931440..b2556ed7558 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 @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; +import com.github.pengrad.mapscaleview.MapScaleView; import com.google.android.gms.location.LocationListener; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; @@ -104,6 +105,7 @@ public class GoogleMapFragment extends Fragment implements ); private GoogleMap map; + private MapScaleView scaleView; private ReadyListener readyListener; private ErrorListener errorListener; private Marker locationCrosshairs; @@ -144,6 +146,8 @@ public void onCreate(@Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.map_layout, container, false); + scaleView = view.findViewById(R.id.scale_view); + SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map); mapFragment.getMapAsync((GoogleMap googleMap) -> { if (googleMap == null) { @@ -167,6 +171,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c googleMap.setMinZoomPreference(1); googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom( toLatLng(INITIAL_CENTER), INITIAL_ZOOM)); + googleMap.setOnCameraMoveListener(() -> scaleView.update(googleMap.getCameraPosition().zoom, googleMap.getCameraPosition().target.latitude)); + googleMap.setOnCameraIdleListener(() -> scaleView.update(googleMap.getCameraPosition().zoom, googleMap.getCameraPosition().target.latitude)); loadReferenceOverlay(); // If the screen is rotated before the map is ready, this fragment diff --git a/google-maps/src/main/res/layout/map_layout.xml b/google-maps/src/main/res/layout/map_layout.xml index e9650afff45..2204d2375c2 100644 --- a/google-maps/src/main/res/layout/map_layout.xml +++ b/google-maps/src/main/res/layout/map_layout.xml @@ -1,7 +1,8 @@ + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + From 118a54ddcb9bb2fd8d791bf2420a7d05cccabf03 Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Tue, 3 Sep 2024 20:16:42 +0200 Subject: [PATCH 5/7] Pull out the dependency into googlmaps module --- .../main/java/dependencies/Dependencies.kt | 1 - google-maps/build.gradle.kts | 1 - .../collect/googlemaps/GoogleMapFragment.java | 2 +- .../collect/googlemaps/scaleview/Drawer.java | 186 ++++++++++++++++++ .../googlemaps/scaleview/MapScaleModel.java | 85 ++++++++ .../googlemaps/scaleview/MapScaleView.java | 174 ++++++++++++++++ .../collect/googlemaps/scaleview/Scale.java | 20 ++ .../collect/googlemaps/scaleview/Scales.java | 26 +++ .../googlemaps/scaleview/ViewConfig.java | 37 ++++ .../src/main/res/layout/map_layout.xml | 2 +- google-maps/src/main/res/values/attrs.xml | 12 ++ 11 files changed, 542 insertions(+), 4 deletions(-) create mode 100644 google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java create mode 100644 google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java create mode 100644 google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java create mode 100644 google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java create mode 100644 google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java create mode 100644 google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java create mode 100644 google-maps/src/main/res/values/attrs.xml diff --git a/buildSrc/src/main/java/dependencies/Dependencies.kt b/buildSrc/src/main/java/dependencies/Dependencies.kt index 62c52640df9..78357f1cc9b 100644 --- a/buildSrc/src/main/java/dependencies/Dependencies.kt +++ b/buildSrc/src/main/java/dependencies/Dependencies.kt @@ -22,7 +22,6 @@ object Dependencies { const val android_flexbox = "com.google.android.flexbox:flexbox:3.0.0" const val play_services_maps = "com.google.android.gms:play-services-maps:19.0.0" const val play_services_location = "com.google.android.gms:play-services-location:20.0.0" // Check if map screens still work when upgrading and location works as expected https://github.com/getodk/collect/issues/6027, especially after moving to FusedLocationProviderClient. - const val mapscaleview = "com.github.pengrad:mapscaleview:1.6.0" const val play_services_oss_licenses = "com.google.android.gms:play-services-oss-licenses:17.1.0" const val mapbox_android_sdk = "com.mapbox.maps:android:11.4.1" const val osmdroid = "org.osmdroid:osmdroid-android:6.1.18" diff --git a/google-maps/build.gradle.kts b/google-maps/build.gradle.kts index 6fa2bbc3ecd..4f26ae708a3 100644 --- a/google-maps/build.gradle.kts +++ b/google-maps/build.gradle.kts @@ -58,7 +58,6 @@ dependencies { implementation(Dependencies.androidx_preference_ktx) implementation(Dependencies.play_services_maps) implementation(Dependencies.play_services_location) - implementation(Dependencies.mapscaleview) implementation(Dependencies.timber) implementation(Dependencies.android_material) 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 b2556ed7558..93fcf9e40be 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 @@ -27,7 +27,6 @@ import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.github.pengrad.mapscaleview.MapScaleView; import com.google.android.gms.location.LocationListener; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.CameraUpdateFactory; @@ -51,6 +50,7 @@ import org.odk.collect.androidshared.system.ContextUtils; import org.odk.collect.androidshared.ui.ToastUtils; import org.odk.collect.googlemaps.GoogleMapConfigurator.GoogleMapTypeOption; +import org.odk.collect.googlemaps.scaleview.MapScaleView; import org.odk.collect.location.LocationClient; import org.odk.collect.maps.LineDescription; import org.odk.collect.maps.MapConfigurator; diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java new file mode 100644 index 00000000000..67116e0d9ed --- /dev/null +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java @@ -0,0 +1,186 @@ +package org.odk.collect.googlemaps.scaleview; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.Typeface; + +public class Drawer { + + private final Paint textPaint = new Paint(); + private final Paint strokePaint = new Paint(); + private final Path strokePath = new Path(); + + private final Paint outlinePaint = new Paint(); + private final Path outlineDiffPath = new Path(); + private float outlineStrokeWidth = 2; // strokeWidth * 2 + private float outlineStrokeDiff = outlineStrokeWidth / 2 / 2; // strokeWidth / 2 + private float outlineTextStrokeWidth = 3; // density * 2 + private boolean outlineEnabled = true; + + private float textHeight; + private float horizontalLineY; + + private boolean expandRtlEnabled; + private int viewWidth; + + private Scales scales = new Scales(null, null); + + Drawer(int color, float textSize, float strokeWidth, float density, boolean outlineEnabled, boolean expandRtlEnabled) { + textPaint.setAntiAlias(true); + textPaint.setColor(color); + textPaint.setStyle(Paint.Style.FILL); + textPaint.setTextSize(textSize); + + strokePaint.setAntiAlias(true); + strokePaint.setColor(color); + strokePaint.setStyle(Paint.Style.STROKE); + strokePaint.setStrokeWidth(strokeWidth); + + outlinePaint.set(strokePaint); + outlinePaint.setARGB(255, 255, 255, 255); + outlineStrokeWidth = strokeWidth * 2; + outlineStrokeDiff = strokeWidth / 2; + outlineTextStrokeWidth = density * 2; + this.outlineEnabled = outlineEnabled; + this.expandRtlEnabled = expandRtlEnabled; + + update(); + } + + private void update() { + outlinePaint.setTextSize(textPaint.getTextSize()); + outlinePaint.setTypeface(textPaint.getTypeface()); + outlinePaint.setStrokeWidth(outlineTextStrokeWidth); + + Rect textRect = new Rect(); + Paint highestPaint = outlineEnabled ? outlinePaint : textPaint; + String possibleText = "1234567890kmift"; + highestPaint.getTextBounds(possibleText, 0, possibleText.length(), textRect); + textHeight = textRect.height(); + + horizontalLineY = textHeight + textHeight / 2; + } + + int getWidth() { + return (int) (scales.maxLength() + strokePaint.getStrokeWidth()); + } + + int getHeight() { + if (scales.bottom() != null) { + return (int) (textHeight * 3 + outlineTextStrokeWidth / 2); + } else { + return (int) (horizontalLineY + strokePaint.getStrokeWidth()); + } + } + + void setScales(Scales scales) { + this.scales = scales; + } + + void setColor(int color) { + textPaint.setColor(color); + strokePaint.setColor(color); + } + + void setTextSize(float textSize) { + textPaint.setTextSize(textSize); + update(); + } + + void setTextFont(Typeface font) { + textPaint.setTypeface(font); + update(); + } + + void setStrokeWidth(float strokeWidth) { + strokePaint.setStrokeWidth(strokeWidth); + outlineStrokeWidth = strokeWidth * 2; + outlineStrokeDiff = strokeWidth / 2; + update(); + } + + void setOutlineEnabled(boolean enabled) { + outlineEnabled = enabled; + update(); + } + + void setExpandRtlEnabled(boolean enabled) { + expandRtlEnabled = enabled; + } + + void setViewWidth(int width) { + viewWidth = width; + } + + void draw(Canvas canvas) { + Scale top = scales.top(); + if (top == null) { + return; + } + if (expandRtlEnabled && viewWidth == 0) { + expandRtlEnabled = false; + } + + if (expandRtlEnabled) { + outlinePaint.setTextAlign(Paint.Align.RIGHT); + textPaint.setTextAlign(Paint.Align.RIGHT); + } else { + outlinePaint.setTextAlign(Paint.Align.LEFT); + textPaint.setTextAlign(Paint.Align.LEFT); + } + + if (outlineEnabled) { + outlinePaint.setStrokeWidth(outlineTextStrokeWidth); + canvas.drawText(top.text(), expandRtlEnabled ? viewWidth : 0, textHeight, outlinePaint); + } + canvas.drawText(top.text(), expandRtlEnabled ? viewWidth : 0, textHeight, textPaint); + + strokePath.rewind(); + strokePath.moveTo(expandRtlEnabled ? (viewWidth - outlineStrokeDiff) : outlineStrokeDiff, horizontalLineY); + strokePath.lineTo(expandRtlEnabled ? (viewWidth - top.length()) : top.length(), horizontalLineY); + if (outlineEnabled) { + strokePath.lineTo(expandRtlEnabled ? (viewWidth - top.length()) : top.length(), textHeight + outlineStrokeDiff); + } else { + strokePath.lineTo(expandRtlEnabled ? (viewWidth - top.length()) : top.length(), textHeight); + } + + Scale bottom = scales.bottom(); + if (bottom != null) { + + if (bottom.length() > top.length()) { + strokePath.moveTo(expandRtlEnabled ? (viewWidth - top.length()) : top.length(), horizontalLineY); + strokePath.lineTo(expandRtlEnabled ? (viewWidth - bottom.length()) : bottom.length(), horizontalLineY); + } else { + strokePath.moveTo(expandRtlEnabled ? (viewWidth - bottom.length()) : bottom.length(), horizontalLineY); + } + + strokePath.lineTo(expandRtlEnabled ? (viewWidth - bottom.length()) : bottom.length(), textHeight * 2); + + float bottomTextY = horizontalLineY + textHeight + textHeight / 2; + if (outlineEnabled) { + canvas.drawText(bottom.text(), expandRtlEnabled ? viewWidth : 0, bottomTextY, outlinePaint); + } + canvas.drawText(bottom.text(), expandRtlEnabled ? viewWidth : 0, bottomTextY, textPaint); + } + + if (outlineEnabled) { + outlinePaint.setStrokeWidth(outlineStrokeWidth); + outlineDiffPath.rewind(); + outlineDiffPath.moveTo(expandRtlEnabled ? viewWidth : 0, horizontalLineY); + outlineDiffPath.lineTo(expandRtlEnabled ? (viewWidth - outlineStrokeDiff) : outlineStrokeDiff, horizontalLineY); + outlineDiffPath.moveTo(expandRtlEnabled ? (viewWidth - top.length()) : top.length(), textHeight + outlineStrokeDiff); + outlineDiffPath.lineTo(expandRtlEnabled ? (viewWidth - top.length()) : top.length(), textHeight); + if (bottom != null) { + outlineDiffPath.moveTo(expandRtlEnabled ? (viewWidth - bottom.length()) : bottom.length(), textHeight * 2); + outlineDiffPath.lineTo(expandRtlEnabled ? (viewWidth - bottom.length()) : bottom.length(), textHeight * 2 + outlineStrokeDiff); + } + + canvas.drawPath(outlineDiffPath, outlinePaint); + canvas.drawPath(strokePath, outlinePaint); + } + + canvas.drawPath(strokePath, strokePaint); + } +} diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java new file mode 100644 index 00000000000..ec34c896068 --- /dev/null +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java @@ -0,0 +1,85 @@ +package org.odk.collect.googlemaps.scaleview; + +class MapScaleModel { + + private static final double EQUATOR_LENGTH_METERS = 40075016.686; + private static final double EQUATOR_LENGTH_FEET = 131479713.537; + + private static final int FT_IN_MILE = 5280; + + private static final float[] METERS = {0.2f, 0.5f, 1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, + 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000, 1000000, 2000000}; + + private static final float[] FT = {1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, + FT_IN_MILE, 2 * FT_IN_MILE, 5 * FT_IN_MILE, 10 * FT_IN_MILE, 20 * FT_IN_MILE, 50 * FT_IN_MILE, + 100 * FT_IN_MILE, 200 * FT_IN_MILE, 500 * FT_IN_MILE, 1000 * FT_IN_MILE, 2000 * FT_IN_MILE}; + + private final float density; + private int maxWidth; + + private float lastZoom = -1; + private double lastLatitude = -100; + + private double tileSizeMetersAt0Zoom = EQUATOR_LENGTH_METERS / 256; + private double tileSizeFeetAt0Zoom = EQUATOR_LENGTH_FEET / 256; + + MapScaleModel(float density) { + this.density = density; + } + + // returns true if width changed + boolean updateMaxWidth(int width) { + if (maxWidth != width) { + maxWidth = width; + return true; + } else return false; + } + + void setTileSize(int tileSize) { + tileSizeMetersAt0Zoom = EQUATOR_LENGTH_METERS / tileSize; + tileSizeFeetAt0Zoom = EQUATOR_LENGTH_FEET / tileSize; + } + + void setPosition(float zoom, double latitude) { + lastZoom = zoom; + lastLatitude = latitude; + } + + /** + * See http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale + */ + Scale update(boolean meters) { + float zoom = lastZoom; + double latitude = lastLatitude; + if (zoom < 0 || Math.abs(latitude) > 90) return null; + + double tileSizeAtZoom0 = meters ? tileSizeMetersAt0Zoom : tileSizeFeetAt0Zoom; + float[] distances = meters ? METERS : FT; + + final double resolution = tileSizeAtZoom0 / density * Math.cos(latitude * Math.PI / 180) / Math.pow(2, zoom); + + float distance = 0; + int distanceIndex = distances.length; + double screenDistance = maxWidth + 1; + + while (screenDistance > maxWidth && distanceIndex > 0) { + distance = distances[--distanceIndex]; + screenDistance = Math.abs(distance / resolution); + } + + lastZoom = zoom; + lastLatitude = latitude; + return new Scale(text(distance, meters), (float) screenDistance); + } + + private String text(float distance, boolean meters) { + if (meters) { + if (distance < 1) return (int) (distance * 100) + " cm"; + if (distance < 1000) return (int) distance + " m"; + else return (int) distance / 1000 + " km"; + } else { + if (distance < FT_IN_MILE) return (int) distance + " ft"; + else return (int) distance / FT_IN_MILE + " mi"; + } + } +} diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java new file mode 100644 index 00000000000..d86b65320f6 --- /dev/null +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java @@ -0,0 +1,174 @@ +package org.odk.collect.googlemaps.scaleview; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Typeface; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.ColorInt; + +public class MapScaleView extends View { + + private final MapScaleModel mapScaleModel; + private final Drawer drawer; + + private final int maxWidth; + + private ScaleType scaleType = ScaleType.BOTH; + + private enum ScaleType { + METERS_ONLY, MILES_ONLY, BOTH + } + + public MapScaleView(Context context) { + this(context, null); + } + + public MapScaleView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public MapScaleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + float density = getResources().getDisplayMetrics().density; + mapScaleModel = new MapScaleModel(density); + + ViewConfig viewConfig = new ViewConfig(context, attrs); + drawer = new Drawer(viewConfig.color, viewConfig.textSize, viewConfig.strokeWidth, density, viewConfig.outline, viewConfig.expandRtl); + + maxWidth = viewConfig.maxWidth; + + if (viewConfig.isMiles) { + scaleType = ScaleType.MILES_ONLY; + } + } + + public void setTileSize(int tileSize) { + mapScaleModel.setTileSize(tileSize); + updateScales(); + } + + public void setColor(@ColorInt int color) { + drawer.setColor(color); + invalidate(); + } + + public void setTextSize(float textSize) { + drawer.setTextSize(textSize); + invalidate(); + requestLayout(); + } + + public void setTextFont(Typeface font) { + drawer.setTextFont(font); + invalidate(); + requestLayout(); + } + + public void setStrokeWidth(float strokeWidth) { + drawer.setStrokeWidth(strokeWidth); + invalidate(); + requestLayout(); + } + + public void setOutlineEnabled(boolean enabled) { + drawer.setOutlineEnabled(enabled); + invalidate(); + } + + public void setExpandRtlEnabled(boolean enabled) { + drawer.setExpandRtlEnabled(enabled); + invalidate(); + } + + /** + * @deprecated Use milesOnly() + */ + @Deprecated + public void setIsMiles(boolean miles) { + if (miles) milesOnly(); + else metersAndMiles(); + } + + public void metersOnly() { + scaleType = ScaleType.METERS_ONLY; + updateScales(); + } + + public void milesOnly() { + scaleType = ScaleType.MILES_ONLY; + updateScales(); + } + + public void metersAndMiles() { + scaleType = ScaleType.BOTH; + updateScales(); + } + + public void update(float zoom, double latitude) { + mapScaleModel.setPosition(zoom, latitude); + updateScales(); + } + + private void updateScales() { + Scale top, bottom = null; + + if (scaleType == ScaleType.MILES_ONLY) { + top = mapScaleModel.update(false); + } else { + top = mapScaleModel.update(true); + if (scaleType == ScaleType.BOTH) { + bottom = mapScaleModel.update(false); + } + } + + drawer.setScales(new Scales(top, bottom)); + invalidate(); + requestLayout(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = measureDimension(desiredWidth(), widthMeasureSpec); + int height = measureDimension(desiredHeight(), heightMeasureSpec); + + if (mapScaleModel.updateMaxWidth(width)) { + updateScales(); + } + + if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) { + width = drawer.getWidth(); + } + + drawer.setViewWidth(width); + setMeasuredDimension(width, height); + } + + private int desiredWidth() { + return maxWidth; + } + + private int desiredHeight() { + return drawer.getHeight(); + } + + private int measureDimension(int desiredSize, int measureSpec) { + int mode = View.MeasureSpec.getMode(measureSpec); + int size = View.MeasureSpec.getSize(measureSpec); + + if (mode == View.MeasureSpec.EXACTLY) { + return size; + } else if (mode == View.MeasureSpec.AT_MOST) { + return Math.min(desiredSize, size); + } else { + return desiredSize; + } + } + + @Override + public void onDraw(Canvas canvas) { + drawer.draw(canvas); + } +} diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java new file mode 100644 index 00000000000..f43b8215bd3 --- /dev/null +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java @@ -0,0 +1,20 @@ +package org.odk.collect.googlemaps.scaleview; + +class Scale { + + private final String text; + private final float length; + + Scale(String text, float length) { + this.text = text; + this.length = length; + } + + public String text() { + return text; + } + + public float length() { + return length; + } +} diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java new file mode 100644 index 00000000000..cbb9af83b43 --- /dev/null +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java @@ -0,0 +1,26 @@ +package org.odk.collect.googlemaps.scaleview; + +import androidx.annotation.Nullable; + +class Scales { + private final Scale top, bottom; + + Scales(Scale top, Scale bottom) { + this.top = top; + this.bottom = bottom; + } + + @Nullable + Scale top() { + return top; + } + + @Nullable + Scale bottom() { + return bottom; + } + + float maxLength() { + return Math.max(top != null ? top.length() : 0, bottom != null ? bottom.length() : 0); + } +} diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java new file mode 100644 index 00000000000..876a0d4ca90 --- /dev/null +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java @@ -0,0 +1,37 @@ +package org.odk.collect.googlemaps.scaleview; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.util.AttributeSet; + +import org.odk.collect.googlemaps.R; + +class ViewConfig { + + final int maxWidth; + final int color; + final float textSize; + final float strokeWidth; + final boolean isMiles; + final boolean outline; + final boolean expandRtl; + + ViewConfig(Context context, AttributeSet attrs) { +// float fontScale = context.getResources().getDisplayMetrics().scaledDensity; + float density = context.getResources().getDisplayMetrics().density; + + TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MapScaleView, 0, 0); + try { + maxWidth = a.getDimensionPixelSize(R.styleable.MapScaleView_scale_maxWidth, (int) (100 * density)); + color = a.getColor(R.styleable.MapScaleView_scale_color, Color.parseColor("#333333")); + textSize = a.getDimension(R.styleable.MapScaleView_scale_textSize, 12 * density); + strokeWidth = a.getDimension(R.styleable.MapScaleView_scale_strokeWidth, 1.5f * density); + isMiles = a.getBoolean(R.styleable.MapScaleView_scale_miles, false); + outline = a.getBoolean(R.styleable.MapScaleView_scale_outline, true); + expandRtl = a.getBoolean(R.styleable.MapScaleView_scale_expandRtl, false); + } finally { + a.recycle(); + } + } +} diff --git a/google-maps/src/main/res/layout/map_layout.xml b/google-maps/src/main/res/layout/map_layout.xml index 2204d2375c2..3d4fe30600d 100644 --- a/google-maps/src/main/res/layout/map_layout.xml +++ b/google-maps/src/main/res/layout/map_layout.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> - + + + + + + + + + + + From 69b172946d35cb5e1d26fab0c18729d7e07dab02 Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Tue, 3 Sep 2024 20:24:54 +0200 Subject: [PATCH 6/7] Fixed code style --- .../googlemaps/scaleview/MapScaleModel.java | 25 +++++++++++++------ .../googlemaps/scaleview/MapScaleView.java | 10 +++++--- .../collect/googlemaps/scaleview/Scales.java | 3 ++- .../googlemaps/scaleview/ViewConfig.java | 1 - 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java index ec34c896068..e424f03ed1f 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java @@ -32,7 +32,9 @@ boolean updateMaxWidth(int width) { if (maxWidth != width) { maxWidth = width; return true; - } else return false; + } else { + return false; + } } void setTileSize(int tileSize) { @@ -51,7 +53,9 @@ void setPosition(float zoom, double latitude) { Scale update(boolean meters) { float zoom = lastZoom; double latitude = lastLatitude; - if (zoom < 0 || Math.abs(latitude) > 90) return null; + if (zoom < 0 || Math.abs(latitude) > 90) { + return null; + } double tileSizeAtZoom0 = meters ? tileSizeMetersAt0Zoom : tileSizeFeetAt0Zoom; float[] distances = meters ? METERS : FT; @@ -74,12 +78,19 @@ Scale update(boolean meters) { private String text(float distance, boolean meters) { if (meters) { - if (distance < 1) return (int) (distance * 100) + " cm"; - if (distance < 1000) return (int) distance + " m"; - else return (int) distance / 1000 + " km"; + if (distance < 1) { + return (int) (distance * 100) + " cm"; + } else if (distance < 1000) { + return (int) distance + " m"; + } else { + return (int) distance / 1000 + " km"; + } } else { - if (distance < FT_IN_MILE) return (int) distance + " ft"; - else return (int) distance / FT_IN_MILE + " mi"; + if (distance < FT_IN_MILE) { + return (int) distance + " ft"; + } else { + return (int) distance / FT_IN_MILE + " mi"; + } } } } diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java index d86b65320f6..d6c949833cf 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java @@ -88,8 +88,11 @@ public void setExpandRtlEnabled(boolean enabled) { */ @Deprecated public void setIsMiles(boolean miles) { - if (miles) milesOnly(); - else metersAndMiles(); + if (miles) { + milesOnly(); + } else { + metersAndMiles(); + } } public void metersOnly() { @@ -113,7 +116,8 @@ public void update(float zoom, double latitude) { } private void updateScales() { - Scale top, bottom = null; + Scale top; + Scale bottom = null; if (scaleType == ScaleType.MILES_ONLY) { top = mapScaleModel.update(false); diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java index cbb9af83b43..5e607305684 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java @@ -3,7 +3,8 @@ import androidx.annotation.Nullable; class Scales { - private final Scale top, bottom; + private final Scale top; + private final Scale bottom; Scales(Scale top, Scale bottom) { this.top = top; diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java index 876a0d4ca90..f1eed952ccd 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java @@ -18,7 +18,6 @@ class ViewConfig { final boolean expandRtl; ViewConfig(Context context, AttributeSet attrs) { -// float fontScale = context.getResources().getDisplayMetrics().scaledDensity; float density = context.getResources().getDisplayMetrics().density; TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MapScaleView, 0, 0); From b3aa991fb35633c3085d50a58fd033b18c085806 Mon Sep 17 00:00:00 2001 From: Grzegorz Orczykowski Date: Tue, 3 Sep 2024 20:36:05 +0200 Subject: [PATCH 7/7] Added comment headers --- .../java/org/odk/collect/googlemaps/scaleview/Drawer.java | 4 ++++ .../org/odk/collect/googlemaps/scaleview/MapScaleModel.java | 4 ++++ .../org/odk/collect/googlemaps/scaleview/MapScaleView.java | 4 ++++ .../main/java/org/odk/collect/googlemaps/scaleview/Scale.java | 4 ++++ .../java/org/odk/collect/googlemaps/scaleview/Scales.java | 4 ++++ .../java/org/odk/collect/googlemaps/scaleview/ViewConfig.java | 4 ++++ google-maps/src/main/res/values/attrs.xml | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java index 67116e0d9ed..80ad6d0c96a 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Drawer.java @@ -1,3 +1,7 @@ +/* + * This file includes code from MapScaleView (https://github.com/pengrad/MapScaleView), + * licensed under the Apache License, Version 2.0. + */ package org.odk.collect.googlemaps.scaleview; import android.graphics.Canvas; diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java index e424f03ed1f..d0590845c1d 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleModel.java @@ -1,3 +1,7 @@ +/* + * This file includes code from MapScaleView (https://github.com/pengrad/MapScaleView), + * licensed under the Apache License, Version 2.0. + */ package org.odk.collect.googlemaps.scaleview; class MapScaleModel { diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java index d6c949833cf..f2f0db3e648 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/MapScaleView.java @@ -1,3 +1,7 @@ +/* + * This file includes code from MapScaleView (https://github.com/pengrad/MapScaleView), + * licensed under the Apache License, Version 2.0. + */ package org.odk.collect.googlemaps.scaleview; import android.content.Context; diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java index f43b8215bd3..ead0b26ac3f 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scale.java @@ -1,3 +1,7 @@ +/* + * This file includes code from MapScaleView (https://github.com/pengrad/MapScaleView), + * licensed under the Apache License, Version 2.0. + */ package org.odk.collect.googlemaps.scaleview; class Scale { diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java index 5e607305684..b4f5f33bfe9 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/Scales.java @@ -1,3 +1,7 @@ +/* + * This file includes code from MapScaleView (https://github.com/pengrad/MapScaleView), + * licensed under the Apache License, Version 2.0. + */ package org.odk.collect.googlemaps.scaleview; import androidx.annotation.Nullable; diff --git a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java index f1eed952ccd..632ce4e908d 100644 --- a/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java +++ b/google-maps/src/main/java/org/odk/collect/googlemaps/scaleview/ViewConfig.java @@ -1,3 +1,7 @@ +/* + * This file includes code from MapScaleView (https://github.com/pengrad/MapScaleView), + * licensed under the Apache License, Version 2.0. + */ package org.odk.collect.googlemaps.scaleview; import android.content.Context; diff --git a/google-maps/src/main/res/values/attrs.xml b/google-maps/src/main/res/values/attrs.xml index 6bd87f0507a..ab9bdbb93ae 100644 --- a/google-maps/src/main/res/values/attrs.xml +++ b/google-maps/src/main/res/values/attrs.xml @@ -1,4 +1,8 @@ +