3

Preface

When we use the map for development, it is a very common method to check the accuracy of the navigation by using the recorded track to replay the track, and the previous article has already finished the recording of the GPS track file when the map is used. Now for the Android system Use Tencent Navigation SDK for track playback to share

Preliminary preparation

Tencent Navigation SDK relies on Tencent Map SDK and Tencent Location SDK. The specific permissions need to go to the official website console of lbs.qq.com to operate. In addition, the permissions of the navigation SDK can be contacted by the assistant (as shown in the figure below). Not much discussion here

16222560693250.jpg

Track playback feature film

system structure

16224265311888.jpg

The GPS playback system is divided into two parts: GPSPlaybackActivity and GPSPlaybackEngine.
GPSPlayback is responsible for the interaction with the outside world, mainly the transmission of information and the interaction of the navigation SDK, while GPSPlaybackEngine is responsible for the specific reading of files and injecting positioning points into the listener through the multithreaded runnable mechanism.

Start track playback

BaseNaviActivity.java

baseNaviActivity is mainly for the management of the life cycle of the naviView part of the navigation SDK. It must be implemented, otherwise the navigation cannot be performed!


/**
 * 导航 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

This part is mainly for the interaction of the navigation SDK and the initialization of the navigation UI part. Note that the navigation sdk must first calculate the road, and then start the navigation. Calculating the path can get the first line of the GPS file as the starting point and the end line of the end point.

Used fields

    private static final String LOG_TAG = "[GpsPlayback]";

    // gps 文件路径
    private String mGpsTrackPath;
    // gps 轨迹的起终点
    private NaviPoi mFrom, mTo;

    // 是否是84坐标系
    private boolean isLocation84 = true;

Because the listener has been monitored in GPSPlaybackEngine, it is necessary to fill in the navigation 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);
        }
    };

The onCreate method initializes the UI and adds 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 method

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 method, get the start and end points for calculating the way

    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;
        }
    }

Calculate the road 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();
                    }
                });
    }
    

Call the GpsPlaybackEngine method, perform listen positioning, and then start navigation

    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());
        }
    }

End navigation

    @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

This part is mainly to read GPS files and provide externally available add/removelistener methods, start/stopMockLocation methods
Because we want the engine to run on its own thread, we use the runnable mechanism

public class GpsPlaybackEngine implements Runnable{

            // 代码在下方
}

And the fields used

// 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;

Key method

  • listener related
    // 添加listener
    public void addTencentLocationListener(TencentLocationListener listener) {
        if (listener != null) {
            mTencentLocationListeners.add(listener);
        }
    }

    // 移除listener 
    public void removeTencentLocationListener(TencentLocationListener listener) {
        if (listener != null) {
            mTencentLocationListeners.remove(listener);
        }
    }
  • Start/close simulation track
    /*
     * 模拟轨迹
     * @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 related
 @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 method used

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 };
    }

The internal class MyTencentLocation implements the interface to locate the 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;
        }
    }

Show results

Finally, according to the recorded track (for the specific recording method, please refer to the last issue of Tencent Location Service Track Recording-Android ), play back the gps track from China Technology Exchange Building to Beijing West Railway Station, and display it through the navigation sdk as follows

tutieshi_640x1386_65s.gif


腾讯位置服务
1.7k 声望133 粉丝

立足生态,连接未来


引用和评论

0 条评论