网页定位的不足
我之前写了篇vue调用百度地图 js api实现定位的文章,
vue 实现定位到当前位置
但是实际使用的时候,发现百度定位js经常定位不精确,这个不足,官网也有提,
为了精确,还是使用原生app调用高德地图api来实现定位,这样就要用到js与原生的交互,js调用某个方法,然后触发原生的定位,原生定位完成,调用js方法,回传给js,我这边做的兼容性比较高,js传入不同的坐标系,原生会在回传给js的方法中,将坐标系转换
实现
vue定义方法
vue定义两个方法,方法
getLocationFromNative(String type)
,调起定位功能,type是坐标类型,默认gcj02,bd09:百度坐标系,gcj02:火星坐标系,gps:GPS坐标系(WGS84坐标系),原生提供根据type,转换坐标系的方法
定位成功或失败,回调
notifyLocationFromApp
,传入json数据,json数据自己组装,我这边是这样的
{"data":{"address":"xxx","city":"xx市",
"country":"中国","description":"在xx附近","district":"xx区","latitude":31.4xx8,
"locationDetail":"#csid:4d4xxx7039e594xx3",
"longitude":120.30xx1,"province":"xx省","street":"xx大道","streetNum":"xx号"}
,"isOk":true}
<template>
<div class="phoneContant">
<a :href="tel">点击拨打电话</a>
<van-button type="warning" @click="goToNext">进入下一页</van-button>
<van-button type="warning" @click="getLocation">从app获取定位</van-button>
</div>
</template>
<script>
//判断是否是安卓还是ios
var config = {
isAndroid: /Android/i.test(navigator.userAgent), //判断是否为移动端
isIos: !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/), //判断是否为IOS
};
export default {
name: "Home",
data() {
return {
msg: "我是首页",
store_info: {
phone: 15xxxx753,
},
nativeLocationSuccess:false,
loadDefault:false
};
},
methods: {
goToNext() {
this.$router.push("/second");
},
getLocation() {
setTimeout(() => {
if(this.nativeLocationSuccess==false){
console.log("定位失败,加载默认的接口");
this.loadDefault=true
}
}, 10000);//10秒未返回数据,加载默认的,防止前端没有getLocationFromNative和notifyLocationFromApp回调
/**
* 经纬度坐标系,默认gcj02
bd09:百度坐标系
gcj02:火星坐标系
gps:GPS坐标系(WGS84坐标系)
*/
if (config.isAndroid && window.hesAndroidNative) {
window.hesAndroidNative.getLocationFromNative("");
} else if (config.isIos && window.webkit) {
window.webkit.messageHandlers.getLocationFromNative.postMessage("");
} else {
}
},
notifyLocationFromApp(jsonStr) {
this.nativeLocationSuccess=true;
if(this.loadDefault==true){
return
}
console.log("json string" + jsonStr);
let valueObj=JSON.parse(jsonStr)
console.log("valueObj " + valueObj);
let value = jsonStr+"||||测试解析结果:"+valueObj.data.address
if(valueObj.isOk==false){
console.log("失败");
}else{
console.log("成功",valueObj.data.longitude, valueObj.data.latitude);
}
this.$dialog
.alert({
confirmButtonText: "是",
closeOnClickOverlay: false,
title: "提示",
showCancelButton: false,
messageAlign: "center",
message: value,
})
.then(() => {
return false;
})
.catch(() => {});
},
},
computed: {
tel() {
return "tel:" + Number(this.store_info.phone);
},
},
created() {
window.notifyLocationFromApp = this.notifyLocationFromApp;
this.getLocation()
},
};
</script>
<style>
</style>
大概逻辑就是进入页面调用原生的定位,然后等待10s,10秒未返回数据,加载默认的,防止前端没有getLocationFromNative和notifyLocationFromApp回调,因为js如果检测到方法未定义,直接报错,也没回调,这样写,能保证程序正常运行
原生实现
public class WebviewBase extends BaseCompatActivity{
@BindView(R.id.iv_back)
protected ImageView ivBack;
@BindView(R.id.iv_close)
protected ImageView ivClose;
@BindView(R.id.tv_title)
protected TextView tvTitle;
@BindView(R.id.rl_top)
protected RelativeLayout rlTop;
@BindView(R.id.pbar_more)
protected ProgressBar progressBar;
@BindView(R.id.webView)
protected WebView webView;
protected LocationReslut locationReslut=new LocationReslut();
protected AMapLocationClient mLocationClient = null;
protected String locationType="";
protected AMapLocationListener mLocationListener = new AMapLocationListener()
{
@Override
public void onLocationChanged(AMapLocation aMapLocation)
{
if (aMapLocation != null)
{
if (aMapLocation.getErrorCode() == 0)
{
LocationReslut.Data locationData=locationReslut.new Data();
locationData.setAddress(aMapLocation.getAddress());
locationData.setCity(aMapLocation.getCity());
locationData.setCountry(aMapLocation.getCountry());
locationData.setStreetNum(aMapLocation.getStreetNum());
locationData.setStreet(aMapLocation.getStreet());
locationData.setDistrict(aMapLocation.getDistrict());
locationData.setProvince(aMapLocation.getProvince());
locationData.setLatitude(aMapLocation.getLatitude());
locationData.setLongitude(aMapLocation.getLongitude());
locationReslut.setData(locationData);
locationReslut.setOk(true);
locationResultToJs();
mLocationClient.stopLocation();
}
else
{
locationResultToJs();
Log.e("定位错误", aMapLocation.getErrorCode() + ":" + aMapLocation.getErrorInfo());
}
}
else
{
locationResultToJs();
Log.e("定位失败", "");
}
}
};
private void locationResultToJs() {
Gson gson = new Gson();
Log.i("location",gson.toJson(locationReslut));
LocationReslut.Data data=locationReslut.getData();
if(locationReslut.isOk){
double[] gps=null;
switch (locationType){
case "bd09":
gps= GPSUtil.gcj02_To_Bd09(data.getLatitude(),data.getLongitude());
data.setLatitude(gps[0]);
data.setLongitude(gps[1]);
break;
case "gps":
gps= GPSUtil.gcj02_To_Gps84(data.getLatitude(),data.getLongitude());
data.setLatitude(gps[0]);
data.setLongitude(gps[1]);
break;
default:
break;
}
}
//原生回调js通知
webView.evaluateJavascript("javascript:notifyLocationFromApp('" + gson.toJson(locationReslut) + "')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview_privacy);
ButterKnife.bind(this);
initWebView();
init();
locationReslut.setData(null);
locationReslut.setOk(false);
....省略各种初始化方法
}
protected void init()
{
mLocationClient = new AMapLocationClient(WebviewBase.this);
mLocationClient.setLocationListener(mLocationListener);
if (ActivityCompat.checkSelfPermission(WebviewBase.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
ActivityCompat.requestPermissions(WebviewBase.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 100);
}
else
{
//mLocationClient.startLocation();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void initWebView() {
WebSettings webSettings = webView.getSettings();
webSettings.setJavaScriptEnabled(true);
// 设置字符编码
webSettings.setDefaultTextEncodingName("utf-8");
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setAllowFileAccess(true);
webSettings.setLoadsImagesAutomatically(true);
// webSettings.setAppCacheEnabled(true);
webSettings.setBlockNetworkImage(false);
// webSettings.setGeolocationEnabled(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
webSettings.setDefaultTextEncodingName("UTF-8");
webSettings.setSupportZoom(true);
// webSettings.setBuiltInZoomControls(true);
// webSettings.setPluginsEnabled(true);
//webView.addJavascriptInterface(this, "AppInterface");
webSettings.setDomStorageEnabled(true);
webSettings.setDatabaseEnabled(true);
final String dbPath = getApplicationContext().getDir("db", Context.MODE_PRIVATE).getPath();
webSettings.setDatabasePath(dbPath);
final String cachePath = getApplicationContext().getDir("cache", Context.MODE_PRIVATE).getPath();
webSettings.setAppCachePath(cachePath);
webSettings.setAppCacheMaxSize(10 * 1024 * 1024);
//设置加载进来的页面自适应手机屏幕
webSettings.setUseWideViewPort(true);
webSettings.setLoadWithOverviewMode(true);
webView.setWebChromeClient(new MyWebChromeClient());
webView.setWebViewClient(new MyWebViewClient());
if (userAgent != null && !StringUtil.isEmpty(userAgent)) {
String ua = webSettings.getUserAgentString();// 获取默认的UA
webSettings.setUserAgentString(ua + "; " + userAgent);// UA追加自定义标识符
}
// 通过addJavascriptInterface()将Java对象映射到JS对象
//参数1:Javascript对象名
//参数2:Java对象名
webView.addJavascriptInterface(new HesAndroidtoJs(), "hesAndroidNative");
}
// 继承自Object类
public class HesAndroidtoJs
{
// 定义JS需要调用的方法
// 被JS调用的方法必须加入@JavascriptInterface注解
@JavascriptInterface
public void getDomainDataFromNative() {
runOnUiThread(new Runnable() {
@Override
public void run() {
//原生回调js通知
webView.evaluateJavascript("javascript:notifyDomainDataFromApp('" + domainData + "')", new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
//此处为 js 返回的结果
}
});
}
});
}
/**
* 经纬度坐标系,默认gcj02
* bd09:百度坐标系
* gcj02:火星坐标系
* gps:GPS坐标系(WGS84坐标系)
* @param type
*/
@JavascriptInterface
public void getLocationFromNative(String type) {
locationType=type;
if(mLocationClient!=null){
mLocationClient.startLocation();
}
}
}
private class LocationReslut{
private boolean isOk;
private Data data;
public boolean isOk() {
return isOk;
}
public void setOk(boolean ok) {
isOk = ok;
}
public Data getData() {
return data;
}
public void setData(Data data) {
this.data = data;
}
public class Data{
String country;
String province;
String city;
String street;
String streetNum;
String address;
double latitude;
double longitude;
String district;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getProvince() {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getStreetNum() {
return streetNum;
}
public void setStreetNum(String streetNum) {
this.streetNum = streetNum;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public String getDistrict() {
return district;
}
public void setDistrict(String district) {
this.district = district;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if(mLocationClient!=null){
mLocationClient.stopLocation();
}
}
}
GPSUtil
public class GPSUtil
{
public static double pi = 3.1415926535897932384626;
public static double x_pi = 3.14159265358979324 * 3000.0 / 180.0;
public static double a = 6378245.0;
public static double ee = 0.00669342162296594323;
public 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 * Math.sin(y * pi / 30.0)) * 2.0 / 3.0;
return ret;
}
public 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;
}
public static double[] transform(double lat, double lon)
{
if (outOfChina(lat, lon))
{
return new double[]{lat, lon};
}
double dLat = transformLat(lon - 105.0, lat - 35.0);
double dLon = transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * pi;
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);
double mgLat = lat + dLat;
double mgLon = lon + dLon;
return new double[]{mgLat, mgLon};
}
public static boolean outOfChina(double lat, double lon)
{
if (lon < 72.004 || lon > 137.8347)
return true;
if (lat < 0.8293 || lat > 55.8271)
return true;
return false;
}
/**
* 84 to 火星坐标系 (GCJ-02) World Geodetic System ==> Mars Geodetic System
*
* @param lat
* @param lon
* @return
*/
public static double[] gps84_To_Gcj02(double lat, double lon)
{
if (outOfChina(lat, lon))
{
return new double[]{lat, lon};
}
double dLat = transformLat(lon - 105.0, lat - 35.0);
double dLon = transformLon(lon - 105.0, lat - 35.0);
double radLat = lat / 180.0 * pi;
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);
double mgLat = lat + dLat;
double mgLon = lon + dLon;
return new double[]{mgLat, mgLon};
}
/**
* * 火星坐标系 (GCJ-02) to 84 * * @param lon * @param lat * @return
*/
public static double[] gcj02_To_Gps84(double lat, double lon)
{
double[] gps = transform(lat, lon);
double lontitude = lon * 2 - gps[1];
double latitude = lat * 2 - gps[0];
return new double[]{latitude, lontitude};
}
/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 将 GCJ-02 坐标转换成 BD-09 坐标
*
* @param lat
* @param lon
*/
public static double[] gcj02_To_Bd09(double lat, double lon)
{
double x = lon, y = lat;
double z = Math.sqrt(x * x + y * y) + 0.00002 * Math.sin(y * x_pi);
double theta = Math.atan2(y, x) + 0.000003 * Math.cos(x * x_pi);
double tempLon = z * Math.cos(theta) + 0.0065;
double tempLat = z * Math.sin(theta) + 0.006;
double[] gps = {tempLat, tempLon};
return gps;
}
/**
* * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换算法 * * 将 BD-09 坐标转换成GCJ-02 坐标 * * @param
* bd_lat * @param bd_lon * @return
*/
public static double[] bd09_To_Gcj02(double lat, double lon)
{
double x = lon - 0.0065, y = lat - 0.006;
double z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi);
double theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
double tempLon = z * Math.cos(theta);
double tempLat = z * Math.sin(theta);
double[] gps = {tempLat, tempLon};
return gps;
}
/**
* 将gps84转为bd09
*
* @param lat
* @param lon
* @return
*/
public static double[] gps84_To_bd09(double lat, double lon)
{
double[] gcj02 = gps84_To_Gcj02(lat, lon);
double[] bd09 = gcj02_To_Bd09(gcj02[0], gcj02[1]);
return bd09;
}
public static double[] bd09_To_gps84(double lat, double lon)
{
double[] gcj02 = bd09_To_Gcj02(lat, lon);
double[] gps84 = gcj02_To_Gps84(gcj02[0], gcj02[1]);
//保留小数点后六位
gps84[0] = retain6(gps84[0]);
gps84[1] = retain6(gps84[1]);
return gps84;
}
/**
* 保留小数点后六位
*
* @param num
* @return
*/
private static double retain6(double num)
{
String result = String.format("%.6f", num);
return Double.valueOf(result);
}
}
总结
这个只是拿定位做个例子,其实这个就是原生和js交互的通用方法,可以直接使用,完成其他需要相互交互的逻辑