利用百度地图开源代码实现定位和实时路径跟踪
更新更新(2020.8.19)
今天刚调试出来了,关于把前面的模拟实时路径跟踪部分修改成了监听手机内部GPS的实时跟踪,能拿着手机在室外跑来跑去,然后定时回调定位监听器的方法,获取到新的经纬度信息,然后边画线,能实现实时路径跟踪的功能了!!!
先给大家演示一下效果
这里最左边的点,是因为一开始跟踪的时候,会接收一个经纬度,但是可能是因为一开始接收GPS信号的时候不太准确,所以会有一个偏差,而且还是偏差有点大的,但是后面的话,相对来说还是大致可以描绘出来我行走的路线的,这个也是GPS定位本来就不太精准的原因吧?
实现实时路径跟踪,我这里是把原来调用模拟位置的函数,改成了回调监听定位函数的代码,就是不断通过重新获取当前经纬度的监听函数,再把这个获取到的点跟前一个点通过画线的方法进行连线;大概这样就能实现实时监听了。
上代码:(这个就是mainActivity文件,其他的xml文件啥的都没变化)
package com.example.map_track;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.location.Address;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.mapapi.CoordType;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.BitmapDescriptor;
import com.baidu.mapapi.map.BitmapDescriptorFactory;
import com.baidu.mapapi.map.InfoWindow;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.Marker;
import com.baidu.mapapi.map.MarkerOptions;
import com.baidu.mapapi.map.MyLocationConfiguration;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.map.Overlay;
import com.baidu.mapapi.map.OverlayOptions;
import com.baidu.mapapi.map.Polyline;
import com.baidu.mapapi.map.PolylineOptions;
import com.baidu.mapapi.model.LatLng;
import com.baidu.mapapi.search.core.SearchResult;
import com.baidu.mapapi.search.geocode.GeoCodeResult;
import com.baidu.mapapi.search.geocode.GeoCoder;
import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener;
import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption;
import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
public static final int PLAYBACK_OVER = 1;
private MapView mapView;
private BaiduMap baiduMap;
private LocationClient mLocationClient = null; //定义一个监听器对象
private MyLocationListener myLocationListener;
private double currentLat, currentLng; //当前的经纬度
private String currentAddr; //当前所在的地址
private DatabaseAdapter dbAdapter;
private GeoCoder geoCoder;
private int currentTrackLineID; //当前跟踪的路线ID
// BDLocation location;
boolean flag = true;
boolean isFirstLocation = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.bmapView);
initBaiduMap(); //初始化地图,显示我的当前位置
dbAdapter = new DatabaseAdapter(this);
}
/**
* 初始化百度地图
*/
LocationClientOption option = new LocationClientOption();
private void initBaiduMap() {
baiduMap = mapView.getMap();
baiduMap.setMyLocationEnabled(true); //打开定位图层,允许定位
mLocationClient = new LocationClient(getApplicationContext()); //声明LocationClient类
myLocationListener = new MyLocationListener();
mLocationClient.registerLocationListener(myLocationListener); //注册监听函数
//LocationClientOption option = new LocationClientOption();
option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); //设置定位模式
option.setOpenGps(true);
option.setCoorType("bd09ll"); //返回的定位结果是百度经纬度,默认值gcj02
option.setScanSpan(1000); //设置发起定位请求的间隔为5000ms
option.setIsNeedAddress(true); //返回的定位结果包含地址信息
option.setNeedDeviceDirect(true); //返回的定位结果包含手机机头方向
option.setWifiCacheTimeOut(5*60*1000);
option.setNeedNewVersionRgc(true);
mLocationClient.setLocOption(option);
mLocationClient.start(); //启动SDK定位
mLocationClient.requestLocation(); //发起定位请求
isFirstLocation = true;
//用于转换地理编码的监听器
geoCoder = GeoCoder.newInstance();
geoCoder.setOnGetGeoCodeResultListener(new OnGetGeoCoderResultListener() {
@Override
public void onGetReverseGeoCodeResult(ReverseGeoCodeResult result) {
if (result == null || result.error != SearchResult.ERRORNO.NO_ERROR) {
//没有检索结果
}
else {
//获取地理编码结果
//System.out.println(result.getAddress());
currentAddr = result.getAddress();
//更新线路的结束位置
dbAdapter.updateEndLoc(currentAddr, currentTrackLineID);
}
}
@Override
public void onGetGeoCodeResult(GeoCodeResult arg0) {
}
});
}
double cur_Lat = 0;
double cur_Lng = 0;
BDLocation location1;
/**
* 定位的监听
*/
class MyLocationListener extends BDAbstractLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
if (location != null && flag) {
flag = false;
currentLat = location.getLatitude(); //当前的纬度
currentLng = location.getLongitude(); //当前的经度
currentAddr = location.getAddrStr(); //当前位置的地址
// cur_Lat = currentLat;
// cur_Lng = currentLng;
Log.i("定位的纬度 = ", String.valueOf(currentLat));
Log.i("定位的经度 = ", String.valueOf(currentLng));
if (isFirstLocation) {
isFirstLocation = false;
//System.out.println("currentAddr=" + currentAddr);
//构造我的当前位置信息
MyLocationData.Builder builder = new MyLocationData.Builder();
builder.latitude(location.getLatitude()); //设置纬度
builder.longitude(location.getLongitude()); //设置经度
builder.accuracy(location.getRadius()); //设置精度(半径)
builder.direction(location.getDirection()); //设置方向
builder.speed(location.getSpeed()); //设置速度
MyLocationData locationData = builder.build();
//把我的位置信息设置到地图上
baiduMap.setMyLocationData(locationData);
//配置我的位置
LatLng latlng = new LatLng(currentLat , currentLng);
baiduMap.setMyLocationConfiguration(new MyLocationConfiguration(MyLocationConfiguration.LocationMode.FOLLOWING,true,null));
//设置我的位置为地图的中心点,并且把地图放大16倍(缩放级别为3-20)
baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngZoom(latlng, 20));
System.out.println("地址:"+currentAddr+"---纬度:"+currentLat+"---经度:"+currentLng+"\n");
Log.i("hello2",currentAddr);
Log.i("hello3", String.valueOf(currentLat));
Log.i("hello4", String.valueOf(currentLng));
Log.i("hello1", String.valueOf(location));
}
}
}
private void getlocation() {
// currentLat = cur_Lat;
// currentLng = cur_Lng;
Log.i("现在纬度 = ", String.valueOf(cur_Lat));
Log.i("现在经度 = ", String.valueOf(cur_Lng));
// baiduMap.setMyLocationEnabled(false);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
//功能菜单项
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mylocation :
mylocation(); //我的位置
break;
case R.id.start_track :
startTrack(); //开始跟踪
break;
case R.id.end_track :
endTrack(); //结束跟踪
break;
// case R.id.track_back :
// trackBack(); //跟踪回放
// break;
default:
break;
}
return true;
}
/**
* 结束跟踪
*/
private void endTrack() {
isTracking = false; //结束线程
Toast.makeText(MainActivity.this, "跟踪结束...", Toast.LENGTH_SHORT).show();
//转换地理编码,把最后的一个经纬度转换成地址(把经纬度转化成对应的地名地址,或者把地名地址转换成对应的经纬度)
geoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(new LatLng(currentLat, currentLng)));
}
/**
* 我的位置
*/
private void mylocation() {
Toast.makeText(MainActivity.this, "正在定位中...", Toast.LENGTH_SHORT).show();
flag = true;
baiduMap.clear(); //清除地图上自定义的图层
baiduMap.setMyLocationEnabled(true);
mLocationClient.requestLocation(); //发起定位请求
}
/**
* 开始跟踪功能
*/
private void startTrack() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("线路跟踪");
builder.setCancelable(true);
final View view = getLayoutInflater().inflate(R.layout.add_track_line_dialog, null);
builder.setView(view);
builder.setPositiveButton("添加", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EditText et_track_name = (EditText) view.findViewById(R.id.editText1_track_name);
String trackName = et_track_name.getText().toString();
System.out.println(trackName);
createTrack(trackName); //创建线路跟踪
Toast.makeText(MainActivity.this, "跟踪开始...", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
private boolean isTracking = false;
//创建一条线路跟踪
private void createTrack(String trackName) {
Track track = new Track();
track.setTrack_name(trackName);
track.setCreate_date(DateUtils.toDate(new Date())); //创建时间为当前的时间
track.setStart_loc(currentAddr);
currentTrackLineID = dbAdapter.addTrack(track); //添加线路,返回线路ID
dbAdapter.addTrackDetail(currentTrackLineID, currentLat, currentLng); //添加明细信息
baiduMap.clear();
addOverlay();
list.add(new LatLng(currentLat, currentLng));
isTracking = true; //线程模拟的标记
System.out.println(list);
new Thread(new TrackThread()).start();
}
//在地图上添加图层(线路的每一个点)
private void addOverlay() {
//--------添加一个标注覆盖物在当前位置--------
//构建Marker图标
baiduMap.setMyLocationEnabled(false); //关闭定位图层
BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(R.mipmap.icon_markx);
//构建MakerOption,用于在地图上添加Marker
LatLng latlng = new LatLng(currentLat, currentLng);
OverlayOptions option = new MarkerOptions().position(latlng).icon(bitmap);
//在地图上添加Marker,并显示
baiduMap.addOverlay(option);
//把当前添加的地图位置作为地图的中心点
baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLng(latlng));
}
//用于存储两个相邻点的经纬度,再画线
private ArrayList<LatLng> list = new ArrayList<LatLng>();
//模拟跟踪的线程
class TrackThread implements Runnable {
@Override
public void run() {
while (isTracking) {
// BDLocation location3 = new BDLocation();
//myLocationListener.getlocation();
baiduMap.setMyLocationEnabled(true);
flag = true;
myLocationListener.onReceiveLocation(location1);
baiduMap.setMyLocationEnabled(false);
Log.i("准备画线前的纬度 = ", String.valueOf(currentLat));
Log.i("准备画线前的经度 = ", String.valueOf(currentLng));
dbAdapter.addTrackDetail(currentTrackLineID, currentLat, currentLng);
addOverlay();
list.add(new LatLng(currentLat, currentLng));
drawLine(); //画线
System.out.println("drawLine");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程休眠失败");
}
// Log.i("hello", String.valueOf(location5));
}
}
}
//在两个点之间画线
private void drawLine() {
OverlayOptions lineOptions = new PolylineOptions().points(list).color(0xFFFF0000);
baiduMap.addOverlay(lineOptions);
list.remove(0); //这里是为了移除两个点之间的前一个,因为在第一次画线之后,每一次画线都只需要添加第二个点,但是它这里是默认把两个点都添加进去,再画线,所以需要删除第一个点
}
@Override
protected void onDestroy() {
super.onDestroy();
//在Activity执行onResume时执行mMapView.onResume(),实现地图生命周期管理
mapView.onDestroy();
}
@Override
protected void onResume() {
super.onResume();
//在Activity执行onResume时执行mMapView.onResume(),实现地图生命周期管理
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//在Activity执行onPause时执行mMapView.onPause(),实现地图生命周期管理
mapView.onPause();
}
}
程序还是有一点问题的,我这里设置了一个标志位isFirstLocation,这样每次回调定位监听函数的时候,就只会接收新的经纬度信息,但是因为设置了这个标志位,所以后面结束跟踪之后,就不能重新把当前的位置定位到地图的中心点;
这个问题的话,我是想着可以设置一个按钮,每次点击这个按钮的时候,就把地图clear一下,把图层都清除了,然后再重新初始化一下定位的方法,这样大概就能实现重新定位到当前位置了。
以上是我这几天调程序的思路,有什么不对的地方,还需要大神们帮忙指出来呀!!网上有demo是采用的鹰眼服务的,但是怎么去调用鹰眼服务这一块,我还没有搞懂,所以我是直接通过的定时回调定位监听函数的方法实现的。
如果有大神知道怎么使用鹰眼服务的话,可以给我提供一些思路吗?
8月18更新:
现在还在卡着,对于怎么把模拟经纬度变化的函数转换成监听手机内部GPS信号的回调函数,这一块我还是没有想明白,我后面是加上了鹰眼服务,但是运行起来还是调用逻辑上有问题,一开始跟踪就会闪退,有大神可以给我提供一些意见和思路吗?
这段时间需要用Android Studio做一个基于百度地图的app,记录一下过程,主要实现了定位和实时路径跟踪。
先演示一下效果:(视频放不了,图片凑凑数)
实时路径的思想和步骤:
思想:如果需要在地图上实现实时跟踪,那么首先就需要实现定位功能,在实现了定位功能的基础上,需要在地图上画出行走轨迹,那么如何画出行走轨迹?就需要实时地记录行走的经纬度,两点之间画一条直线,中间设置定位的频率高的话,那么记录的经纬度就越多,这样就可以让记录的行走路径光滑一些。
如果需要回放功能,就需要建立一个数据库将行走线路存储下来,数据库就应该要一条线路对应多个点的关系,所以需要两张表,一个表是记录的线路,另一个表是记录的一条线路对应的多个点。(回放功能我还没有实现)
先添加一下权限和密钥之类的吧,先修改好manifests.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.map_track"
android:versionCode="1"
android:versionName="1.0">
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<!-- 这个权限用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"></uses-permission>
<!-- 这个权限用于访问GPS定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"></uses-permission>
<!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<!-- 获取运营商信息,用于支持提供运营商信息相关的接口 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" ></uses-permission>
<!-- 这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<!-- 用于读取手机当前的状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" ></uses-permission>
<!-- 写外置存储。如果开发者使用了离线地图,并且数据写在外置存储区域,则需要申请该权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" ></uses-permission>
<!-- 访问网络,进行地图相关业务数据请求,包括地图数据,路线规划,POI检索等 -->
<uses-permission android:name="android.permission.INTERNET" ></uses-permission>
<!-- SD卡读取权限,用户写入离线定位数据 -->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>
<!-- 允许应用读取低级别的系统日志文件-->
<uses-permission android:name="android.permission.READ_LOGS"></uses-permission>
<!-- 读取外置存储。如果开发者使用了so动态加载功能并且把so文件放在了外置存储区域,则需要申请该权限,否则不需要 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher_background"
android:label="线路跟踪案例"
android:theme="@style/AppTheme" >
<meta-data
android:name="com.baidu.lbsapi.API_KEY"
android:value="Dt7DLSza4fP0NBGMEMjF2IRNaOtXqqn6" ></meta-data>
<activity
android:name="com.example.map_track.MainActivity"
android:label="线路跟踪案例" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote"> </service>
</application>
</manifest>
再就是设置一下布局文件activity_main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
tools:context=".MainActivity">
<!--
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
-->
<com.baidu.mapapi.map.MapView
android:id="@+id/bmapView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true" />
</RelativeLayout>
(以下几个类都是在app/java/com.example.map_track包中完成)
首先就要建立一个线路类Track,用于存储每条线路的信息;
package com.example.map_track;
import java.util.ArrayList;
/**
*线路跟踪对象
**/
public class Track { //线路
private int id;
private String track_name;
private String create_date;
private String start_loc;
private String end_loc;
private ArrayList<TrackDetail> trackDetails;
public ArrayList<TrackDetail> getTrackDetails() {
return trackDetails;
}
public void setTrackDetails(ArrayList<TrackDetail> trackDetails) {
this.trackDetails = trackDetails;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getTrack_name() {
return track_name;
}
public void setTrack_name(String track_name) {
this.track_name = track_name;
}
public String getCreate_date() {
return create_date;
}
public void setCreate_date(String create_date) {
this.create_date = create_date;
}
public String getStart_loc() {
return start_loc;
}
public void setStart_loc(String start_loc) {
this.start_loc = start_loc;
}
public String getEnd_loc() {
return end_loc;
}
public void setEnd_loc(String end_loc) {
this.end_loc = end_loc;
}
public Track(int id, String track_name, String create_date,String start_loc, String end_loc) {
super();
this.id = id;
this.track_name = track_name;
this.create_date = create_date;
this.start_loc = start_loc;
this.end_loc = end_loc;
}
public Track(String track_name, String create_date, String start_loc, String end_loc) {
super();
this.track_name = track_name;
this.create_date = create_date;
this.start_loc = start_loc;
this.end_loc = end_loc;
}
public Track() {
super();
}
}
线路类Track建立好了之后,还需要建立一个路线结点类TrackDetail,用于存储线路上每个点的信息;
package com.example.map_track;
public class TrackDetail {
private int id;
private double lat;
private double lng;
private Track track;
public int getId() {
return id;
}
public void setId(int id) { this.id = id; }
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
public Track getTrack() {
return track;
}
public void setTrack(Track track) {
this.track = track;
}
public TrackDetail(int id, double lat, double lng) {
super();
this.id = id;
this.lat = lat;
this.lng = lng;
}
}
还需要设置一个数据库助手类DatabaseHelper:
package com.example.map_track;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
数据助手类
**/
public class DatabaseHelper extends SQLiteOpenHelper {
private static String DB_NAME = "track.db";
//表名
public static String TABLE_TRACK = "track";
public static String TABLE_TRACK_DETAIL = "track_detail";
//字段
public static String ID = "_id";
//跟踪表
public static String TRACK_NAME = "track_name";
public static String CREATE_DATE = "create_date";
public static String START_LOC = "start_loc";
public static String END_LOC = "end_loc";
//明细表
public static String TID = "tid"; //线路的ID
public static String LAT = "lat"; //纬度
public static String LNG = "lng"; //经度
private static String CREATE_TABLE_TRACK = "create table track(_id integer primary key autoincrement,track_name text,create_date text,start_loc text,end_loc text)";
private static String CREATE_TABLE_TRACK_DETAIL = "create table track_detail(_id integer primary key autoincrement,tid integer not null,lat real,lng real)";
private static int VERSION = 1;
public DatabaseHelper(Context context) {
super(context,DB_NAME,null,VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_TRACK);
db.execSQL(CREATE_TABLE_TRACK_DETAIL);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (newVersion > oldVersion) {
db.execSQL("drop table if exists track");
db.execSQL("drop table if exists track_detail");
db.execSQL(CREATE_TABLE_TRACK);
db.execSQL(CREATE_TABLE_TRACK_DETAIL);
}
}
}
再需要建立一个databaseAdapter类,用于数据库的增删改查:
package com.example.map_track;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
/**
*数据库适配器
**/
public class DatabaseAdapter {
private DatabaseHelper dbHelper;
public DatabaseAdapter(Context context) {
dbHelper = new DatabaseHelper(context);
}
//添加线路跟踪
public int addTrack(Track track) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
//String sql = "insert to track(track_name,create_date,start_loc,end_loc) values(?,?,?,?)";
//db.exceSQL(sql,new Object[]{track.getTrack_name(),track.getCreate_date(),track.getStart_loc(),track.getEnd_loc()});
ContentValues values = new ContentValues();
values.put(DatabaseHelper.TRACK_NAME, track.getTrack_name());
values.put(DatabaseHelper.CREATE_DATE, track.getCreate_date());
values.put(DatabaseHelper.START_LOC, track.getStart_loc());
values.put(DatabaseHelper.END_LOC, track.getEnd_loc());
long id = db.insertOrThrow(DatabaseHelper.TABLE_TRACK, null, values);
db.close();
return (int) id;
}
/**
*更新终点地址
**/
//刚开始走的时候,起点和终点是同一个位置,当结束的时候,终点才变了,这个时候需要一个更新终点位置的函数
public void updateEndLoc(String endLoc, int id) {
String sql = "update track set end_loc=? where _id=?";
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.execSQL(sql, new Object[] {endLoc, id});
db.close();
}
//添加路线跟踪明细
public void addTrackDetail(int tid, double lat, double lng ) { //tid为外键
SQLiteDatabase db= dbHelper.getWritableDatabase();
String sql = "insert into track_detail(tid,lat,lng) values(?,?,?)";
db.execSQL(sql, new Object[] {tid ,lat, lng});
db.close();
}
//根据ID查询线路跟踪
public ArrayList<TrackDetail> getTrackDetails(int id) {
String sql = "select _id,lat,lng from track_detail where tid=? order by _id desc";
ArrayList<TrackDetail> list = new ArrayList<TrackDetail>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor c= db.rawQuery(sql, new String[] { String.valueOf(id) });
if (c != null) {
TrackDetail detail = null;
while (c.moveToNext()) {
detail = new TrackDetail(c.getInt(0), c.getDouble(1),c.getDouble(2));
list.add(detail);
}
c.close();
}
return list;
}
/**
查询所有线路
**/
public ArrayList<Track> getTracks() {
ArrayList<Track> tracks = new ArrayList<Track>();
String sql = "select _id,track_name,create_date,start_loc,end_loc from track ";
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor c = db.rawQuery(sql, null);
Track t = null;
if (c != null) {
while (c.moveToNext()) {
t = new Track(c.getInt(0), c.getString(1), c.getString(2), c.getString(3), c.getString(4));
tracks.add(t);
}
c.close();
}
db.close();
return tracks;
}
//根据ID删除线路跟踪
public void delTrack(int id) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
String sql1 = "delete from track where _id=?";
String sql2 = "delete from track_detail where tid=?";
try {
db.beginTransaction();
db.execSQL(sql2, new Object[] { id });
db.execSQL(sql1, new Object[] { id });
db.setTransactionSuccessful();
}
catch (Exception e) {
e.printStackTrace();
}
finally {
db.endTransaction();
if( db != null)
db.close();
}
}
}
再需要设置一个DateUtils类,用于日期转换:
package com.example.map_track;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*日期转换的工具类
**/
public class DateUtils {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static String toDate(Date date) {
return sdf.format(date);
}
}
接下来就是核心的类MainActivity:
package com.example.map_track;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import com.baidu.location.BDAbstractLocationListener;
import com.baidu.location.BDLocation;
import com.baidu.location.BDLocationListener;
import com.baidu.location.LocationClient;
import com.baidu.location.LocationClientOption;
import com.baidu.mapapi.CoordType;
import com.baidu.mapapi.SDKInitializer;
import com.baidu.mapapi.map.BaiduMap;
import com.baidu.mapapi.map.BitmapDescriptor;
import com.baidu.mapapi.map.BitmapDescriptorFactory;
import com.baidu.mapapi.map.InfoWindow;
import com.baidu.mapapi.map.MapStatusUpdate;
import com.baidu.mapapi.map.MapStatusUpdateFactory;
import com.baidu.mapapi.map.MapView;
import com.baidu.mapapi.map.Marker;
import com.baidu.mapapi.map.MarkerOptions;
import com.baidu.mapapi.map.MyLocationConfiguration;
import com.baidu.mapapi.map.MyLocationData;
import com.baidu.mapapi.map.Overlay;
import com.baidu.mapapi.map.OverlayOptions;
import com.baidu.mapapi.map.Polyline;
import com.baidu.mapapi.map.PolylineOptions;
import com.baidu.mapapi.model.LatLng;
import com.baidu.mapapi.search.core.SearchResult;
import com.baidu.mapapi.search.geocode.GeoCodeResult;
import com.baidu.mapapi.search.geocode.GeoCoder;
import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener;
import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption;
import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
public static final int PLAYBACK_OVER = 1;
private MapView mapView;
private BaiduMap baiduMap;
private LocationClient mLocationClient = null; //定义一个监听器对象
private MyLocationListener myLocationListener;
private double currentLat, currentLng; //当前的经纬度
private String currentAddr; //当前所在的地址
private DatabaseAdapter dbAdapter;
private GeoCoder geoCoder;
private int currentTrackLineID; //当前跟踪的路线ID
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SDKInitializer.initialize(getApplicationContext());
setContentView(R.layout.activity_main);
mapView = (MapView) findViewById(R.id.bmapView);
initBaiduMap(); //初始化地图,显示我的当前位置
dbAdapter = new DatabaseAdapter(this);
}
/**
* 初始化百度地图
*/
private void initBaiduMap() {
baiduMap = mapView.getMap();
baiduMap.setMyLocationEnabled(true); //打开定位图层,允许定位
mLocationClient = new LocationClient(getApplicationContext()); //声明LocationClient类
myLocationListener = new MyLocationListener();
mLocationClient.registerLocationListener(myLocationListener); //注册监听函数
LocationClientOption option = new LocationClientOption();
option.setLocationMode(LocationClientOption.LocationMode.Hight_Accuracy); //设置定位模式
option.setCoorType("bd09ll"); //返回的定位结果是百度经纬度,默认值gcj02
option.setScanSpan(5000); //设置发起定位请求的间隔为5000ms
option.setIsNeedAddress(true); //返回的定位结果包含地址信息
option.setNeedDeviceDirect(true); //返回的定位结果包含手机机头方向
mLocationClient.setLocOption(option);
mLocationClient.start(); //启动SDK定位
mLocationClient.requestLocation(); //发起定位请求
//用于转换地理编码的监听器
geoCoder = GeoCoder.newInstance();
geoCoder.setOnGetGeoCodeResultListener(new OnGetGeoCoderResultListener() {
@Override
public void onGetReverseGeoCodeResult(ReverseGeoCodeResult result) {
if (result == null || result.error != SearchResult.ERRORNO.NO_ERROR) {
//没有检索结果
}
else {
//获取地理编码结果
//System.out.println(result.getAddress());
currentAddr = result.getAddress();
//更新线路的结束位置
dbAdapter.updateEndLoc(currentAddr, currentTrackLineID);
}
}
@Override
public void onGetGeoCodeResult(GeoCodeResult arg0) {
}
});
}
boolean flag = true;
/**
* 定位的监听
*/
class MyLocationListener extends BDAbstractLocationListener {
@Override
public void onReceiveLocation(BDLocation location) {
if (location != null && flag) {
flag = false;
currentLat = location.getLatitude(); //当前的纬度
currentLng = location.getLongitude(); //当前的经度
currentAddr = location.getAddrStr(); //当前位置的地址
//System.out.println("currentAddr=" + currentAddr);
//构造我的当前位置信息
MyLocationData.Builder builder = new MyLocationData.Builder();
builder.latitude(location.getLatitude()); //设置纬度
builder.longitude(location.getLongitude()); //设置经度
builder.accuracy(location.getRadius()); //设置精度(半径)
builder.direction(location.getDirection()); //设置方向
builder.speed(location.getSpeed()); //设置速度
MyLocationData locationData = builder.build();
//把我的位置信息设置到地图上
baiduMap.setMyLocationData(locationData);
//配置我的位置
LatLng latlng = new LatLng(currentLat , currentLng);
baiduMap.setMyLocationConfiguration(new MyLocationConfiguration(MyLocationConfiguration.LocationMode.FOLLOWING,true,null));
//设置我的位置为地图的中心点,并且把地图放大16倍(缩放级别为3-20)
baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLngZoom(latlng, 20));
//System.out.println("地址:"+currentAddr+"---纬度:"+currentLat+"---经度:"+currentLng+"\n");
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
//功能菜单项
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mylocation :
mylocation(); //我的位置
break;
case R.id.start_track :
startTrack(); //开始跟踪
break;
case R.id.end_track :
endTrack(); //结束跟踪
break;
// case R.id.track_back :
// trackBack(); //跟踪回放
// break;
default:
break;
}
return true;
}
/**
* 结束跟踪
*/
private void endTrack() {
isTracking = false; //结束线程
Toast.makeText(MainActivity.this, "跟踪结束...", Toast.LENGTH_SHORT).show();
//转换地理编码,把最后的一个经纬度转换成地址(把经纬度转化成对应的地名地址,或者把地名地址转换成对应的经纬度)
geoCoder.reverseGeoCode(new ReverseGeoCodeOption().location(new LatLng(currentLat, currentLng)));
}
/**
* 我的位置
*/
private void mylocation() {
Toast.makeText(MainActivity.this, "正在定位中...", Toast.LENGTH_SHORT).show();
flag = true;
baiduMap.clear(); //清除地图上自定义的图层
baiduMap.setMyLocationEnabled(true);
mLocationClient.requestLocation(); //发起定位请求
}
/**
* 开始跟踪功能
*/
private void startTrack() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("线路跟踪");
builder.setCancelable(true);
final View view = getLayoutInflater().inflate(R.layout.add_track_line_dialog, null);
builder.setView(view);
builder.setPositiveButton("添加", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
EditText et_track_name = (EditText) view.findViewById(R.id.editText1_track_name);
String trackName = et_track_name.getText().toString();
System.out.println(trackName);
createTrack(trackName); //创建线路跟踪
Toast.makeText(MainActivity.this, "跟踪开始...", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builder.show();
}
//创建一条线路跟踪
private void createTrack(String trackName) {
Track track = new Track();
track.setTrack_name(trackName);
track.setCreate_date(DateUtils.toDate(new Date())); //创建时间为当前的时间
track.setStart_loc(currentAddr);
currentTrackLineID = dbAdapter.addTrack(track); //添加线路,返回线路ID
dbAdapter.addTrackDetail(currentTrackLineID, currentLat, currentLng); //添加明细信息
// baiduMap.clear();
addOverlay();
list.add(new LatLng(currentLat, currentLng));
isTracking = true; //线程模拟的标记
System.out.println(list);
new Thread(new TrackThread()).start();
}
private boolean isTracking = false;
//模拟跟踪的线程
class TrackThread implements Runnable {
@Override
public void run() {
while (isTracking) {
getLocation(); //获取模拟位置
dbAdapter.addTrackDetail(currentTrackLineID, currentLat, currentLng);
addOverlay();
list.add(new LatLng(currentLat, currentLng));
drawLine(); //画线
System.out.println("drawLine");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//用于存储两个相邻点的经纬度,再画线
private ArrayList<LatLng> list = new ArrayList<LatLng>();
//在地图上添加图层(线路的每一个点)
private void addOverlay() {
//--------添加一个标注覆盖物在当前位置--------
//构建Marker图标
baiduMap.setMyLocationEnabled(false); //关闭定位图层
BitmapDescriptor bitmap = BitmapDescriptorFactory.fromResource(R.mipmap.ic_launcher_round);
//构建MakerOption,用于在地图上添加Marker
LatLng latlng = new LatLng(currentLat, currentLng);
OverlayOptions option = new MarkerOptions().position(latlng).icon(bitmap);
//在地图上添加Marker,并显示
baiduMap.addOverlay(option);
//把当前添加的地图位置作为地图的中心点
baiduMap.animateMapStatus(MapStatusUpdateFactory.newLatLng(latlng));
}
//在两个点之间画线
private void drawLine() {
OverlayOptions lineOptions = new PolylineOptions().points(list).color(0xFFFF0000);
baiduMap.addOverlay(lineOptions);
list.remove(0); //这里是为了移除两个点之间的前一个,因为在第一次画线之后,每一次画线都只需要添加第二个点,但是它这里是默认把两个点都添加进去,再画线,所以需要删除第一个点
}
/**
* 模拟位置,真实的时候应该使用监听器来获取地理位置信息
*/
private void getLocation() {
currentLat = currentLat + Math.random() / 1000;
currentLng = currentLng + Math.random() / 1000;
}
@Override
protected void onDestroy() {
super.onDestroy();
//在Activity执行onResume时执行mMapView.onResume(),实现地图生命周期管理
mapView.onDestroy();
}
@Override
protected void onResume() {
super.onResume();
//在Activity执行onResume时执行mMapView.onResume(),实现地图生命周期管理
mapView.onResume();
}
@Override
protected void onPause() {
super.onPause();
//在Activity执行onPause时执行mMapView.onPause(),实现地图生命周期管理
mapView.onPause();
}
}
这里要设置一个菜单项,当点击菜单项的时候,就可以弹出多个选项,实现对应的功能:
需要在app/res下创建一个menu文件夹,在menu下再创建一个main.xml文件;
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/mylocation"
android:orderInCategory="10"
app:showAsAction="never"
android:title="我的位置" />
<item
android:id="@+id/start_track"
android:orderInCategory="100"
android:title="开始跟踪"
app:showAsAction="never" />
<item
android:id="@+id/end_track"
android:orderInCategory="200"
android:title="结束跟踪"
app:showAsAction="never" />
<item
android:id="@+id/track_back"
android:orderInCategory="300"
android:title="跟踪回放"
app:showAsAction="never" />
</menu>
MainActivity的大致执行过程:
首先就是SDK的初始化,然后初始化地图,也就是调用initBaiduMap方法,设置定位的模式,发起定位请求, 在请求的MyLocationListener回调方法里面,就可以得到经纬度信息,并显示在我的位置上;
接下来就是点击startTrack(),这是开始跟踪的方法,这里会调用add_track_line_dialog布局文件,所以还需要在res/layout文件夹中创建一个add_track_line_dialog.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="16dp"
android:orientation="vertical" >
<EditText
android:id="@+id/editText1_track_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入跟踪线路名称"
android:ems="10"
android:layout_marginBottom="5dp" >
<requestFocus />
</EditText>
<EditText
android:id="@+id/editText2_start_loc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="起始位置"
android:cursorVisible="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:ems="10" />
</LinearLayout>
接下来会调用createTrack方法,这是一个创建线路跟踪的方法;然后会去调用addOverlay方法,这个是添加一个标注覆盖物在当前位置,每在线路上画一个线段的时候,就把当前的经纬度用一个覆盖物标注出来;
再创建一个模拟线程TrackThread(),这时候会去调用getLocation()方法,这里是采取的模拟位置,即利用当前位置和一个随机数来确定一个新的经纬度;
确定好新的位置之后,就调用画线方法drawLine(),把两个点用线连接起来;
(其他的功能还没有实现,例如跟踪回放,这个在类中已经写好了,剩下的就是需要在MainActivity中添加对应的回调函数)
我这里的线路,是采用的模拟线路,还需要把它给改好,改成监听手机内部GPS的方法,大概就可以拿着手机到外面实际测试一下是否可行了
这是刚开始学的,有什么错误的地方还需要大佬们帮忙指出来呀