仿去哪网酒店的地图:POI、定位、国际地图、导航、marker及其自定义infowindow

来源:互联网 发布:c语言graphics 编辑:程序博客网 时间:2024/05/01 16:59

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<String>() {                    @Override                    public void onSuccess(ResponseInfo<String> responseInfo) {                        processData(responseInfo.result);                    }                    private void processData(String result) {                        Type listType = new TypeToken<GeocodeBean>() {}.getType();                        Gson gson = new Gson();                        final GeocodeBean resultBean = gson.fromJson(result, listType);                        if (null != resultBean){                            List<GeocodeBean.ResultsBean> 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<PoiItem> 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<PackageInfo> packageInfos = packageManager.getInstalledPackages(0);        //用于存储所有已安装程序的包名        List<String> packageNames = new ArrayList<String>();        //从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).append(",").append(lng);        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(stringBuffer.toString()));        startActivity(i);    }

到这里就结束了,有兴趣的小伙伴可以给出建议哦,大家一起讨论。在android不归路上越走越远!!!

一些代码:点击打开链接

阅读全文
1 0