Skip to content

refactor(widgets): use android-maps-utils Units API in ScaleBar - #1072

Open
dkhawk wants to merge 1 commit into
feat/maps-utils-7.0.0from
feat/scale-bar-units
Open

dkhawk wants to merge 1 commit into
feat/maps-utils-7.0.0from
feat/scale-bar-units

Conversation

@dkhawk

@dkhawk dkhawk commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Note: Stacked on #1073.

Summary

  1. Adopt the android-maps-utils Units API in ScaleBar:

    • Replaces manual meter/foot/kilometer/mile string formatting in ScaleBar with MetricUnitsConverter, ImperialUnitsConverter(milesThreshold = 1.miles), NumberFormatSymbols.COMPACT, and Length.toDistanceString(...).
    • Uses a 1.miles (5,280 ft) threshold so the imperial scale bar transitions from feet to miles at 1 mile, matching the metric transition from meters to kilometers at 1 km (1,000 m).
  2. Fix ScaleBar ground distance calculation to measure at the center of the map viewport (measureAtCenter = true):

    • The problem: Previously, calculateDistance computed the ground distance between Point(0, 0) and Point(widthInPixels, 0) at the top-left corner of the map viewport, and only recomputed when cameraPositionState.position.zoom changed.
    • Why that matters (Web Mercator distortion): Google Maps uses a conformal Web Mercator projection (EPSG:3857), which stretches map scale isotropically in both latitude (North–South) and longitude (East–West) by a factor of 1 / cos(latitude) as you move toward the poles. Because of this stretching, the ground distance per pixel at the top corner of the screen can differ dramatically from the center of the screen where the user is looking.
      • For example, when the map is centered on Iceland (~65° N) at zoom = 3.15, the ScaleBar width at the center of the screen spans the full coast-to-coast width of Iceland (429 km / 267 mi). However, the top corner of the screen (Point(0, 0)) lies up in northern Greenland (~82° N), causing the top-corner calculation to report only 140 km / 87 mi (over 3x smaller than the scale at the center of the map).
    • The fix:
      • By default (measureAtCenter: Boolean = true), ScaleBar and DisappearingScaleBar now project cameraPositionState.position.target to screen coordinates and sample the horizontal pixel span across the center of the viewport (centerPoint.y).
      • Observes cameraPositionState.position, cameraPositionState.isMoving, and LocalConfiguration.current inside derivedStateOf so the scale bar updates accurately across latitude panning at constant zoom, map bearing rotation, camera tilt, and device orientation changes.
      • Adds measureAtCenter: Boolean = true to ScaleBar and DisappearingScaleBar so callers can preserve the legacy top-corner behavior (measureAtCenter = false) if desired.
  3. Interactive visual verification in ScaleBarActivity:

    • Draws three sets of crossed East–West (red) and North–South (blue) reference polylines at 10%, 50%, and 90% of the viewport height, where each line's ground length on Earth equals the exact distance reported by the ScaleBar.
    • You can see the Web Mercator stretching as you move north (stretching to a similar degree in both latitude and longitude, as intended for a conformal projection), while the center crosshairs match the exact width of the ScaleBar.
    • Includes a semi-transparent overlay box with checkboxes to toggle "Measure at center" (new center-target behavior vs. legacy top-corner behavior) and "Show crosshairs".

Testing

  • Connected Instrumentation Tests (:maps-app:connectedDebugAndroidTest & :maps-compose-widgets:connectedDebugAndroidTest):
    • ScaleBarTests: Verifies initial state, fractional kilometers (< 10 km), fractional miles (< 10 mi), latitude changes at constant zoom (Equator vs. Iceland), map bearing rotation (0°, 45°, 90°, 180°, 270°), in-place and full-Activity device rotation (Portrait <-> Landscape), and legacy top-corner measurement (measureAtCenter = false).
    • ScaleBarUnitTest: Verifies calculateDistance centered at target, legacy top-left fallback, and portrait-to-landscape viewport rotation.
  • Unit Tests (:maps-app:testDebugUnitTest):
    • ScaleBarRenderingTests: Verifies formatMetricDistance and formatImperialDistance across meters/kilometers and feet/miles thresholds.

