preface

When we use maps for development, it is a very common method to check the accuracy of navigation by using recorded tracks for track playback. In the last article, we have finished the recording of GPS track files when using maps. Now I would like to share the track playback using Tencent navigation SDK under Android system

preparation

Tencent navigation SDK relies on Tencent map SDK and Tencent positioning SDK, and the specific permissions need to be operated on the official website console of lbs.qq.com. In addition, you can contact Little Assistant for consultation about the permissions of navigation SDK (as shown in the figure below), which will not be discussed here

Track playback positive

System architecture

The GPS playback system is divided into two parts: GPSPlaybackActivity and GPSPlaybackEngine. The GPSPlayback is responsible for the interaction with the outside world, mainly the information transfer and the navigation SDK interaction, while the GPSPlaybackEngine is responsible for the specific read files and the registration points through the multi-threaded Runnable mechanism into the listener.

Start track playback

BaseNaviActivity.java

BasenaviActivity is mainly for the navigation SDK NaviView part of the lifecycle management, must be implemented, otherwise can not be navigated!

/** * Navigation SDK {@link CarNaviView} initializes the cycle management class. */ public abstract class BaseNaviActivity { private static Context mApplicationContext; protected CarNaviView mCarNaviView; // Set up the singleton pattern of TencentCarnavimanager, TencentCarnavimanager public static final Singleton< TencentCarnavimanager > > 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 section is mainly about the interaction with the navigation SDK and the initialization of adding the navigation UI. Note that the navigation SDK must first calculate the path before starting the navigation. Path calculation can obtain the first behavior of the GPS file starting point, end behavior end.

Used in the fields of

private static final String LOG_TAG = "[GpsPlayback]"; Private String mgpStrackPath; Private NaviPoi mFrom, mTo; private NaviPoi mFrom, mTo; Private Boolean isLocation84 = true; private Boolean isLocation84 = true;

Because the GPSPlaybackEngine is already listening, the navigation SDK needs to be irrigated

Private TencentLocationListener Listener = new TencentLocationListener() {@Override public void Listener(); private TencentLocationListener() 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()); // The MCARManagerSingleton is a singleton created using the navigation SDK's Carnavimanager. MCARManagerSingleton. Get ().updateLocation(converthelper.convertToGPSLocation (tencentLocation), error, mCARManagerSingleton. reason); } @Override public void onStatusUpdate(String provider, int status, String description) { Log.d(LOG_TAG, "onStatusUpdate provider: " + provider + ", status: " + status + ", desc: " + description); // Update GPS status.mCARManagerSingleton.get ().updateGPSStatus (provider, status, description); }};

The onCreate method initializes the UI and adds a callback

@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); MgPStrackPath = getIntent().getStringExtra(" GPStrackPath "); if (mGpsTrackPath == null || mGpsTrackPath.isEmpty()) { return; } initUi(); addTencentCallback(); 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) { } };

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 point to calculate the 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; }}

Calculate way searchAndStartNavigation ()

Private void searchAndStartNavigation() {mCARManagerSingleton.get ().searchroute (new); private void searchAndStartNavigation() {mCARManagerSingleton 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(); }}); }

Call the GPSPlaybackEngine method, locate the Listen, and start the navigation

Private void handlegPSPlayback () {// Interact with the 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()); }}

The end of the navigation

Override protected void onDestroy() {// Interact with the GPSPlaybackEngine, removelocationListener mCarManagerSingleton.get().removeTencentNaviCallback(mTencentCallback); / / interact with GpsPlaybackEngine, end positioning GpsPlaybackEngine. GetInstance (). RemoveTencentLocationListener (the listener); GpsPlaybackEngine.getInstance().stopMockLocation(); If (McManagerSingleton. Get ().Navigating ()) {// Navigate the McManagerSingleton. Get ().StopNavi (); } super.onDestroy(); }

GPSPlaybackEngine.java

This section mainly reads the GPS file and provides the Add/RemoveListener method available to the outside world. The Start/StopmockLocation method uses the Runnable mechanism because we want the Engine to run on its own thread

Public class GPSPlaybackEngine implements Runnable{// public class GPSPlaybackEngine implements Runnable

The fields that are used

// Tencent trajectory Mock TencentLocationListener use tencent positioning the SDK to obtain private ArrayList < TencentLocationListener > mTencentLocationListeners = new ArrayList<>(); Private List<String bb0 mDatas = new ArrayList<String>(); private boolean mIsReplaying = false; private boolean mIsMockTencentLocation = true; private Thread mMockGpsProviderTask = null; // Pause private Boolean mPause = false; private double lastPointTime = 0; private double sleepTime = 0;

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); }}
  • Start/close the simulated trajectory
/* * public void startMockTencentLocation(String fileName, String fileName, String fileName) Boolean is84) {// First clear the previous data mdatas.clear (); // MockTencentLocation =! 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; } // start asynchronous Thread MockGPSProviderTask = 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(); }}}
Public void StopMockTencentLocation () {try {misRePlaying = false; public void StopMockTencentLocation () {misRePlaying = false; mMockGpsProviderTask.join(); mMockGpsProviderTask = null; lastPointTime = 0; } catch (Exception e) { Log.e(TAG, "stopMockTencentLocation Exception", e); e.printStackTrace(); }}
  • 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();
            }
        }
    }

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 location emulation is not on, an exception may occur here e.printStackTrace(); return false; } return true; }

CoordinateConverter.wg84togcj02

/** * @param LNG WGS84 * @param LAT WGS84 * @return Mars */ 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 inner class MyTencentLocation implements locates the SDK’s interface

Class MyTencentLocation implements TencentLocation {/** * Latitude */ private Double Latitude = 0; Private double longitude = 0; /** ** private double longitude = 0; Private float accuracy = 0; private float accuracy = 0; Private float direction = -1; private float direction = -1; Private float velocity = 0; private float velocity = 0; /** ** / private long time = 0; Private Double Altitude = 0; /** * private Double Altitude = 0; /** * private String provider = ""; Private int RSSI = 0; private int RSSI = 0; /** * private float PhoneDirection = -1; /** * 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; }}

Results show

Finally, according to the recorded track (please refer to the specific recording method of Tencent Location Service Track Recording – Android in the last issue), the GPS track from China Technology Exchange Building to Beijing West Railway Station was played back, and the navigation SDK was used to display it as follows