Android 博客之路第二弹:关于最近研究地图的总结。
前言:最近App开发酒店信息需要用到地图模块,所以就目前需要的功能研究了一下。虽然以前也有用到,但以前仅限于marker及infowindow,而且还是copy别人的,也不甚了解,这次功能要求的算是稍微多了点,但也不是很全,地图还有大量的东西值得去研究,但如果仅限于app开发使用某些功能的话,还是用到啥就研究啥就好,毕竟也不是专业研究地图的,地图功能只是辅助工具而已,是锦上添花的,而不是雪中之炭。
借鉴别人写博客的套路,也是尊重自己的知识来源,说一下研究过程参考的几个博客:
(1)高德地图国内外切换官网demo:点击打开链接
(2)基于Android平台的全球地图方案:点击打开链接
(3)根据经纬度获取地理位置信息(google的):点击打开链接
(4)自定义infowindow:点击打开链接
(5)给高德地图添加google瓦片:点击打开链接
(6)仿微信调用三方map应用导航:点击打开链接
总体而言的,主要是模仿去哪儿网的酒店地图功能,自己整理的一些关于map的知识。这里还要说明一下,我采用的是高德地图,而不是百度地图。不是对百度地图有偏见,而是以前用的百度,个人感觉有点驾驭不了api文档,而且踩坑略微有点多,其精准度有些差别(我定位没有定位到自己的楼里,而是定位到了几十米外的街道上),所以采用了高德地图。如果比较喜欢使用百度地图的小伙伴可以去寻找下资料,也可以评论回复,大家一起参考。当然,有写的不好的地方也请批评指正,不胜感激。
下面进入正题(ps:本来要上动态的效果图,奈何情浅缘深,yy许久也不会搞,就贴一些静态图片吧)
1.国际地图
这里采用的策略是高德地图添加google瓦片,这种添加瓦片的方式比较简单。翠花,上代码---->
UrlTileProvider tileProvider = new UrlTileProvider(256, 256) {
@Override
public URL getTileUrl(int x, int y, int zoom) {
try {
Random random = new Random();
String s = String.format("http://mt"+random.nextInt(3)+".google.cn/vt/lyrs=m@142&hl=zh-CN&gl=cn&x=%d&y=%d&z=%d&s=Galil",x,y,zoom);
return new URL(s);
} catch (MalformedURLException e) {
e.printStackTrace();
}
return null;
}
};
if (mAmapView != null){
mtileOverlay = mAmapView.getMap().addTileOverlay(new
TileOverlayOptions()
.tileProvider(tileProvider)
.diskCacheEnabled(true)
.diskCacheDir("/storage/emulated/0/demo/cache")
.diskCacheSize(100000)
.memoryCacheEnabled(true)
.memCacheSize(100000));
mtileOverlay.setVisible(false);
}
http://mt+random.nextInt(3)+".google.cn/vt/lyrs=m@142&hl=zh-CN&gl=cn&x=%d&y=%d&z=%d&s=Galil"
这个东西具体细节可以查看(5)我就不细说了,这东西copy过去就能加上覆盖的图层
于是我就满心欢喜的加了上去,完了就发现地图一直有这个覆盖的图层。但是我在国内的城市
显示的时候就不想用这个图层了,最起码不能让他显示,因为我觉得他丑,丑人多作怪。
想了几种办法吧----->开始的思路是想通过地图本身的方法去删除、增加这一层overlay.然后一顿查,
amapView.getMap()里边只有addoverlay方法,没有删除,试过了一些比如清除缓存之类的都不行。
怎么办,于是想到了最基本的显隐方法,setVisible,尝试了一下,果然可以,有种恍然大明白的赶脚。
这会就可以肆意的用显隐了,于是就判断当前手机地图显示的中心点是否在国内,在就去掉图层,
反之显示就好了。
2.判断map中心点在国内还是国外
说道这个问题,我觉得大家可能和我想的一样,高德、百度这样的地图执牛耳者肯定提供了api,对,你这么想就是对的。我去查了demo,见(1).里边有难点提示,ios的有如何判断国内外的显示,我心想,android是不是很简单,于是就查了查demo的代码,结果是这样的。
/**
* 粗略判断当前屏幕显示的地图中心点是否在国内
* @param latitude 纬度
* @param longtitude 经度
* @return 屏幕中心点是否在国内
*/
private boolean isInArea(double latitude, double longtitude) {
if ((latitude > 3.837031) && (latitude < 53.563624)
&& (longtitude < 135.095670) && (longtitude > 73.502355)) {
return true;
}
return false;
}
看见 "粗略判断" 四个大字了么,对就是他。这样的判断就是比如蒙古啊,还有很多东南亚的国家,越南之类的都涵盖在了大中国范围内,我也想这样,但这毕竟还没实现。还是实际一点。只能说高德android api程序员的心情我可以理解。这样确实可以粗略判断,而且效率也蛮高,毕竟就是判断几个经纬度的事情。但若要精确到东南亚的酒店,这样就不行了,就得有个稍微准确一点的。
//根据谷歌地图获取某经纬度的信息
public static void getGeocodeOfTheWorld(LatLng latLng, final MyCallBack myCallBack){
RequestParams params = new RequestParams();
String url = "http://www.google.cn/maps/api/geocode/json?latlng=" + latLng.latitude + "," + latLng.longitude +
"&sensor=true,language=zh%20CN";
HttpUtils httpUtils = new HttpUtils();
httpUtils.send(HttpRequest.HttpMethod.POST, url, params,
new RequestCallBack() {
@Override
public void onSuccess(ResponseInfo responseInfo) {
processData(responseInfo.result);
}
private void processData(String result) {
Type listType = new TypeToken() {}.getType();
Gson gson = new Gson();
final GeocodeBean resultBean = gson.fromJson(result, listType);
if (null != resultBean){
List results = resultBean.getResults();
if (results != null && results.size() > 0){
String country = results.get(0).getFormatted_address();
myCallBack.success(country,"geocode");
}
}
}
@Override
public void onFailure(HttpException error, String msg) {
myCallBack.success(msg,"geocode");
}
});
}
这是个简单的网络请求,传入经纬度,调用的是google的cn网站的返回信息,具体的可以详见 (3)返回数据类型就不赘述了,自己去请求下,判断其返回的地址信息是否包含中国,我就是这样判断的,这样给来个回调就好了。
问题来了,每次用手强行拖拽地图的时候会执行下面的方法,这样就在反复滑动中,执行网络请求。虽然他很快,但也是请求网络呀,一方面回刷不及时,一方面请求网络过于频繁,另一方面,很可能内存泄露的大兄弟。
/**
* 高德地图移动完成回调
* @param cameraPosition 地图移动结束的中心点位置信息
*/
@Override
public void onCameraChangeFinish(com.amap.api.maps.model.CameraPosition cameraPosition) {
longitude = cameraPosition.target.longitude;
latitude = cameraPosition.target.latitude;
if (isHotelInChina){
if (isInArea(latitude,longitude)){
changeToAMap();
}else {
changeToGoogleMap();
}
}
}
于是,我就迂回了下,提前根据传进来的目的地的经纬度,判断好了境内外。境内的话,就粗略判断,但人家在滑动的时候像美国这样的不在大中华经纬度范围内的,可以显示图层,而不是空白;境外的话就是一直显示图层,不用顾忌国内的去掉图层了。这样基本的产品需求也就满足了。
3.POI信息
看到去哪儿网app的这些信息的时候,有点懵逼,下意识以为是他们自己的数据,自己可能做不了。后来看高德(当然百度谷歌也都有)的POI数据就找到了曙光
//初始化POI数据
public void initPoiData(String keyWord,String cityCode,LatLng latLng){
PoiSearch.Query query = new PoiSearch.Query(keyWord, "", cityCode);
query.setPageSize(10);//请求了10个
query.setPageNum(0);
PoiSearch poiSearch = new PoiSearch(this, query);
poiSearch.setBound(new PoiSearch.SearchBound(new LatLonPoint(latLng.latitude,latLng.longitude), 100000));//设置周边搜索的中心点以及半径
poiSearch.setOnPoiSearchListener(this);
poiSearch.searchPOIAsyn();
}
就是这个了,他还有个回调,可以在里边处理一下请求回来的数据.
@Override
public void onPoiSearched(PoiResult poiResult, int i) {
if (i == 1000){//1000为返回成功
//解析result获取POI信息
if (null != poiResult){
ArrayList poiItems = poiResult.getPois();
for (PoiItem poiItem : poiItems){
LatLng latLng = new LatLng(poiItem.getLatLonPoint().getLatitude(),poiItem.getLatLonPoint().getLongitude());
Marker marker = handleMarkers(poiItem.getTitle(), latLng, poiItem.getSnippet());
saveMarkers(marker);
Log.e("result",poiItem.getSnippet() +"--"+poiItem.getLatLonPoint().toString()+"--"+poiItem.getTitle()+"--");
}
}
}
}
我这里就是把请求回来的数据存起来了,因为marker要显示,判断下是否有这个数据,有就去显示,没有就去重新请求,毕竟有些人可能来回点。显示了marker就得移动当前的地图中心点。
//移动当前地图确定中心点
public void moveCameraMap(LatLng latLng,float zoom){
if (null != mAmapView){
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(latLng,zoom);
mAmapView.getMap().animateCamera(update);
}
}
这个很平常,可看到了去哪儿的变化zoom装x后毅然决然加了上去,感觉还不错。
ps:这个poi国外的数据我没有能搞到手,有第三方数据的,有google提供的大家可以自行去查找。
4.自定义infowindow就不多说了,大家可以参考(4),但是有几点需要注意:a.若想在自定义的adapter中获取marker的数据,需要自己在前边处理marker时存到marker的相应信息中 b.我是把adapter这种的点击事件通过回调搞到activity中了,因为要显示导航提示,跳转第三方导航。
5.跳转第三方导航
废话不多说,上代码:
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.Toast;
import com.amap.api.maps.model.LatLng;
import com.lh.im.IMHelper;
import com.lh.im.IMModel;
import com.lh.kplx.R;
import com.lh.util.PixAndDpTools;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by mling on 2017/10/25.
* des:
*/
public class OpenMapHelper {
private static OpenMapHelper instance = null;
private PopupWindow typePopWindow;
private OpenMapHelper() {
}
public synchronized static OpenMapHelper getInstance() {
if (instance == null) {
instance = new OpenMapHelper();
}
return instance;
}
public void initView(final Context context, final LatLng latlng,final View view){
View mapIntentView = LayoutInflater.from(context).inflate(R.layout.map_navagation_intent, null);
typePopWindow = new PopupWindow(mapIntentView, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT);
typePopWindow.setFocusable(true);
typePopWindow.setOutsideTouchable(false);
typePopWindow.update();
typePopWindow.setAnimationStyle(R.style.showPopupAnimation);
typePopWindow.setBackgroundDrawable(new BitmapDrawable());
typePopWindow.showAsDropDown(view);
Button bt_baidu = (Button) mapIntentView.findViewById(R.id.map_baidu_btn);
Button bt_amap = (Button) mapIntentView.findViewById(R.id.map_amap_btn);
Button bt_google = (Button) mapIntentView.findViewById(R.id.map_google_btn);
LinearLayout ll_map_root = (LinearLayout) mapIntentView.findViewById(R.id.map_amap_root_ll);
LinearLayout ll_bottom_map = (LinearLayout) mapIntentView.findViewById(R.id.map_bottom);
ll_bottom_map.setAnimation(AnimationUtils.loadAnimation(context,R.anim.activity_open));
ll_map_root.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
typePopWindow.dismiss();
}
});
bt_baidu.setOnClickListener(new View.OnClickListener() {//百度
@Override
public void onClick(View view) {
intentToBaidu(context,latlng);
typePopWindow.dismiss();
}
});
bt_amap.setOnClickListener(new View.OnClickListener() {//高德
@Override
public void onClick(View view) {
intentToAmap(context,latlng);
typePopWindow.dismiss();
}
});
bt_google.setOnClickListener(new View.OnClickListener() {//google
@Override
public void onClick(View view) {
intentToGoogle(context,latlng);
typePopWindow.dismiss();
}
});
}
//跳转谷歌
public static void intentToGoogle(Context context, LatLng latlng) {
if (isAvilible(context, "com.google.android.apps.maps")) {
StringBuffer stringBuffer = new StringBuffer("google.navigation:q=").append(latlng.latitude).append(",").append(latlng.longitude).append("&mode=d");
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(stringBuffer.toString()));
intent.setPackage("com.google.android.apps.maps");
context.startActivity(intent);
} else {
Toast.makeText(context, "您尚未安装谷歌地图", Toast.LENGTH_LONG).show();
Uri uri = Uri.parse("market://details?id=com.google.android.apps.maps");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (intent.resolveActivity(context.getPackageManager()) != null){
context.startActivity(intent);
}
}
}
//跳转高德
private void intentToAmap(Context context, LatLng latlng) {
if (isAvilible(context, "com.autonavi.minimap")) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.addCategory(Intent.CATEGORY_DEFAULT);
//将功能Scheme以URI的方式传入data
Uri uri = Uri.parse("androidamap://navi?sourceApplication=appname&poiname=fangheng&lat=" + latlng.latitude + "&lon=" + latlng.longitude + "&dev=1&style=2");
intent.setData(uri);
//启动该页面即可
context.startActivity(intent);
} else {
Toast.makeText(context, "您尚未安装高德地图", Toast.LENGTH_LONG).show();
Uri uri = Uri.parse("market://details?id=com.autonavi.minimap");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (intent.resolveActivity(context.getPackageManager()) != null){
context.startActivity(intent);
}
}
}
//跳转百度
public static void intentToBaidu(Context context, LatLng latlng) {
if (isAvilible(context, "com.baidu.BaiduMap")) {//传入指定应用包名
try {
Intent intent = Intent.getIntent("intent://map/direction?" +
"destination=latlng:" + latlng.latitude + "," + latlng.longitude + "|name:我的目的地" + //终点
"&mode=driving&" + //导航路线方式
"&src=appname#Intent;scheme=bdapp;package=com.baidu.BaiduMap;end");
context.startActivity(intent); //启动调用
} catch (URISyntaxException e) {
Log.e("intent", e.getMessage());
}
} else {//未安装
//market为路径,id为包名
//显示手机上所有的market商店
Toast.makeText(context, "您尚未安装百度地图", Toast.LENGTH_LONG).show();
Uri uri = Uri.parse("market://details?id=com.baidu.BaiduMap");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (intent.resolveActivity(context.getPackageManager()) != null){
context.startActivity(intent);
}
}
}
;
/**
* 检查手机上是否安装了指定的软件
*
* @param context
* @param packageName:应用包名
* @return
*/
public static boolean isAvilible(Context context, String packageName) {
//获取packagemanager
final PackageManager packageManager = context.getPackageManager();
//获取所有已安装程序的包信息
List packageInfos = packageManager.getInstalledPackages(0);
//用于存储所有已安装程序的包名
List packageNames = new ArrayList();
//从pinfo中将包名字逐一取出,压入pName list中
if (packageInfos != null) {
for (int i = 0; i < packageInfos.size(); i++) {
String packName = packageInfos.get(i).packageName;
packageNames.add(packName);
}
}
//判断packageNames中是否有目标程序的包名,有TRUE,没有FALSE
return packageNames.contains(packageName);
}
}
这里我是把自己的弹出类似微信的页面也加了。直接调用或更改就可以。
/**
* 打开google Web地图导航
*/
private void openWebGoogleNavi() {
StringBuffer stringBuffer = new StringBuffer("http://ditu.google.cn/maps?hl=zh&mrt=loc&q=").append(lat).app end(",").append(lng);
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(stringBuffer.toString()));
startActivity(i);
}