diff --git a/api/android-for-maplibre.api b/api/android-for-maplibre.api index 6f94dc5..a0ef0f6 100644 --- a/api/android-for-maplibre.api +++ b/api/android-for-maplibre.api @@ -10,19 +10,27 @@ public final class com.mapconductor.maplibre.GeoRectBoundsKt { public static final com.mapconductor.core.features.GeoRectBounds toGeoRectBounds(org.maplibre.android.geometry.LatLngBounds); } public final class com.mapconductor.maplibre.MapCameraPositionKt { + public static final double MAPLIBRE_VERTICAL_FOV; + public static final double[] toMaplibrePadding(com.mapconductor.core.map.MapPaddingsInterface, float); + public static final com.mapconductor.core.map.MapPaddings toMapPaddings(double[], float); public static final org.maplibre.android.camera.CameraPosition toCameraPosition(com.mapconductor.core.map.MapCameraPosition); + public static final org.maplibre.android.camera.CameraPosition toCameraPosition(com.mapconductor.core.map.MapCameraPosition, java.lang.Float); public static final com.mapconductor.core.map.MapCameraPosition from(com.mapconductor.core.map.MapCameraPosition$Companion, com.mapconductor.core.map.MapCameraPositionInterface); public static final com.mapconductor.core.map.MapCameraPosition toMapCameraPosition(org.maplibre.android.camera.CameraPosition); public static final com.mapconductor.core.map.MapCameraPosition toMapCameraPosition(org.maplibre.android.camera.CameraPosition, java.lang.Double); + public static final com.mapconductor.core.map.MapCameraPosition toMapCameraPosition(org.maplibre.android.camera.CameraPosition, java.lang.Double, java.lang.Float); } public final class com.mapconductor.maplibre.MapLibreCameraStateSnapshot { - public com.mapconductor.maplibre.MapLibreCameraStateSnapshot(org.maplibre.android.camera.CameraPosition, java.lang.Double); + public com.mapconductor.maplibre.MapLibreCameraStateSnapshot(org.maplibre.android.camera.CameraPosition, java.lang.Double, java.lang.Float); + public com.mapconductor.maplibre.MapLibreCameraStateSnapshot(org.maplibre.android.camera.CameraPosition, java.lang.Double, java.lang.Float, int, kotlin.jvm.internal.DefaultConstructorMarker); public final org.maplibre.android.camera.CameraPosition getCameraPosition(); public final java.lang.Double getLogicalTiltHint(); + public final java.lang.Float getDensity(); public final com.mapconductor.core.map.MapCameraPosition toMapCameraPosition(); public final org.maplibre.android.camera.CameraPosition component1(); public final java.lang.Double component2(); - public final com.mapconductor.maplibre.MapLibreCameraStateSnapshot copy(org.maplibre.android.camera.CameraPosition, java.lang.Double); + public final java.lang.Float component3(); + public final com.mapconductor.maplibre.MapLibreCameraStateSnapshot copy(org.maplibre.android.camera.CameraPosition, java.lang.Double, java.lang.Float); public java.lang.String toString(); public int hashCode(); public boolean equals(java.lang.Object); @@ -164,6 +172,7 @@ public final class com.mapconductor.maplibre.MapLibreViewController extends com. } public final class com.mapconductor.maplibre.MapLibreViewControllerCameraKt { public static final com.mapconductor.core.map.MapCameraPosition readLogicalCameraPosition(com.mapconductor.maplibre.MapLibreViewController); + public static final float density(com.mapconductor.maplibre.MapLibreViewController); public static final com.mapconductor.core.map.MapCameraPosition getMapCameraPosition(com.mapconductor.maplibre.MapLibreViewController, com.mapconductor.core.map.MapCameraPositionInterface); public static final void handleMoveCamera(com.mapconductor.maplibre.MapLibreViewController, com.mapconductor.core.map.MapCameraPosition); public static final void handleAnimateCamera(com.mapconductor.maplibre.MapLibreViewController, com.mapconductor.core.map.MapCameraPosition, long); diff --git a/src/main/java/com/mapconductor/maplibre/MapCameraPosition.kt b/src/main/java/com/mapconductor/maplibre/MapCameraPosition.kt index 84c1d99..d620658 100644 --- a/src/main/java/com/mapconductor/maplibre/MapCameraPosition.kt +++ b/src/main/java/com/mapconductor/maplibre/MapCameraPosition.kt @@ -4,6 +4,8 @@ import com.mapconductor.core.features.GeoPoint import com.mapconductor.core.map.CameraBearing import com.mapconductor.core.map.MapCameraPosition import com.mapconductor.core.map.MapCameraPositionInterface +import com.mapconductor.core.map.MapPaddings +import com.mapconductor.core.map.MapPaddingsInterface import com.mapconductor.core.spherical.Spherical import com.mapconductor.maplibre.zoom.ZoomAltitudeConverter import org.maplibre.android.camera.CameraPosition @@ -12,10 +14,40 @@ import kotlin.math.cos import kotlin.math.tan private val converter = ZoomAltitudeConverter() + +/** + * MapLibre のカメラの縦方向の視野角(度)。MapLibre Native の既定値で、地図の大きさに + * よらず一定(焦点距離 = 地図の高さ × 1.5)。TB520FU の縦・横で 36.9° を実測。 + */ +internal const val MAPLIBRE_VERTICAL_FOV = 36.87 + +/** dp のパディング → MapLibre の px のパディング(left, top, right, bottom)。 */ +internal fun MapPaddingsInterface.toMaplibrePadding(density: Float): DoubleArray = + doubleArrayOf(left * density, top * density, right * density, bottom * density) + +/** MapLibre の px のパディング(left, top, right, bottom)→ dp のパディング。 */ +internal fun DoubleArray.toMapPaddings(density: Float): MapPaddings? { + if (size < 4 || density <= 0f) return null + return MapPaddings( + top = this[1] / density, + left = this[0] / density, + bottom = this[3] / density, + right = this[2] / density, + ) +} + private const val NEGATIVE_TILT_TARGET_DISTANCE_SCALE = 1.83 private const val NEGATIVE_TILT_ZOOM_OFFSET_AT_MAX_TILT = -0.9 -fun MapCameraPosition.toCameraPosition(): CameraPosition { +fun MapCameraPosition.toCameraPosition(): CameraPosition = toCameraPosition(density = null) + +/** + * [density] があれば [MapCameraPosition.paddings] を MapLibre のパディングとして渡す。 + * MapLibre はパディングを除いた領域の中央にカメラの中心を置き、tilt の遠近もエンジンが解く。 + * MapLibre のパディングは次に変えるまで残るので、null(未指定)のときは渡さない。 + */ +internal fun MapCameraPosition.toCameraPosition(density: Float?): CameraPosition { + val padding = if (density != null) paddings?.toMaplibrePadding(density) else null if (tilt >= 0) { return CameraPosition .Builder() @@ -23,8 +55,7 @@ fun MapCameraPosition.toCameraPosition(): CameraPosition { .zoom(ZoomAltitudeConverter.googleZoomToMaplibreZoom(zoom)) .tilt(tilt) .bearing(CameraBearing.toNativeHeading(bearing)) - // TODO: -// .padding(paddings?.toEdgeInsects()) + .apply { padding?.let { padding(it) } } .build() } else { // tilt < 0: MapLibre cannot represent an upward pitch directly. @@ -47,8 +78,7 @@ fun MapCameraPosition.toCameraPosition(): CameraPosition { .zoom(ZoomAltitudeConverter.googleZoomToMaplibreZoom(adjustedZoom)) .tilt(tiltAbsDeg) .bearing(CameraBearing.toNativeHeading(bearing)) - // TODO: -// .padding(paddings?.toEdgeInsects()) + .apply { padding?.let { padding(it) } } .build() } } @@ -62,7 +92,9 @@ fun MapCameraPosition.Companion.from(cameraPosition: MapCameraPositionInterface) zoom = cameraPosition.zoom, bearing = cameraPosition.bearing, tilt = cameraPosition.tilt, + paddings = cameraPosition.paddings, visibleRegion = cameraPosition.visibleRegion, + fov = cameraPosition.fov, ) } @@ -71,11 +103,20 @@ fun CameraPosition.toMapCameraPosition() = toMapCameraPosition(logicalTiltHint = internal data class MapLibreCameraStateSnapshot( val cameraPosition: CameraPosition, val logicalTiltHint: Double?, + val density: Float? = null, ) { - fun toMapCameraPosition(): MapCameraPosition = cameraPosition.toMapCameraPosition(logicalTiltHint) + fun toMapCameraPosition(): MapCameraPosition = cameraPosition.toMapCameraPosition(logicalTiltHint, density) } -internal fun CameraPosition.toMapCameraPosition(logicalTiltHint: Double?): MapCameraPosition { +internal fun CameraPosition.toMapCameraPosition(logicalTiltHint: Double?): MapCameraPosition = + toMapCameraPosition(logicalTiltHint, density = null) + +/** [density] があれば MapLibre のパディングを dp に直して paddings として返す。 */ +internal fun CameraPosition.toMapCameraPosition( + logicalTiltHint: Double?, + density: Float?, +): MapCameraPosition { + val paddings = density?.let { d -> padding?.toMapPaddings(d) } ?: MapPaddings.Zeros val pitch = tilt val pitchAbsDeg = abs(pitch).coerceIn(0.0, 60.0) @@ -85,7 +126,9 @@ internal fun CameraPosition.toMapCameraPosition(logicalTiltHint: Double?): MapCa zoom = ZoomAltitudeConverter.maplibreZoomToGoogleZoom(zoom), bearing = CameraBearing.bearingFromNativeHeading(bearing), tilt = pitch, + paddings = paddings, visibleRegion = null, + fov = MAPLIBRE_VERTICAL_FOV, ) } @@ -107,6 +150,8 @@ internal fun CameraPosition.toMapCameraPosition(logicalTiltHint: Double?): MapCa zoom = originalGoogleZoom, bearing = CameraBearing.bearingFromNativeHeading(bear), tilt = -pitchAbsDeg, + paddings = paddings, visibleRegion = null, + fov = MAPLIBRE_VERTICAL_FOV, ) } diff --git a/src/main/java/com/mapconductor/maplibre/MapLibreMapView.kt b/src/main/java/com/mapconductor/maplibre/MapLibreMapView.kt index 763fb6b..258ad7d 100644 --- a/src/main/java/com/mapconductor/maplibre/MapLibreMapView.kt +++ b/src/main/java/com/mapconductor/maplibre/MapLibreMapView.kt @@ -90,7 +90,7 @@ fun MapLibreMapView( modifier = modifier, viewProvider = { val cameraPosition = - state.cameraPosition.toCameraPosition() + state.cameraPosition.toCameraPosition(context.resources.displayMetrics.density) val mapInitOptions = MapLibreMapOptions .createFromAttributes(context) diff --git a/src/main/java/com/mapconductor/maplibre/MapLibreViewControllerCamera.kt b/src/main/java/com/mapconductor/maplibre/MapLibreViewControllerCamera.kt index a1bc241..c9be20b 100644 --- a/src/main/java/com/mapconductor/maplibre/MapLibreViewControllerCamera.kt +++ b/src/main/java/com/mapconductor/maplibre/MapLibreViewControllerCamera.kt @@ -20,8 +20,11 @@ internal fun MapLibreViewController.readLogicalCameraPosition(): MapCameraPositi MapLibreCameraStateSnapshot( cameraPosition = holder.map.cameraPosition, logicalTiltHint = lastLogicalCameraPosition?.tilt, + density = density(), ).toMapCameraPosition() +internal fun MapLibreViewController.density(): Float = holder.mapView.resources.displayMetrics.density + internal fun MapLibreViewController.getMapCameraPosition(camera: MapCameraPositionInterface): MapCameraPosition? { // 4 隅の逆投影は全プロバイダ共通なのでコアの buildVisibleRegion を使う。 val visibleRegion = holder.buildVisibleRegion() ?: return null @@ -31,7 +34,7 @@ internal fun MapLibreViewController.getMapCameraPosition(camera: MapCameraPositi internal fun MapLibreViewController.handleMoveCamera(position: MapCameraPosition) { lastLogicalCameraPosition = position mainCoroutine.launch { - val cameraPos = position.toCameraPosition() + val cameraPos = position.toCameraPosition(density()) val cameraUpdate = CameraUpdateFactory .newCameraPosition(cameraPos) @@ -46,7 +49,7 @@ internal fun MapLibreViewController.handleAnimateCamera( ) { lastLogicalCameraPosition = position mainCoroutine.launch { - val cameraPos = position.toCameraPosition() + val cameraPos = position.toCameraPosition(density()) val cameraUpdate = CameraUpdateFactory .newCameraPosition(cameraPos)