• 腾讯位置服务GPS轨迹回放


    前言

    当我们使用地图进行开发时,利用已经录制好的轨迹进行轨迹回放来检查导航的准确性是十分常用的手段,并且上一篇已经讲完了关于地图使用时GPS轨迹文件的录制,现在对于安卓系统下使用腾讯导航SDK进行轨迹回放做一个分享

    前期准备

    腾讯导航SDK依赖于腾讯地图SDK、腾讯定位SDK,具体权限的开通需要去lbs.qq.com 的官网控制台去操作,另外导航SDK的权限可以联系小助手咨询(如下图所示),这里就不多做探讨

    16222560693250.jpg

    轨迹回放正片

    系统架构

    16224265311888.jpg

    GPS回放系统分成两部分:GPSPlaybackActivity 和 GPSPlaybackEngine。
    GPSPlayback负责和外界的交互,主要是信息的传递和导航SDK的交互,而GPSPlaybackEngine负责具体的读取文件和将定位点通过多线程runnable机制灌入listener。

    开始轨迹回放

    BaseNaviActivity.java

    baseNaviActivity 主要是对于导航SDK naviView部分的生命周期的管理,必须实现,否则不能进行导航!

    
    /**
     * 导航 SDK {@link CarNaviView} 初始化与周期管理类。
     */
    public abstract class BaseNaviActivity {
    
        private static Context mApplicationContext;
    
        protected CarNaviView mCarNaviView;
    
        // 建立了TencentCarNaviManager 单例模式,也可以直接调用TencentCarNaviManager来建立自己的carNaviManager
        public static final Singleton<TencentCarNaviManager> mCarManagerSingleton =
                new Singleton<TencentCarNaviManager>() {
                    @Override
                    protected TencentCarNaviManager create() {
                        return new TencentCarNaviManager(mApplicationContext);
                    }
                };
    
        public static TencentCarNaviManager getCarNaviManager(Context appContext) {
            mApplicationContext = appContext;
            return mCarManagerSingleton.get();
        }
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutID());
            super.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
            mApplicationContext = getApplicationContext();
            mCarNaviView = findViewById(R.id.tnk_car_navi_view);
            mCarManagerSingleton.get().addNaviView(mCarNaviView);
        }
    
        public int getLayoutID() {
            return R.layout.tnk_activity_navi_base;
        }
    
        protected View getCarNaviViewChaild() {
            final int count = mCarNaviView.getChildCount();
            if (0 >= count) {
                return mCarNaviView;
            }
            return mCarNaviView.getChildAt(count - 1);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            if (!isDestoryMap()) {
                return;
            }
            mCarManagerSingleton.get().removeAllNaviViews();
            if (mCarNaviView != null) {
                mCarNaviView.onDestroy();
            }
    //        mCarManagerSingleton.destory();
        }
    
        @Override
        protected void onStart() {
            super.onStart();
            if (mCarNaviView != null) {
                mCarNaviView.onStart();
            }
        }
    
        @Override
        protected void onRestart() {
            super.onRestart();
            if (mCarNaviView != null) {
                mCarNaviView.onRestart();
            }
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (mCarNaviView != null) {
                mCarNaviView.onResume();
            }
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            if (mCarNaviView != null) {
                mCarNaviView.onPause();
            }
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            if (mCarNaviView != null) {
                mCarNaviView.onStop();
            }
        }
    
        protected boolean isDestoryMap() {
            return true;
        }
    }
    
    

    GPSPlaybackActivity.java

    这一部分主要是对于导航 SDK的交互和添加导航UI部分初始化工作。注意导航sdk一定要先算路,再开始导航。算路可以取得GPS文件的首行为起点,末行为终点。

    用到的fields

        private static final String LOG_TAG = "[GpsPlayback]";
    
        // gps 文件路径
        private String mGpsTrackPath;
        // gps 轨迹的起终点
        private NaviPoi mFrom, mTo;
    
        // 是否是84坐标系
        private boolean isLocation84 = true;
    

    因为在GPSPlaybackEngine已经进行了listener监听,所以需要对于导航SDK进行灌点

    // 腾讯定位sdk的listener
        private TencentLocationListener listener = new TencentLocationListener() {
            @Override
            public void onLocationChanged(TencentLocation tencentLocation, int error, String reason) {
                if (error != TencentLocation.ERROR_OK || tencentLocation == null) {
                    return;
                }
                Log.d(LOG_TAG, "onLocationChanged : "
                        + ", latitude" + tencentLocation.getLatitude()
                        + ", longitude: " + tencentLocation.getLongitude()
                        + ", provider: " + tencentLocation.getProvider()
                        + ", accuracy: " + tencentLocation.getAccuracy());
    
                // 将定位点灌入导航SDK
                // mCarManagerSingleton是使用导航SDK的carNaviManager创建的单例,开发者可以自己实现
                mCarManagerSingleton.get().updateLocation(ConvertHelper
                        .convertToGpsLocation(tencentLocation), error, reason);
            }
    
            @Override
            public void onStatusUpdate(String provider, int status, String description) {
                Log.d(LOG_TAG, "onStatusUpdate provider: " + provider
                        + ", status: " + status
                        + ", desc: " + description);
    
                // 更新GPS状态.
                mCarManagerSingleton.get().updateGpsStatus(provider, status, description);
            }
        };
    

    onCreate方法初始化UI和添加callback

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            // 获取GPS文件轨迹路径,这里可以由开发者自己获取
            mGpsTrackPath = getIntent().getStringExtra("gpsTrackPath");
            if (mGpsTrackPath == null || mGpsTrackPath.isEmpty()) {
                return;
            }
    
            initUi();
            addTencentCallback();
    
            new Handler().post(() -> {
            
                // 目的获取每条轨迹的arraylist
                ArrayList<String> gpsLineStrs = readGpsFile(mGpsTrackPath);
                if (gpsLineStrs == null || gpsLineStrs.isEmpty()) {
                    return;
                }
                // 获取起终点
                getFromAndTo(gpsLineStrs);
                if (mFrom == null || mTo == null) {
                    return;
                }
                final Handler handlerUi = new Handler(Looper.getMainLooper());
                handlerUi.post(() -> searchAndStartNavigation());
            });
    
        }
        
    
    private void initUi() {
            mCarManagerSingleton.get().setInternalTtsEnabled(true);
    
            final int margin = CommonUtils.dip2px(this, 36);
            // 全览模式的路线边距
            mCarNaviView.setVisibleRegionMargin(margin, margin, margin, margin);
            mCarNaviView.setAutoScaleEnabled(true);
            mCarManagerSingleton.get().setMulteRoutes(true);
            mCarNaviView.setNaviMapActionCallback(mCarManagerSingleton.get());
    
            // 使用默认UI
            CarNaviInfoPanel carNaviInfoPanel = mCarNaviView.showNaviInfoPanel();
            carNaviInfoPanel.setOnNaviInfoListener(() -> {
                mCarManagerSingleton.get().stopNavi();
                finish();
            });
            CarNaviInfoPanel.NaviInfoPanelConfig config = new CarNaviInfoPanel.NaviInfoPanelConfig();
            config.setRerouteViewEnable(true);             // 重算按钮
            carNaviInfoPanel.setNaviInfoPanelConfig(config);
        }
    
        private void addTencentCallback() {
            mCarManagerSingleton.get().addTencentNaviCallback(mTencentCallback);
        }
        
        private TencentNaviCallback mTencentCallback = new TencentNaviCallback() {
            @Override
            public void onStartNavi() { }
    
            @Override
            public void onStopNavi() { }
    
            @Override
            public void onOffRoute() { }
    
            @Override
            public void onRecalculateRouteSuccess(int recalculateType,
                                                  ArrayList<RouteData> routeDataList) { }
            @Override
            public void onRecalculateRouteSuccessInFence(int recalculateType) { }
    
            @Override
            public void onRecalculateRouteFailure(int recalculateType,
                                                  int errorCode, String errorMessage) { }
    
            @Override
            public void onRecalculateRouteStarted(int recalculateType) { }
    
            @Override
            public void onRecalculateRouteCanceled() { }
    
            @Override
            public int onVoiceBroadcast(NaviTts tts) {
                return 0;
            }
    
            @Override
            public void onArrivedDestination() { }
    
            @Override
            public void onPassedWayPoint(int passPointIndex) { }
    
            @Override
            public void onUpdateRoadType(int roadType) { }
    
            @Override
            public void onUpdateParallelRoadStatus(ParallelRoadStatus parallelRoadStatus) {
    
            }
    
            @Override
            public void onUpdateAttachedLocation(AttachedLocation location) { }
    
            @Override
            public void onFollowRouteClick(String routeId, ArrayList<LatLng> latLngArrayList) { }
        };
    

    readGpsFile方法

    private ArrayList<String> readGpsFile(String fileName) {
            ArrayList<String> gpsLineStrs = new ArrayList<>();
            BufferedReader reader = null;
            try {
                File file = new File(fileName);
                InputStream is = new FileInputStream(file);
                reader = new BufferedReader(new InputStreamReader(is));
    
                String line;
                while ((line = reader.readLine()) != null) {
                    gpsLineStrs.add(line);
                }
                return gpsLineStrs;
            } catch (Exception e) {
                Log.e(LOG_TAG, "startMockTencentLocation Exception", e);
                e.printStackTrace();
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (Exception e) {
                    Log.e(LOG_TAG, "startMockTencentLocation Exception", e);
                    e.printStackTrace();
                }
            }
            return null;
        }
    

    getFromAndTo方法,获取起终点为进行算路

        private void getFromAndTo(ArrayList<String> gpsLineStrs) {
            final int size;
            if ((size = gpsLineStrs.size()) < 2) {
                return;
            }
            final String firstLine = gpsLineStrs.get(0);
            final String endLine = gpsLineStrs.get(size - 1);
            try {
                final String[] fromParts = firstLine.split(",");
                mFrom = new NaviPoi(Double.valueOf(fromParts[1]), Double.valueOf(fromParts[0]));
                final String[] endParts = endLine.split(",");
                mTo = new NaviPoi(Double.valueOf(endParts[1]), Double.valueOf(endParts[0]));
            } catch (Exception e) {
                mFrom = null;
                mTo = null;
            }
        }
    

    算路searchAndStartNavigation()

    可以使用导航SDK的算路方法并且获取算路成功和失败的回调
    
    
        private void searchAndStartNavigation() {
            mCarManagerSingleton.get()
                    .searchRoute(new TencentRouteSearchCallback() {
                        @Override
                        public void onRouteSearchFailure(int i, String s) {
                            toast("路线规划失败");
                        }
    
                        @Override
                        public void onRouteSearchSuccess(ArrayList<RouteData> arrayList) {
                            if (arrayList == null || arrayList.isEmpty()) {
                                toast("未能召回路线");
                                return;
                            }
                            handleGpsPlayback();
                        }
                    });
        }
        
    

    调用GpsPlaybackEngine方法,进行listen定位,然后开始导航

        private void handleGpsPlayback() {
    
    // 与GpsPlaybackEngine 进行交互, 添加locationListener
    GpsPlaybackEngine.getInstance().addTencentLocationListener(listener);
    
    //与GpsPlaybackEngine 进行交互,开始定位
            GpsPlaybackEngine.getInstance().startMockTencentLocation(mGpsTrackPath, isLocation84);
            try {
                mCarManagerSingleton.get().startNavi(0);
            } catch (Exception e) {
                toast(e.getMessage());
            }
        }
    

    结束导航

        @Override
        protected void onDestroy() {
    // 与GpsPlaybackEngine 进行交互, removelocationListener
    mCarManagerSingleton.get().removeTencentNaviCallback(mTencentCallback);
    //与GpsPlaybackEngine 进行交互,结束定位GpsPlaybackEngine.getInstance().removeTencentLocationListener(listener);
            GpsPlaybackEngine.getInstance().stopMockLocation();
            if (mCarManagerSingleton.get().isNavigating()) {
            // 结束导航
                mCarManagerSingleton.get().stopNavi();
            }
            super.onDestroy();
        }
    

    GPSPlaybackEngine.java

    这一部分主要是对于GPS文件进行读取并且提供外界可用的add/removelistener方法,start/stopMockLocation方法
    因为要让engine运行在自己的线程,所以使用runnable机制

    public class GpsPlaybackEngine implements Runnable{
    
                // 代码在下方
    }
    

    而使用到的fields

    // Tencent轨迹Mock, TencentLocationListener需要利用腾讯定位SDK获取
    private ArrayList<TencentLocationListener> mTencentLocationListeners = new ArrayList<>();
        
    // 获取的location数据
    private List<String> mDatas = new ArrayList<String>();
         
    private boolean mIsReplaying = false;
    
    private boolean mIsMockTencentLocation = true;
    
    private Thread mMockGpsProviderTask = null;
    
    // 是否已经暂停
    private boolean mPause = false;
    
    private double lastPointTime = 0;
    private double sleepTime = 0;
    

    关键方法

    • listener相关
        // 添加listener
        public void addTencentLocationListener(TencentLocationListener listener) {
            if (listener != null) {
                mTencentLocationListeners.add(listener);
            }
        }
    
        // 移除listener 
        public void removeTencentLocationListener(TencentLocationListener listener) {
            if (listener != null) {
                mTencentLocationListeners.remove(listener);
            }
        }
    
    • 开始/关闭模拟轨迹
        /*
         * 模拟轨迹
         * @param context
         * @param fileName 轨迹文件绝对路径
         */
        public void startMockTencentLocation(String fileName, boolean is84) {
    
           // 首先清除以前的data
            mDatas.clear();
            // 判断是否是84坐标系
            mIsMockTencentLocation = !is84;
            BufferedReader reader = null;
            try {
                File file = new File(fileName);
                InputStream is = new FileInputStream(file);
                reader = new BufferedReader(new InputStreamReader(is));
    
                String line;
                while ((line = reader.readLine()) != null) {
                    mDatas.add(line);
                }
                if (mDatas.size() > 0) {
                    mIsReplaying = true;
                    synchronized (this) {
                        mPause = false;
                    }
                    // 开启异步线程
                    mMockGpsProviderTask = new Thread(this);
                    mMockGpsProviderTask.start();
                }
            } catch (Exception e) {
                Log.e(TAG, "startMockTencentLocation Exception", e);
                e.printStackTrace();
            } finally {
                try {
                    if (reader != null) {
                        reader.close();
                    }
                } catch (Exception e) {
                    Log.e(TAG, "startMockTencentLocation Exception", e);
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * 退出应用前也需要调用停止模拟位置,否则手机的正常GPS定位不会恢复
         */
        public void stopMockTencentLocation() {
            try {
                mIsReplaying = false;
                mMockGpsProviderTask.join();
                mMockGpsProviderTask = null;
                lastPointTime = 0;
            } catch (Exception e) {
                Log.e(TAG, "stopMockTencentLocation Exception", e);
                e.printStackTrace();
            }
        }
    
    • runnable相关
     @Override
        public void run() {
            for (String line : mDatas) {
                if (!mIsReplaying) {
                    Log.e(TAG, "stop gps replay");
                    break;
                }
                if (TextUtils.isEmpty(line)) {
                    continue;
                }
    
                try {
                    Thread.sleep(getSleepTime(line) * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                boolean mockResult;
                mockResult = mockTencentLocation(line);
                if (!mockResult) {
                    break;
                }
    
                try {
                    checkToPauseThread();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    

    使用到的private方法

    private void checkToPauseThread() throws InterruptedException {
            synchronized (this) {
                while (mPause) {
                    wait();
                }
            }
    }
    
    private int getSleepTime(String line) {
        try {
            String[] parts = line.split(",");
            double time = Double.valueOf(parts[6]);
            time = (int) Math.floor(time);
            if(lastPointTime != 0) {
                sleepTime = time  - lastPointTime; // 单位s,取整数
            }
            lastPointTime = time;
        }catch (Exception e) {
        
        }
        return (int)sleepTime;
    }
    
    private boolean mockTencentLocation(String line) {
        try {
            String[] parts = line.split(",");
    
            double latitude = Double.valueOf(parts[1]);
            double longitude = Double.valueOf(parts[0]);
            float accuracy = Float.valueOf(parts[2]);
            float bearing = Float.valueOf(parts[3]);
            float speed = Float.valueOf(parts[4]);
            double altitude = Double.valueOf(parts[7]);
            double time = Double.valueOf(parts[6]);
    
            String buildingId;
            String floorName;
            if (parts.length >= 10) {
                buildingId = parts[8];
                floorName = parts[9];
            } else {
                buildingId = "";
                floorName = "";
            }
    
            if (!mIsMockTencentLocation) {
                double[] result = CoordinateConverter.wgs84togcj02(longitude, latitude);
                longitude = result[0];
                latitude = result[1];
            }
    
            GpsPlaybackEngine.MyTencentLocation location = new GpsPlaybackEngine.MyTencentLocation();
            location.setProvider("gps");
            location.setLongitude(longitude);
            location.setLatitude(latitude);
            location.setAccuracy(accuracy);
            location.setDirection(bearing);
            location.setVelocity(speed);
            location.setAltitude(altitude);
            location.setBuildingId(buildingId);
            location.setFloorName(floorName);
            location.setRssi(4);
            location.setTime(System.currentTimeMillis());
    //			location.setTime((long) time * 1000);
    
            for (TencentLocationListener listener : mTencentLocationListeners) {
                if (listener != null) {
                    String curTime;
                    if (location != null && location.getTime() != 0) {
                        long millisecond = location.getTime();
                        Date date = new Date(millisecond);
                        SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd hh:mm:ss");
                        curTime = format.format(date);
                    } else {
                        curTime = "null";
                    }
                    Log.e(TAG, "time : " + curTime
                            + ", longitude : " + longitude
                            + " , latitude : " + latitude);
    
                    listener.onLocationChanged(location, 0, "");
                    listener.onStatusUpdate(LocationManager.GPS_PROVIDER, mMockGpsStatus, "");
                }
            }
        } catch(Exception e) {
            Log.e(TAG, "Mock Location Exception", e);
            // 如果未开位置模拟,这里可能出异常
            e.printStackTrace();
            return false;
        }
        return true;
    }
    

    CoordinateConverter.wg84togcj02

    	/**
    	 * WGS84转GCJ02(火星坐标系)
    	 * 
    	 * @param lng WGS84坐标系的经度
    	 * @param lat WGS84坐标系的纬度
    	 * @return 火星坐标数组
    	 */
    	public static double[] wgs84togcj02(double lng, double lat) {
    		if (out_of_china(lng, lat)) {
    			return new double[] { lng, lat };
    		}
    		double dlat = transformlat(lng - 105.0, lat - 35.0);
    		double dlng = transformlng(lng - 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);
    		dlng = (dlng * 180.0) / (a / sqrtmagic * Math.cos(radlat) * pi);
    		double mglat = lat + dlat;
    		double mglng = lng + dlng;
    		return new double[] { mglng, mglat };
    	}
    

    内部类MyTencentLocation implements 定位sdk的接口

    class MyTencentLocation implements TencentLocation {
            /**
             * 纬度
             */
            private double latitude = 0;
            /**
             * 经度
             */
            private double longitude = 0;
            /**
             * 精度
             */
            private float accuracy = 0;
            /**
             * gps方向
             */
            private float direction = -1;
            /**
             * 速度
             */
            private float velocity = 0;
            /**
             * 时间
             */
            private long time = 0;
            /**
             * 海拔高度
             */
            private double altitude = 0;
            /**
             * 定位来源
             */
            private String provider = "";
            /**
             * GPS信号等级
             */
            private int rssi = 0;
    
            /**
             * 手机的机头方向
             */
            private float phoneDirection = -1;
    
            private String buildingId = "";
    
            private String floorName = "";
    
            private  String fusionProvider = "";
    
            @Override
            public String getProvider() {
                return provider;
            }
    
            @Override
            public String getSourceProvider() {
                return null;
            }
    
            @Override
            public String getFusionProvider() {
                return fusionProvider;
            }
    
            @Override
            public String getCityPhoneCode() {
                return null;
            }
    
            @Override
            public double getLatitude() {
                return latitude;
            }
    
            @Override
            public double getLongitude() {
                return longitude;
            }
    
            @Override
            public double getAltitude() {
                return latitude;
            }
    
            @Override
            public float getAccuracy() {
                return accuracy;
            }
    
            @Override
            public String getName() {
                return null;
            }
    
            @Override
            public String getAddress() {
                return null;
            }
    
            @Override
            public String getNation() {
                return null;
            }
    
            @Override
            public String getProvince() {
                return null;
            }
    
            @Override
            public String getCity() {
                return null;
            }
    
            @Override
            public String getDistrict() {
                return null;
            }
    
            @Override
            public String getTown() {
                return null;
            }
    
            @Override
            public String getVillage() {
                return null;
            }
    
            @Override
            public String getStreet() {
                return null;
            }
    
            @Override
            public String getStreetNo() {
                return null;
            }
    
            @Override
            public Integer getAreaStat() {
                return null;
            }
    
            @Override
            public List<TencentPoi> getPoiList() {
                return null;
            }
    
            @Override
            public float getBearing() {
                return direction;
            }
    
            @Override
            public float getSpeed() {
                return velocity;
            }
    
            @Override
            public long getTime() {
                return time;
            }
    
            @Override
            public long getElapsedRealtime() {
                return time;
            }
    
            @Override
            public int getGPSRssi() {
                return rssi;
            }
    
            @Override
            public String getIndoorBuildingId() {
                return buildingId;
            }
    
            @Override
            public String getIndoorBuildingFloor() {
                return floorName;
            }
    
            @Override
            public int getIndoorLocationType() {
                return 0;
            }
    
            @Override
            public double getDirection() {
                return phoneDirection;
            }
    
            @Override
            public String getCityCode() {
                return null;
            }
    
            @Override
            public TencentMotion getMotion() {
                return null;
            }
    
            @Override
            public int getGpsQuality() {
                return 0;
            }
    
            @Override
            public float getDeltaAngle() {
                return 0;
            }
    
            @Override
            public float getDeltaSpeed() {
                return 0;
            }
    
            @Override
            public int getCoordinateType() {
                return 0;
            }
    
            @Override
            public int getFakeReason() {
                return 0;
            }
    
            @Override
            public int isMockGps() {
                return 0;
            }
    
            @Override
            public Bundle getExtra() {
                return null;
            }
    
            @Override
            public int getInOutStatus() {
                return 0;
            }
    
            public void setLatitude(double latitude) {
                this.latitude = latitude;
            }
    
            public void setLongitude(double longitude) {
                this.longitude = longitude;
            }
    
            public void setAccuracy(float accuracy) {
                this.accuracy = accuracy;
            }
    
            public void setDirection(float direction) {
                this.direction = direction;
            }
    
            public void setVelocity(float velocity) {
                this.velocity = velocity;
            }
    
            public void setTime(long time) {
                this.time = time;
            }
    
            public void setAltitude(double altitude) {
                this.altitude = altitude;
            }
    
            public void setProvider(String provider) {
                this.provider = provider;
            }
    
            public void setFusionProvider(String fusionProvider) { this.fusionProvider = fusionProvider; }
    
            public void setRssi(int rssi) {
                this.rssi = rssi;
            }
    
            public void setPhoneDirection(float phoneDirection) {
                this.phoneDirection = phoneDirection;
            }
    
            public void setBuildingId(String buildingId) {
                this.buildingId = buildingId;
            }
    
            public void setFloorName(String floorName) {
                this.floorName = floorName;
            }
        }
    
    

    效果展示

    最终根据已经录制好的轨迹(具体录制方法可以参见上期腾讯位置服务轨迹录制-安卓篇),从中国技术交易大厦到北京西站的gps轨迹进行回放,并通过导航sdk进行展示如下

    tutieshi_640x1386_65s.gif

    作者:腾讯位置服务

    链接:https://my.oschina.net/u/4209404/blog/5069640

    来源:开源中国

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    你以为在用SharePoint但事实上不是
    python 站点爬虫 下载在线盗墓笔记小说到本地的脚本
    CF 552C 进制转换
    ArcGIS制图——多图层道路压盖处理
    数据结构实验图论一:基于邻接矩阵的广度优先搜索遍历
    ANTLR4权威參考手冊(一)
    Codeforces Round #270--B. Design Tutorial: Learn from Life
    二叉树近期公共父节点
    for循环遍历字符串的还有一种方法
    Android学习笔记技巧之垂直和水平滚动视图
  • 原文地址:https://www.cnblogs.com/Yi-Xiu/p/14850620.html
Copyright © 2020-2023  润新知