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

白腾
2023-12-01

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).app	end(",").append(lng);
        Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(stringBuffer.toString()));
        startActivity(i);
    }

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

一些代码: 点击打开链接

 类似资料: