preface

When we use maps for development, it is a very common means to check the accuracy of navigation by using recorded tracks for track playback. In addition, the recording of GPS track files during the use of maps has been finished in the last article. Now we will share the track playback using Tencent navigation SDK under Android system

preparation

Tencent navigation SDK relies on Tencent Map SDK and Tencent positioning SDK. To open specific permissions, you need to go to the console of lbs.qq.com. In addition, you can contact the assistant to consult the permissions of navigation SDK (as shown in the picture below)

Track playback positive

System architecture

The GPS playback system is divided into two parts: GPSPlaybackActivity and GPSPlaybackEngine. GPSPlayback is responsible for interaction with the outside world, primarily for information delivery and navigation SDKS, while GPSPlaybackEngine is responsible for reading files and filling anchor points into listeners via a multi-threaded runnable mechanism.

Start track playback

BaseNaviActivity.java

BaseNaviActivity is mainly for the navigation SDK naviView part of the life cycle management, must be implemented, otherwise can not carry out navigation!

/** * Navigation SDK {@link CarNaviView} initialization and cycle management class. */ public abstract class BaseNaviActivity { private static Context mApplicationContext; protected CarNaviView mCarNaviView; // Create the TencentCarNaviManager singleton, 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; }}Copy the code

GPSPlaybackActivity.java

This section is mainly about the initialization of the navigation SDK interaction and adding navigation UI. Note that the navigation SDK must calculate the path before starting navigation. Path calculation can obtain GPS file starting point, end point.

Used in the fields of

private static final String LOG_TAG = "[GpsPlayback]"; // GPS file path private String mGpsTrackPath; Private NaviPoi mFrom, mTo; Private Boolean isLocation84 = true;Copy the code

Because GPSPlaybackEngine is already listening, you need to dot the navigation SDK

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()); // mCarManagerSingleton is a singleton created using the navigation SDK's carNaviManager, 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); McArmanagersingleton.get ().updategpsStatus (provider, status, description); }};Copy the code

The onCreate method initializes the UI and adds the callback

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); MGpsTrackPath = getIntent().getStringExtra("gpsTrackPath"); if (mGpsTrackPath == null || mGpsTrackPath.isEmpty()) { return; } initUi(); addTencentCallback(); GpsLineStrs = readGpsFile(mGpsTrackPath); new Handler().post(() -> {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); / / overview mode route margins mCarNaviView setVisibleRegionMargin (margin, margin, margin, margin); mCarNaviView.setAutoScaleEnabled(true); mCarManagerSingleton.get().setMulteRoutes(true); mCarNaviView.setNaviMapActionCallback(mCarManagerSingleton.get()); / / use the default UI CarNaviInfoPanel CarNaviInfoPanel = mCarNaviView. ShowNaviInfoPanel (); carNaviInfoPanel.setOnNaviInfoListener(() -> { mCarManagerSingleton.get().stopNavi(); finish(); }); CarNaviInfoPanel.NaviInfoPanelConfig config = new CarNaviInfoPanel.NaviInfoPanelConfig(); config.setRerouteViewEnable(true); / / restart button 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) { } };Copy the code

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; }Copy the code

GetFromAndTo method, get the start and end as the calculation path

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; }}Copy the code

Calculate way searchAndStartNavigation ()

You can use the path calculation method of the navigation SDK and get the successful and failed callback of the path calculation private void searchAndStartNavigation() {McArmanagersingleton.get ().searchroute (new) TencentRouteSearchCallback () {@ Override public void onRouteSearchFailure (int, String s) {toast (" route planning failure "); } @Override public void onRouteSearchSuccess(ArrayList<RouteData> arrayList) { if (arrayList == null || Arraylist.isempty ()) {toast(" failed to recall route "); return; } handleGpsPlayback(); }}); }Copy the code

Call the GpsPlaybackEngine method, locate listen, and then start the navigation

Private void handleGpsPlayback() {// Interact with GpsPlaybackEngine, Add a locationListener GpsPlaybackEngine. GetInstance (). AddTencentLocationListener (the listener); / / interact with GpsPlaybackEngine, began to locate GpsPlaybackEngine. GetInstance (). StartMockTencentLocation (mGpsTrackPath isLocation84); try { mCarManagerSingleton.get().startNavi(0); } catch (Exception e) { toast(e.getMessage()); }}Copy the code

The end of the navigation

Override protected void onDestroy() {// Interact with GpsPlaybackEngine, removelocationListener mCarManagerSingleton.get().removeTencentNaviCallback(mTencentCallback); / / interact with GpsPlaybackEngine, end positioning GpsPlaybackEngine. GetInstance (). RemoveTencentLocationListener (the listener); GpsPlaybackEngine.getInstance().stopMockLocation(); If (McArmanagersingleton.get ().isnavigating ()) {// Navigating McArmanagersingleton.get ().stopnavi (); } super.onDestroy(); }Copy the code

GPSPlaybackEngine.java

This section mainly reads the GPS file and provides externally available add/removelistener methods. The Start /stopMockLocation method uses the Runnable mechanism because the engine is running on its own thread

Public class implements Runnable{// implements Runnable}Copy the code

And use the fields

// Tencent track Mock, TencentLocationListener use tencent positioning the SDK to obtain private ArrayList < TencentLocationListener > mTencentLocationListeners = new ArrayList<>(); 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;Copy the code

The key way to

  • The listener related
/ / add the listener public void addTencentLocationListener (TencentLocationListener listener) {if (listener! = null) { mTencentLocationListeners.add(listener); }} / / remove the listener public void removeTencentLocationListener (TencentLocationListener listener) {if (listener! = null) { mTencentLocationListeners.remove(listener); }}Copy the code
  • Start/close simulation track
*/ public void startTencentLocation (String fileName, Boolean is84) {// First clear previous data mdatas.clear (); // 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(); }}}Copy the code
*/ public void stoptencentLocation () {try {mIsReplaying = false; / / Public void stoptencentLocation () {try {mIsReplaying = false; mMockGpsProviderTask.join(); mMockGpsProviderTask = null; lastPointTime = 0; } catch (Exception e) { Log.e(TAG, "stopMockTencentLocation Exception", e); e.printStackTrace(); }}Copy the code
  • A 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();
            }
        }
    }
Copy the code

The 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; } 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); // If the position is not simulated, there may be an exception e.prinintStackTrace (); return false; } return true; }Copy the code

CoordinateConverter.wg84togcj02

Public static double[] public static double[] public static double[] public static double[] public static double[] 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 }; }Copy the code

The internal class MyTencentLocation implements locates the SDK interface

Class MyTencentLocation implements TencentLocation {/** * private double latitude = 0; /** * longitude */ private double longitude = 0; /** ** private float accuracy = 0; /** * private float direction = -1; /** * private float velocity = 0; /** * private long time = 0; /** * private double altitude = 0; Private String provider = ""; /** * 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; }}Copy the code

Results show

Finally, the GPS track from China Technology Exchange Building to Beijing West Railway Station will be played back according to the recorded track (specific recording method can be refer to the previous Tencent Location Service Track Recording – Android), which will be displayed through the navigation SDK as follows

Author: Tencent Location Service

Link: my.oschina.net/u/4209404/b…

Source: Open Source China

Copyright belongs to the author. Commercial reprint please contact the author for authorization, non-commercial reprint please indicate the source.