Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
1 change: 1 addition & 0 deletions play-services-maps/core/hms/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand All @@ -21,16 +23,20 @@ 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.*
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
Expand All @@ -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 <T : Any> LongSparseArray<T>.values() = (0 until size()).mapNotNull { valueAt(it) }
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand All @@ -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() }
Expand All @@ -877,13 +950,15 @@ 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

created = false
initialized = false
loaded = false
isAddLocationCallback = false
}

override fun onStart() {
Expand Down Expand Up @@ -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 = "") {

Expand All @@ -955,7 +1040,7 @@ 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() }
runCallbacks()
}
if (!wasCallbackActive) isInvokingInitializedCallbacks.set(false)
} else {
Expand All @@ -975,17 +1060,12 @@ 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
}
}
Loading