@dkhawk dkhawk changed the title refactor(widgets): use android-maps-utils 7.0.0 Units API in ScaleBar feat!: raise minSdk to 24 and use android-maps-utils 7.0.0 Units API in ScaleBar Nov 3, 2026
@dkhawk
dkhawk force-pushed the feat/scale-bar-units branch from 61326dc to d1f772a Compare November 3, 2026 21:10
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Coverage (unit tests)

Compared against 53c9725 on main (#1067).

Module Line % Change Branch % Change
maps-compose 15.28% no change 3.76% no change
maps-compose-utils 6.09% no change 4.47% no change
maps-compose-widgets 3.85% 🔻 -7.03 0.00% 🔻 -11.11
TOTAL 12.59% 🔻 -0.40 3.77% 🔻 -0.35

Line and branch coverage from unit test reports. History is recorded on the coverage-history branch after each merge to main.

@dkhawk
dkhawk force-pushed the feat/scale-bar-units branch from d1f772a to 4b87319 Compare November 3, 2026 21:39
@dkhawk dkhawk changed the title feat!: raise minSdk to 24 and use android-maps-utils 7.0.0 Units API in ScaleBar refactor(widgets): use android-maps-utils 7.0.0 Units API in ScaleBar Nov 3, 2026
@dkhawk
dkhawk changed the base branch from main to feat/maps-utils-7.0.0 November 3, 2026 21:42
@dkhawk dkhawk changed the title refactor(widgets): use android-maps-utils 7.0.0 Units API in ScaleBar refactor(widgets): use android-maps-utils Units API in ScaleBar Nov 3, 2026
@dkhawk
dkhawk force-pushed the feat/scale-bar-units branch from 4b87319 to 12eb5aa Compare November 3, 2026 21:52
@dkhawk
dkhawk added this pull request to stack #1074 November 3, 2026 21:52
@dkhawk
dkhawk marked this pull request as ready for review November 3, 2026 21:53
@dkhawk
dkhawk requested a review from kikoso November 3, 2026 21:54
Adopt the Units API from android-maps-utils (MetricUnitsConverter, ImperialUnitsConverter with a 1-mile threshold, NumberFormatSymbols.COMPACT, and toDistanceString) for formatting metric and imperial distances in ScaleBar.

Also fix a pre-existing accuracy bug in ScaleBar where the ground distance was previously calculated between Point(0, 0) and Point(widthInPixels, 0) at the top-left corner of the viewport and only recomputed when cameraPositionState.position.zoom changed:

- Because Google Maps uses a conformal Web Mercator projection (EPSG:3857), map scale stretches isotropically in both latitude (North-South) and longitude (East-West) by a factor of 1 / cos(latitude) as you move toward the poles. Measuring at the top corner of the screen rather than the center of the map caused the scale bar to reflect the scale at the top edge of the viewport instead of where the camera is focused (for example, when centered on Iceland at zoom 3.15, the top edge of the screen lies in northern Greenland near 82N, causing the top-corner calculation to report 140 km / 87 mi instead of the 429 km / 267 mi width at Iceland).

- Measure the scale across the camera target (cameraPositionState.position.target) at the center of the viewport by default, and observe cameraPositionState.position, cameraPositionState.isMoving, and LocalConfiguration.current so the scale bar stays accurate across latitude panning, map bearing rotation, camera tilt, and device orientation changes.

- Add a measureAtCenter: Boolean = true parameter to ScaleBar and DisappearingScaleBar so callers can preserve the legacy top-corner behavior (measureAtCenter = false) if desired.

- Update ScaleBarActivity with three sets of East-West and North-South reference crosshairs (at 10%, 50%, and 90% of the viewport height) whose ground lengths match the ScaleBar distance, plus UI checkboxes to toggle 'Measure at center' and 'Show crosshairs'.

- Add unit and instrumentation tests covering Units API formatting, latitude changes at constant zoom, map bearing rotation, device rotation, and legacy top-corner measurement.
@dkhawk
dkhawk force-pushed the feat/scale-bar-units branch from 12eb5aa to ae29c41 Compare November 4, 2026 00:02
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Checkbox
import androidx.compose.material.Checkbox
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants