@@ -560,6 +560,44 @@ class MapController(
560560 )
561561 }
562562
563+ /* *
564+ * Calculates the appropriate zoom level to fit bounds within the specified viewport dimensions.
565+ *
566+ * Iterates from maximum zoom down to minimum zoom, finding the largest zoom level
567+ * where the bounds fit entirely within the viewport.
568+ *
569+ * @param bounds The geographic bounds to fit
570+ * @param viewWidth The viewport width in pixels
571+ * @param viewHeight The viewport height in pixels
572+ * @return The calculated zoom level (between 2.0 and 19.0)
573+ */
574+ private fun calculateZoomForBounds (
575+ bounds : LatLngBounds ,
576+ viewWidth : Int ,
577+ viewHeight : Int ,
578+ ): Double {
579+ // Ensure we have valid viewport dimensions
580+ if (viewWidth <= 0 || viewHeight <= 0 ) {
581+ return DEFAULT_MIN_ZOOM
582+ }
583+
584+ // Iterate from max zoom down to find the largest zoom that fits
585+ for (zoom in 19 downTo 2 ) {
586+ val (swX, swY) = ProjectionUtils .latLngToPixel(bounds.southwest, zoom)
587+ val (neX, neY) = ProjectionUtils .latLngToPixel(bounds.northeast, zoom)
588+
589+ val boundsWidth = kotlin.math.abs(neX - swX)
590+ val boundsHeight = kotlin.math.abs(swY - neY) // Y increases downward
591+
592+ if (boundsWidth <= viewWidth && boundsHeight <= viewHeight) {
593+ return zoom.toDouble()
594+ }
595+ }
596+
597+ // Fallback to minimum zoom
598+ return DEFAULT_MIN_ZOOM
599+ }
600+
563601 private fun calculateTargetPosition (
564602 cameraUpdate : CameraUpdate ,
565603 currentPosition : CameraPosition ,
@@ -599,6 +637,50 @@ class MapController(
599637 target = currentPosition.target,
600638 zoom = (currentPosition.zoom + cameraUpdate.amount).coerceIn(minZoomPreference, maxZoomPreference),
601639 )
640+ is CameraUpdate .ScrollBy -> {
641+ // Convert current center to pixel coordinates
642+ val (centerPixelX, centerPixelY) =
643+ ProjectionUtils .latLngToPixel(
644+ currentPosition.target,
645+ currentPosition.zoom.toInt(),
646+ )
647+
648+ // Apply pixel scroll offset
649+ val newPixelX = (centerPixelX + cameraUpdate.xPixels).toInt()
650+ val newPixelY = (centerPixelY + cameraUpdate.yPixels).toInt()
651+
652+ // Convert back to LatLng
653+ val newTarget = ProjectionUtils .pixelToLatLng(newPixelX, newPixelY, currentPosition.zoom.toInt())
654+
655+ CameraPosition (
656+ target = newTarget,
657+ zoom = currentPosition.zoom,
658+ )
659+ }
660+ is CameraUpdate .NewLatLngBounds -> {
661+ val zoom =
662+ calculateZoomForBounds(
663+ bounds = cameraUpdate.bounds,
664+ viewWidth = viewWidth - cameraUpdate.padding * 2 ,
665+ viewHeight = viewHeight - cameraUpdate.padding * 2 ,
666+ )
667+ CameraPosition (
668+ target = cameraUpdate.bounds.getCenter(),
669+ zoom = zoom.coerceIn(minZoomPreference, maxZoomPreference),
670+ )
671+ }
672+ is CameraUpdate .NewLatLngBoundsWithSize -> {
673+ val zoom =
674+ calculateZoomForBounds(
675+ bounds = cameraUpdate.bounds,
676+ viewWidth = cameraUpdate.width - cameraUpdate.padding * 2 ,
677+ viewHeight = cameraUpdate.height - cameraUpdate.padding * 2 ,
678+ )
679+ CameraPosition (
680+ target = cameraUpdate.bounds.getCenter(),
681+ zoom = zoom.coerceIn(minZoomPreference, maxZoomPreference),
682+ )
683+ }
602684 }
603685
604686 private fun interpolate (
0 commit comments