From f6a532eaa949a28569f5bb1412da6dcfe5697029 Mon Sep 17 00:00:00 2001 From: DaVinci9196 Date: Thu, 6 Aug 2026 10:26:15 +0800 Subject: [PATCH 1/4] fix: improve HMS Maps location and coordinate handling - add fused location updates to the HMS Maps backend - convert coordinates between WGS-84 and GCJ-02 - improve map initialization and loaded callback handling --- .../microg/gms/utils/CoordinateConverter.java | 125 ++++++++++++++++++ play-services-maps/core/hms/build.gradle | 1 + .../org/microg/gms/maps/hms/GoogleMap.kt | 124 ++++++++++++++--- .../gms/maps/hms/utils/typeConverter.kt | 8 +- 4 files changed, 241 insertions(+), 17 deletions(-) create mode 100644 play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java diff --git a/play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java b/play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java new file mode 100644 index 0000000000..9aa4cd243c --- /dev/null +++ b/play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java @@ -0,0 +1,125 @@ +/** + * SPDX-FileCopyrightText: 2026 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.utils; + +/** + * Conversion between the internationally accepted geographic coordinate system (WGS-84) + * and the geographic coordinate system used in China (GCJ-02) + */ +public class CoordinateConverter { + private static final double A = 6378245.0; + private static final double EE = 0.006693421622965943; + private static final double PI = Math.PI; + private static final double EPSILON = 1e-6; + private static final int MAX_ITERATIONS = 10; + private static final double PI_OVER_180 = PI / 180.0; + private static final double A_1_EE = A * (1 - EE); + + // ---------- WGS-84 to GCJ-02 ---------- + + /** + * Convert WGS-84 coordinates to GCJ-02 coordinates + * + * @param wgsLat latitude + * @param wgsLon longitude + * @return GCJ-02 coordinate array, index 0 is latitude, 1 is longitude + */ + public static double[] wgs84ToGcj02(double wgsLat, double wgsLon) { + if (isOutOfChina(wgsLat, wgsLon)) { + return new double[]{wgsLat, wgsLon}; + } + + double dLat = transformLat(wgsLon - 105.0, wgsLat - 35.0); + double dLon = transformLon(wgsLon - 105.0, wgsLat - 35.0); + double radLat = wgsLat * PI_OVER_180; + double magic = Math.sin(radLat); + magic = 1 - EE * magic * magic; + double sqrtMagic = Math.sqrt(magic); + + dLat = (dLat * 180.0) / (A_1_EE / (magic * sqrtMagic) * PI); + dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI); + + return new double[]{wgsLat + dLat, wgsLon + dLon}; + } + + // ---------- GCJ-02 to WGS-84 ---------- + + /** + * Convert GCJ-02 coordinates to WGS-84 coordinates (iterative approximation method) + * + * @param gcjLat latitude + * @param gcjLon longitude + * @return WGS-84 coordinate array, index 0 is latitude, 1 is longitude + */ + public static double[] gcj02ToWgs84(double gcjLat, double gcjLon) { + if (isOutOfChina(gcjLat, gcjLon)) { + return new double[]{gcjLat, gcjLon}; + } + + double[] result = {gcjLat, gcjLon}; + double[] delta = new double[2]; + int iteration = 0; + + while (iteration++ < MAX_ITERATIONS) { + double[] gcjGuess = wgs84ToGcj02(result[0], result[1]); + delta[0] = gcjLat - gcjGuess[0]; + delta[1] = gcjLon - gcjGuess[1]; + + double h = 1e-4; + double[] gradLat = gradient(result[0], result[1], h, 0); + double[] gradLon = gradient(result[0], result[1], h, 1); + + double det = gradLat[0] * gradLon[1] - gradLat[1] * gradLon[0]; + if (Math.abs(det) < 1e-12) break; + + double stepLat = (delta[0] * gradLon[1] - delta[1] * gradLat[1]) / det; + double stepLon = (delta[1] * gradLat[0] - delta[0] * gradLon[0]) / det; + + result[0] += stepLat; + result[1] += stepLon; + + if (Math.abs(stepLat) < EPSILON && Math.abs(stepLon) < EPSILON) { + break; + } + } + return result; + } + + // numerical differentiation to compute gradients + private static double[] gradient(double lat, double lon, double h, int axis) { + double[] base = wgs84ToGcj02(lat, lon); + double[] delta; + if (axis == 0) { + delta = wgs84ToGcj02(lat + h, lon); + } else { + delta = wgs84ToGcj02(lat, lon + h); + } + return new double[]{(delta[0] - base[0]) / h, (delta[1] - base[1]) / h}; + } + + // determine whether the coordinates are outside China + private static boolean isOutOfChina(double lat, double lon) { + return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271; + } + + // latitude conversion formula + private static double transformLat(double x, double y) { + double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0; + ret += (160.0 * Math.sin(y / 12.0 * PI) + 320.0 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0; + return ret; + } + + // longitude conversion formula + private static double transformLon(double x, double y) { + double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); + ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; + ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0; + ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0; + return ret; + } +} diff --git a/play-services-maps/core/hms/build.gradle b/play-services-maps/core/hms/build.gradle index 9ca2082e01..396b46de6e 100644 --- a/play-services-maps/core/hms/build.gradle +++ b/play-services-maps/core/hms/build.gradle @@ -9,6 +9,7 @@ apply plugin: 'kotlin-android' dependencies { implementation project(':play-services-base-core') implementation project(':play-services-maps') + implementation project(':play-services-location') implementation 'com.huawei.hms:maps:6.9.0.300' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt index 486aaa0ef2..ac82a61316 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt @@ -5,7 +5,9 @@ package org.microg.gms.maps.hms +import android.Manifest import android.content.Context +import android.content.pm.PackageManager import android.graphics.Bitmap import android.location.Location import android.os.* @@ -21,9 +23,12 @@ import android.widget.RelativeLayout import androidx.annotation.IdRes import androidx.annotation.Keep import androidx.collection.LongSparseArray +import androidx.core.app.ActivityCompat import com.google.android.gms.dynamic.IObjectWrapper import com.google.android.gms.dynamic.ObjectWrapper import com.google.android.gms.dynamic.unwrap +import com.google.android.gms.location.LocationListener +import com.google.android.gms.location.LocationServices import com.google.android.gms.maps.GoogleMap.MAP_TYPE_TERRAIN import com.google.android.gms.maps.GoogleMapOptions import com.google.android.gms.maps.internal.* @@ -31,6 +36,7 @@ import com.google.android.gms.maps.model.* import com.google.android.gms.maps.model.internal.* import com.huawei.hms.maps.CameraUpdate import com.huawei.hms.maps.HuaweiMap +import com.huawei.hms.maps.LocationSource import com.huawei.hms.maps.MapView import com.huawei.hms.maps.MapsInitializer import com.huawei.hms.maps.OnMapReadyCallback @@ -42,8 +48,11 @@ import com.huawei.hms.maps.internal.IOnPoiClickListener import com.huawei.hms.maps.model.Marker import org.microg.gms.maps.hms.model.* import org.microg.gms.maps.hms.utils.* +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.Priority import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean +import com.google.android.gms.maps.model.LatLng private fun LongSparseArray.values() = (0 until size()).mapNotNull { valueAt(it) } @@ -102,6 +111,36 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private var projectionImpl: ProjectionImpl? = null private var inDeveloperAnimation = false + private var locationEnabled: Boolean = false + private var isAddLocationCallback: Boolean = false + private var lastLocation: Location? = null + private var myLocationChangeListener: IOnMyLocationChangeListener? = null + + private val locationService by lazy { LocationServices.getFusedLocationProviderClient(context) } + private val locationCallback = LocationListener { location -> + lastLocation = location + try { + myLocationChangeListener?.onMyLocationChanged(ObjectWrapper.wrap(location)) + } catch (e: RemoteException) { + Log.w(TAG, "Failed to notify my-location listener", e) + } + val gcj02Location = Location(location).apply { + val hmsLatLng = LatLng(location.latitude, location.longitude).toHms() + latitude = hmsLatLng.latitude + longitude = hmsLatLng.longitude + } + mLocationChangedListener?.onLocationChanged(gcj02Location) + } + private var mLocationChangedListener: LocationSource.OnLocationChangedListener? = null + private var hwLocationSource: LocationSource = object : LocationSource { + override fun activate(listener: LocationSource.OnLocationChangedListener) { + mLocationChangedListener = listener + } + override fun deactivate() { + mLocationChangedListener = null + } + } + init { BitmapDescriptorFactoryImpl.initialize(context.resources) runOnMainLooper { @@ -340,7 +379,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun addMarker(options: MarkerOptions): IMarkerDelegate { val marker = MarkerImpl(this, "m${markerId++}", options) - if (map != null) { + if (map != null && initialized) { marker.update() } else { markers[marker.id] = marker @@ -425,16 +464,53 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setMyLocationEnabled(myLocation: Boolean) = afterInitialize { Log.d(TAG, "setMyLocationEnabled $myLocation") - it.isMyLocationEnabled = myLocation + synchronized(mapLock) { + locationEnabled = myLocation + try { + setLocationSource(null) + } catch (e: Exception) { + Log.w(TAG, e) + locationEnabled = false + } finally { + it.isMyLocationEnabled = locationEnabled + } + } } - override fun getMyLocation(): Location? { - Log.d(TAG, "deprecated Method: getMyLocation") - return null - } + override fun getMyLocation(): Location? = lastLocation override fun setLocationSource(locationSource: ILocationSourceDelegate?) = afterInitialize { - Log.d(TAG, "unimplemented Method: setLocationSource") + synchronized(mapLock) { + it.setLocationSource(hwLocationSource) + updateLocationEngineListener(locationEnabled) + } + } + + private fun updateLocationEngineListener(myLocation: Boolean) { + if (ActivityCompat.checkSelfPermission( + context, Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || ActivityCompat.checkSelfPermission( + context, Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + if (myLocation) { + if (!isAddLocationCallback) { + isAddLocationCallback = true + locationService.requestLocationUpdates( + LocationRequest.Builder(DEFAULT_LOCATION_INTERVAL_MILLIS) + .setPriority(Priority.PRIORITY_HIGH_ACCURACY) + .setMinUpdateIntervalMillis(DEFAULT_LOCATION_INTERVAL_MILLIS) + .setMaxUpdateDelayMillis(DEFAULT_LOCATION_INTERVAL_MILLIS) + .build(), locationCallback, Looper.getMainLooper() + ) + } + } else { + if (isAddLocationCallback) { + isAddLocationCallback = false + locationService.removeLocationUpdates(locationCallback) + } + } + } } override fun setContentDescription(desc: String?) = afterInitialize { @@ -507,7 +583,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setOnMarkerClickListener(listener: IOnMarkerClickListener?) = afterInitialize { hmap -> hmap.setOnMarkerClickListener { - Log.d("GmsGoogleMap", "setOnMarkerClickListener marker id -> ${it.id}") + Log.d(TAG, "setOnMarkerClickListener marker id -> ${it.id}") listener?.onMarkerClick(markers[it.id]) ?: false } } @@ -557,6 +633,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun setOnMyLocationChangeListener(listener: IOnMyLocationChangeListener?) = afterInitialize { Log.d(TAG, "deprecated Method: setOnMyLocationChangeListener") + myLocationChangeListener = listener } override fun setOnMyLocationButtonClickListener(listener: IOnMyLocationButtonClickListener?) = afterInitialize { @@ -630,11 +707,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) synchronized(mapLock) { if (loaded) { Log.d(TAG, "Invoking callback instantly, as map is loaded") - try { - scheduleExecute { callback.onMapLoaded() } - } catch (e: Exception) { - Log.w(TAG, e) - } + callback.scheduleExecute() } else { Log.d(TAG, "Delay callback invocation, as map is not yet loaded") loadedCallback = callback @@ -847,8 +920,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } internalOnInitializedCallbackList.clear() fakeWatermark { Log.d(TAG_LOGO, "fakeWatermark success") } - scheduleExecute { loadedCallback?.onMapLoaded() } - + loadedCallback?.scheduleExecute() mapView?.visibility = View.VISIBLE } @@ -862,6 +934,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) override fun onPause() = mapView?.onPause() ?: Unit override fun onDestroy() { Log.d(TAG, "onDestroy") + locationService.removeLocationUpdates(locationCallback) initializedCallbackList.clear() internalOnInitializedCallbackList.clear() circles.map { it.value.remove() } @@ -877,6 +950,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) // TODO can crash? mapView?.onDestroy() mapView = null + mLocationChangedListener = null // Don't make it null; this object is not deleted immediately, and it may want to access map.* stuff //map = null @@ -884,6 +958,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) created = false initialized = false loaded = false + isAddLocationCallback = false } override fun onStart() { @@ -929,6 +1004,16 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } } + private fun IOnMapLoadedCallback.scheduleExecute() { + Handler(Looper.getMainLooper()).postDelayed({ + try { + this.onMapLoaded() + } catch (e: Exception) { + Log.w(TAG, e) + } + }, ON_MAP_LOADED_CALLBACK_DELAY) + } + private var isInvokingInitializedCallbacks = AtomicBoolean(false) private fun tryRunUserInitializedCallbacks(tag: String = "") { @@ -958,6 +1043,13 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) scheduleExecute { runCallbacks() } } if (!wasCallbackActive) isInvokingInitializedCallbacks.set(false) + } else if (mapView?.isShown == false) { + // Match the Mapbox backend: let applications configure the map while the + // backend is still initializing. Camera updates and other operations that + // require HuaweiMap are queued by their existing initialization guards. + runOnMainLooper(forceQueue = true) { + runCallbacks() + } } else { Log.d( "$TAG:$tag", @@ -987,5 +1079,7 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) private const val TAG_LOGO = "fakeWatermark" private const val ON_MAP_CALLBACK_DELAY = 300L + private const val ON_MAP_LOADED_CALLBACK_DELAY = 500L + private const val DEFAULT_LOCATION_INTERVAL_MILLIS = 1000L } } diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt index dce21f082e..ab0d3793c2 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt @@ -14,6 +14,7 @@ import com.huawei.hms.maps.HuaweiMap import com.huawei.hms.maps.HuaweiMapOptions import com.huawei.hms.maps.model.* import org.microg.gms.maps.hms.R +import org.microg.gms.utils.CoordinateConverter import com.google.android.gms.maps.model.CameraPosition as GmsCameraPosition import com.google.android.gms.maps.model.CircleOptions as GmsCircleOptions import com.google.android.gms.maps.model.Dash as GmsDash @@ -79,7 +80,8 @@ fun GoogleMapOptions.toHms(): HuaweiMapOptions { } fun GmsLatLng.toHms(): LatLng = - LatLng(latitude, longitude) + runCatching { CoordinateConverter.wgs84ToGcj02(latitude, longitude) } + .getOrNull()?.let { LatLng(it[0], it[1]) } ?: LatLng(latitude, longitude) fun GmsLatLngBounds.toHms(): LatLngBounds = LatLngBounds( @@ -196,7 +198,9 @@ fun PatternItem.toGms(): GmsPatternItem = when (this) { else -> GmsGap(0f) } -fun LatLng.toGms(): GmsLatLng = GmsLatLng(latitude, longitude) +fun LatLng.toGms(): GmsLatLng = + runCatching { CoordinateConverter.gcj02ToWgs84(latitude, longitude) } + .getOrNull()?.let { GmsLatLng(it[0], it[1]) } ?: GmsLatLng(latitude, longitude) fun LatLngBounds.toGms(): GmsLatLngBounds = GmsLatLngBounds( GmsLatLng(southwest.latitude, southwest.longitude), From cb606773d1952b2044536a0194623ba5a297bd79 Mon Sep 17 00:00:00 2001 From: DaVinci9196 Date: Fri, 7 Aug 2026 17:24:23 +0800 Subject: [PATCH 2/4] fix: synchronize HMS Maps ready callbacks with initialization --- .../kotlin/org/microg/gms/maps/hms/GoogleMap.kt | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt index ac82a61316..2fb0aca73c 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt @@ -1040,16 +1040,9 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) Log.d("$TAG:$tag", "Invoking callback now, as map is initialized") val wasCallbackActive = isInvokingInitializedCallbacks.getAndSet(true) runOnMainLooper(forceQueue = wasCallbackActive) { - scheduleExecute { runCallbacks() } - } - if (!wasCallbackActive) isInvokingInitializedCallbacks.set(false) - } else if (mapView?.isShown == false) { - // Match the Mapbox backend: let applications configure the map while the - // backend is still initializing. Camera updates and other operations that - // require HuaweiMap are queued by their existing initialization guards. - runOnMainLooper(forceQueue = true) { runCallbacks() } + if (!wasCallbackActive) isInvokingInitializedCallbacks.set(false) } else { Log.d( "$TAG:$tag", @@ -1067,18 +1060,11 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } - private fun scheduleExecute(block:() -> Unit) { - Handler(Looper.getMainLooper()).postDelayed({ - try { block.invoke() } catch (_: Exception) {} - }, ON_MAP_CALLBACK_DELAY) - } - companion object { private const val TAG = "GmsGoogleMap" private const val SNAPSHOT_OLD_VERSION_CODE = 4000000 private const val TAG_LOGO = "fakeWatermark" - private const val ON_MAP_CALLBACK_DELAY = 300L private const val ON_MAP_LOADED_CALLBACK_DELAY = 500L private const val DEFAULT_LOCATION_INTERVAL_MILLIS = 1000L } From 2a07c80bf8f649b559d1dca90d5fe995b7043bf9 Mon Sep 17 00:00:00 2001 From: DaVinci9196 Date: Tue, 25 Aug 2026 19:10:34 +0800 Subject: [PATCH 3/4] normalize HMS default camera origin --- .../kotlin/org/microg/gms/maps/hms/GoogleMap.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt index 2fb0aca73c..17df50f50e 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/GoogleMap.kt @@ -53,6 +53,7 @@ import com.google.android.gms.location.Priority import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicBoolean import com.google.android.gms.maps.model.LatLng +import kotlin.math.abs private fun LongSparseArray.values() = (0 until size()).mapNotNull { valueAt(it) } @@ -262,7 +263,16 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) } override fun getCameraPosition(): CameraPosition { - return map?.cameraPosition?.toGms() ?: CameraPosition(LatLng(0.0, 0.0), 0f, 0f, 0f) + val raw = map?.cameraPosition?.toGms() ?: CameraPosition(LatLng(0.0, 0.0), 0f, 0f, 0f) + val shouldNormalizeOrigin = raw.zoom <= DEFAULT_HMS_MIN_ZOOM && + abs(raw.target.latitude) < INITIAL_CAMERA_ORIGIN_EPSILON && + abs(raw.target.longitude) < INITIAL_CAMERA_ORIGIN_EPSILON + val result = if (shouldNormalizeOrigin) { + CameraPosition(LatLng(0.0, 0.0), raw.zoom, raw.tilt, raw.bearing) + } else { + raw + } + return result } override fun getMaxZoomLevel(): Float = toHmsZoom(map?.maxZoomLevel ?: 18.toFloat()) override fun getMinZoomLevel(): Float = toHmsZoom(map?.minZoomLevel ?: 3.toFloat()) @@ -1062,6 +1072,8 @@ class GoogleMapImpl(private val context: Context, var options: GoogleMapOptions) companion object { private const val TAG = "GmsGoogleMap" + private const val DEFAULT_HMS_MIN_ZOOM = 3f + private const val INITIAL_CAMERA_ORIGIN_EPSILON = 1e-5 private const val SNAPSHOT_OLD_VERSION_CODE = 4000000 private const val TAG_LOGO = "fakeWatermark" From 54663081ff43fbb0bf3da274a60428b642bf2a5a Mon Sep 17 00:00:00 2001 From: DaVinci9196 Date: Wed, 26 Aug 2026 16:16:40 +0800 Subject: [PATCH 4/4] remove automatic coordinate conversion --- .../microg/gms/utils/CoordinateConverter.java | 125 ------------------ .../gms/maps/hms/utils/typeConverter.kt | 8 +- 2 files changed, 2 insertions(+), 131 deletions(-) delete mode 100644 play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java diff --git a/play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java b/play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java deleted file mode 100644 index 9aa4cd243c..0000000000 --- a/play-services-base/core/src/main/java/org/microg/gms/utils/CoordinateConverter.java +++ /dev/null @@ -1,125 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2026 microG Project Team - * SPDX-License-Identifier: Apache-2.0 - */ - -package org.microg.gms.utils; - -/** - * Conversion between the internationally accepted geographic coordinate system (WGS-84) - * and the geographic coordinate system used in China (GCJ-02) - */ -public class CoordinateConverter { - private static final double A = 6378245.0; - private static final double EE = 0.006693421622965943; - private static final double PI = Math.PI; - private static final double EPSILON = 1e-6; - private static final int MAX_ITERATIONS = 10; - private static final double PI_OVER_180 = PI / 180.0; - private static final double A_1_EE = A * (1 - EE); - - // ---------- WGS-84 to GCJ-02 ---------- - - /** - * Convert WGS-84 coordinates to GCJ-02 coordinates - * - * @param wgsLat latitude - * @param wgsLon longitude - * @return GCJ-02 coordinate array, index 0 is latitude, 1 is longitude - */ - public static double[] wgs84ToGcj02(double wgsLat, double wgsLon) { - if (isOutOfChina(wgsLat, wgsLon)) { - return new double[]{wgsLat, wgsLon}; - } - - double dLat = transformLat(wgsLon - 105.0, wgsLat - 35.0); - double dLon = transformLon(wgsLon - 105.0, wgsLat - 35.0); - double radLat = wgsLat * PI_OVER_180; - double magic = Math.sin(radLat); - magic = 1 - EE * magic * magic; - double sqrtMagic = Math.sqrt(magic); - - dLat = (dLat * 180.0) / (A_1_EE / (magic * sqrtMagic) * PI); - dLon = (dLon * 180.0) / (A / sqrtMagic * Math.cos(radLat) * PI); - - return new double[]{wgsLat + dLat, wgsLon + dLon}; - } - - // ---------- GCJ-02 to WGS-84 ---------- - - /** - * Convert GCJ-02 coordinates to WGS-84 coordinates (iterative approximation method) - * - * @param gcjLat latitude - * @param gcjLon longitude - * @return WGS-84 coordinate array, index 0 is latitude, 1 is longitude - */ - public static double[] gcj02ToWgs84(double gcjLat, double gcjLon) { - if (isOutOfChina(gcjLat, gcjLon)) { - return new double[]{gcjLat, gcjLon}; - } - - double[] result = {gcjLat, gcjLon}; - double[] delta = new double[2]; - int iteration = 0; - - while (iteration++ < MAX_ITERATIONS) { - double[] gcjGuess = wgs84ToGcj02(result[0], result[1]); - delta[0] = gcjLat - gcjGuess[0]; - delta[1] = gcjLon - gcjGuess[1]; - - double h = 1e-4; - double[] gradLat = gradient(result[0], result[1], h, 0); - double[] gradLon = gradient(result[0], result[1], h, 1); - - double det = gradLat[0] * gradLon[1] - gradLat[1] * gradLon[0]; - if (Math.abs(det) < 1e-12) break; - - double stepLat = (delta[0] * gradLon[1] - delta[1] * gradLat[1]) / det; - double stepLon = (delta[1] * gradLat[0] - delta[0] * gradLon[0]) / det; - - result[0] += stepLat; - result[1] += stepLon; - - if (Math.abs(stepLat) < EPSILON && Math.abs(stepLon) < EPSILON) { - break; - } - } - return result; - } - - // numerical differentiation to compute gradients - private static double[] gradient(double lat, double lon, double h, int axis) { - double[] base = wgs84ToGcj02(lat, lon); - double[] delta; - if (axis == 0) { - delta = wgs84ToGcj02(lat + h, lon); - } else { - delta = wgs84ToGcj02(lat, lon + h); - } - return new double[]{(delta[0] - base[0]) / h, (delta[1] - base[1]) / h}; - } - - // determine whether the coordinates are outside China - private static boolean isOutOfChina(double lat, double lon) { - return lon < 72.004 || lon > 137.8347 || lat < 0.8293 || lat > 55.8271; - } - - // latitude conversion formula - private static double transformLat(double x, double y) { - double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); - ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(y * PI) + 40.0 * Math.sin(y / 3.0 * PI)) * 2.0 / 3.0; - ret += (160.0 * Math.sin(y / 12.0 * PI) + 320.0 * Math.sin(y * PI / 30.0)) * 2.0 / 3.0; - return ret; - } - - // longitude conversion formula - private static double transformLon(double x, double y) { - double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); - ret += (20.0 * Math.sin(6.0 * x * PI) + 20.0 * Math.sin(2.0 * x * PI)) * 2.0 / 3.0; - ret += (20.0 * Math.sin(x * PI) + 40.0 * Math.sin(x / 3.0 * PI)) * 2.0 / 3.0; - ret += (150.0 * Math.sin(x / 12.0 * PI) + 300.0 * Math.sin(x / 30.0 * PI)) * 2.0 / 3.0; - return ret; - } -} diff --git a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt index ab0d3793c2..dce21f082e 100644 --- a/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt +++ b/play-services-maps/core/hms/src/main/kotlin/org/microg/gms/maps/hms/utils/typeConverter.kt @@ -14,7 +14,6 @@ import com.huawei.hms.maps.HuaweiMap import com.huawei.hms.maps.HuaweiMapOptions import com.huawei.hms.maps.model.* import org.microg.gms.maps.hms.R -import org.microg.gms.utils.CoordinateConverter import com.google.android.gms.maps.model.CameraPosition as GmsCameraPosition import com.google.android.gms.maps.model.CircleOptions as GmsCircleOptions import com.google.android.gms.maps.model.Dash as GmsDash @@ -80,8 +79,7 @@ fun GoogleMapOptions.toHms(): HuaweiMapOptions { } fun GmsLatLng.toHms(): LatLng = - runCatching { CoordinateConverter.wgs84ToGcj02(latitude, longitude) } - .getOrNull()?.let { LatLng(it[0], it[1]) } ?: LatLng(latitude, longitude) + LatLng(latitude, longitude) fun GmsLatLngBounds.toHms(): LatLngBounds = LatLngBounds( @@ -198,9 +196,7 @@ fun PatternItem.toGms(): GmsPatternItem = when (this) { else -> GmsGap(0f) } -fun LatLng.toGms(): GmsLatLng = - runCatching { CoordinateConverter.gcj02ToWgs84(latitude, longitude) } - .getOrNull()?.let { GmsLatLng(it[0], it[1]) } ?: GmsLatLng(latitude, longitude) +fun LatLng.toGms(): GmsLatLng = GmsLatLng(latitude, longitude) fun LatLngBounds.toGms(): GmsLatLngBounds = GmsLatLngBounds( GmsLatLng(southwest.latitude, southwest.longitude),