背景
实现两个地点之间的路程规划,结合上篇添加标签(Marker)的文章来实现一下的需求:
- 提供地点的选择。
- 根据两个地点,计算路线规划,包括驾车、步行、骑行、公共交通四种交通类型选择。
- 展示不同交通方式需要的距离和时间。
实现如下图的操作效果:

关键代码分析
计算驾车路径
DrivingRouteParams的关键属性:
- origins:道路绑定的起点坐标列表,最后一个坐标是真正的出发地。这里不太明白起点可以是多个的使用场景在什么地方。
- destination:目的地,通过latitude(纬度)和longitude(经度)来设置目的地的坐标,通过matchType属性来绑定路类型,默认限制绑路。起点避免绑定封闭、施工、限行、偏好设置所避免的道路,终点避免绑定高速、轮渡、 偏好设置所避免的道路。
- optimize:是否对途径点进行优化。
arkts
/**
* 计算驾车路径
* @param start 开始经纬度
* @param end 结束经纬度
* @param wayPoint 途径点
* @returns 驾车路径结果
*/
public static async DrivingRoute(start: mapCommon.LatLng,
end: mapCommon.LatLng, wayPoint: mapCommon.LatLng[]): Promise<GetRouteResult> {
let getDrivingResult: GetRouteResult = new GetRouteResult(RouterType.Driving);
let drivingParams: navi.DrivingRouteParams = {
origins: [{
latitude: start.latitude,
longitude: start.longitude
}],
destination: {
latitude: end.latitude,
longitude: end.longitude
},
language: "zh_CN",
avoids: [0],
//途径点
waypoints: wayPoint,
//途径点类型。false:经停点,将分段下发路线信息。true:经过点,将整段下发路线信息。
isViaType: true,
//是否对途经点进行优化。true:进行途经点优化
optimize: true,
//是否返回多条规划路线结果。false:返回单条规划路线结果
alternatives: false,
//预计出发时间。时间戳。必须是当前或者未来时间,不能是过去时间。0为当前时间处理
departAt: 0,
//事故华北预估模型。0:智能预测;1:路况差于历史平均水平;2:路况优于历史平均水平。
trafficMode: 0
}
try {
const drivingResult: navi.RouteResult = await navi.getDrivingRoutes(drivingParams);
if (drivingResult.routes.length == 0) {
throw new Error("获取驾驶信息出现错误. ");
}
MapRouterUtils.SetResult(getDrivingResult, drivingResult.routes[0]);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取驾驶信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getDrivingResult;
}
计算步行路径
关键属性:
- origins:道路绑定的起点坐标列表,最后一个坐标是真正的出发地。这里不太明白起点可以是多个的使用场景在什么地方。
- destination:目的地,通过latitude(纬度)和longitude(经度)来设置目的地的坐标,通过matchType属性来绑定路类型,默认限制绑路。起点避免绑定封闭、施工、限行、偏好设置所避免的道路,终点避免绑定高速、轮渡、 偏好设置所避免的道路。
arkts
/**
* 计算步行路径
* @param start 开始经纬度
* @param end 结束经纬度
* @returns
*/
public static async WalkingRoute(start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult> {
let getWalkingResult: GetRouteResult = new GetRouteResult(RouterType.Walking);
let walkingParams: navi.RouteParams = {
origins: [start],
destination: {
latitude: end.latitude,
longitude: end.longitude
},
language: "zh_CN",
avoids: [0],
}
try {
const walkingResult: navi.RouteResult = await navi.getWalkingRoutes(walkingParams);
if (walkingResult.routes.length == 0) {
throw new Error("获取步行信息出现错误. ");
}
MapRouterUtils.SetResult(getWalkingResult, walkingResult.routes[0]);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取步行信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getWalkingResult;
}
计算骑行路径
和步行的传参方式一致
arkts
/**
* 计算骑行路径
* @param start
* @param end
* @returns
*/
public static async CyclingRoute(start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult> {
let getCyclingResult: GetRouteResult = new GetRouteResult(RouterType.Cycling);
let cyclingParams: navi.RouteParams = {
origins: [{
latitude: start.latitude,
longitude: start.longitude
}],
destination: {
latitude: end.latitude,
longitude: end.longitude
},
language: "zh_CN",
avoids: [0],
}
try {
const cyclingResult: navi.RouteResult = await navi.getCyclingRoutes(cyclingParams);
if (cyclingResult.routes.length == 0) {
throw new Error("获取骑行信息出现错误. ");
}
MapRouterUtils.SetResult(getCyclingResult, cyclingResult.routes[0]);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取骑行信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getCyclingResult;
}
计算公共交通路径
还未印证正确性,因为发现途径点坐标会只有一个,已经提工单反馈官方修改,后续应该可以直接使用。
arkts
/**
* 计算公共交通
* @param context
* @param start
* @param end
* @returns
*/
public static async TransitRoute(context: Context, start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult> {
let transitParams: navi.TransitRouteParams = {
//起点
origin: {
longitude: start.longitude,
latitude: start.latitude
},
//终点
destination: {
longitude: end.longitude,
latitude: end.latitude
},
//自选设置
// 0:推荐(默认值)。
// 1:地铁优先。
// 2:少走路。
// 3:少中转。
// 4:时间短。
// 5:不坐地铁。
preference: 0
}
let getTransitResult: GetRouteResult = new GetRouteResult(RouterType.Transit);
try {
const transitResult: navi.TransitRouteResult = await navi.getTransitRoutes(context, transitParams);
if (!transitResult.routes || transitResult.routes.length == 0) {
throw new Error("获取公交信息出现错误. ");
}
if (transitResult.routes) {
const route: navi.TransitRoute =
transitResult.routes.sort((a, b) => ((a.busSortInfo as navi.BusSortInfo).totalCost as number) -
((b.busSortInfo as navi.BusSortInfo).totalCost as number))[0];
getTransitResult.Duration = route.busSortInfo?.totalCost;
getTransitResult.DurationDescription = DateUtils.FormatDuration(route.busSortInfo?.totalCost as number);
let distance: number = 0;
route.sections.forEach((section, index) => {
let wapPoints: mapCommon.LatLng[] = []
if (section.type == "pedestrian" && section.pedestrianSection) {
wapPoints = section.pedestrianSection.polyline as mapCommon.LatLng[]
distance += (section.pedestrianSection.travelSummary as navi.BaseSummary).length;
} else if (section.type == "transit" && section.transitSection) {
wapPoints = section.transitSection.polyline as mapCommon.LatLng[]
distance += (section.transitSection.travelSummary as navi.BaseSummary).length;
}
getTransitResult.WayPoint = getTransitResult.WayPoint.concat(wapPoints);
})
getTransitResult.Distance = distance;
getTransitResult.DistanceDescription = `${(distance / 1000).toFixed(2)}公里`
}
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取公交信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getTransitResult;
}
完整代码
GetRouteResult.ets
包含了交通规划类型
arkts
import { map, mapCommon } from "@kit.MapKit"
/**
* 路径规划类
*/
export class GetRouteResult {
/**
* 路径规划类型
*/
Type: RouterType = RouterType.Driving
/**
* 路径规划类型名字
*/
TypeName: string = ""
/**
* 距离
*/
Distance?: number = 0
/**
* 距离描述
*/
DistanceDescription?: string = ""
/**
* 持续时间。秒
*/
Duration?: number = 0
/**
* 持续时间描述
*/
DurationDescription?: string = ""
/**
* 途径点,包括起终点
*/
WayPoint: mapCommon.LatLng[] = []
constructor(routerType: RouterType) {
this.Type = routerType;
switch (routerType) {
case RouterType.Driving:
this.TypeName = "驾驶";
break;
case RouterType.Walking:
this.TypeName = "步行";
break;
case RouterType.Cycling:
this.TypeName = "骑行";
break;
case RouterType.Transit:
this.TypeName = "公共交通";
break;
}
}
}
/**
* 交通规划类型
*/
export enum RouterType {
/**
* 驾车
*/
Driving,
/**
* 步行
*/
Walking,
/**
* 骑行
*/
Cycling,
/**
* 公共交通
*/
Transit
}
DateUtils.ets
路线规划返回的时间单位为秒,公共交通的规划结果中没有直接给出时间的格式化文字,因此,需要写一个类来实现时间的转换。
arkts
export class DateUtils {
public static FormatDuration(seconds: number): string {
if (seconds < 0) {
return "0秒";
}
if (seconds === 0) {
return "0秒";
}
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
// 根据不同的时间长度选择不同的显示方式
if (hours > 0) {
return `${hours}小时${minutes}分${remainingSeconds}秒`;
} else if (minutes > 0) {
return `${minutes}分${remainingSeconds}秒`;
} else {
return `${remainingSeconds}秒`;
}
}
}
MapRouterUtils.ets
路径规划类封装
arkts
import { GetRouteResult, RouterType } from "../Models/GetRouterResult";
import { mapCommon, navi } from "@kit.MapKit";
import { BusinessError } from '@kit.BasicServicesKit';
import { DateUtils } from "./DateUsils";
export class MapRouterUtils {
/**
* 根据起点和终点数据规划路线
* @param start
* @param end
* @returns
*/
public static async CalculateRouter(context: Context, start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult[]> {
let result: GetRouteResult[] = [];
//驾驶计算
const drivingResult = await MapRouterUtils.DrivingRoute(start, end, []);
//步行计算
const walkingResult = await MapRouterUtils.WalkingRoute(start, end);
//骑行计算
const cyclingResult = await MapRouterUtils.CyclingRoute(start, end);
//计算公交出行时间
const transitResult = await MapRouterUtils.TransitRoute(context, start, end);
result = [drivingResult, walkingResult, cyclingResult, transitResult]
return result;
}
/**
* 根据起点和终点获得一种数据
* @param context
* @param start
* @param end
* @returns
*/
public static async SimpleCalculateRouter(context: Context, start: mapCommon.LatLng,
end: mapCommon.LatLng, routeType: RouterType): Promise<GetRouteResult> {
let result: GetRouteResult = new GetRouteResult(routeType);
switch (routeType) {
case RouterType.Driving:
result = await MapRouterUtils.DrivingRoute(start, end, []);
break;
case RouterType.Walking:
result = await MapRouterUtils.WalkingRoute(start, end);
break;
case RouterType.Cycling:
result = await MapRouterUtils.CyclingRoute(start, end);
break;
case RouterType.Transit:
result = await MapRouterUtils.TransitRoute(context, start, end);
break;
}
return result;
}
/**
* 计算驾车路径
* @param start 开始经纬度
* @param end 结束经纬度
* @param wayPoint 途径点
* @returns 驾车路径结果
*/
public static async DrivingRoute(start: mapCommon.LatLng,
end: mapCommon.LatLng, wayPoint: mapCommon.LatLng[]): Promise<GetRouteResult> {
let getDrivingResult: GetRouteResult = new GetRouteResult(RouterType.Driving);
let drivingParams: navi.DrivingRouteParams = {
origins: [{
latitude: start.latitude,
longitude: start.longitude
}],
destination: {
latitude: end.latitude,
longitude: end.longitude
},
language: "zh_CN",
avoids: [0],
//途径点
waypoints: wayPoint,
//途径点类型。false:经停点,将分段下发路线信息。true:经过点,将整段下发路线信息。
isViaType: true,
//是否对途经点进行优化。true:进行途经点优化
optimize: true,
//是否返回多条规划路线结果。false:返回单条规划路线结果
alternatives: false,
//预计出发时间。时间戳。必须是当前或者未来时间,不能是过去时间。0为当前时间处理
departAt: 0,
//事故华北预估模型。0:智能预测;1:路况差于历史平均水平;2:路况优于历史平均水平。
trafficMode: 0
}
try {
const drivingResult: navi.RouteResult = await navi.getDrivingRoutes(drivingParams);
if (drivingResult.routes.length == 0) {
throw new Error("获取驾驶信息出现错误. ");
}
MapRouterUtils.SetResult(getDrivingResult, drivingResult.routes[0]);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取驾驶信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getDrivingResult;
}
/**
* 计算步行路径
* @param start 开始经纬度
* @param end 结束经纬度
* @returns
*/
public static async WalkingRoute(start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult> {
let getWalkingResult: GetRouteResult = new GetRouteResult(RouterType.Walking);
let walkingParams: navi.RouteParams = {
origins: [start],
destination: {
latitude: end.latitude,
longitude: end.longitude
},
language: "zh_CN",
avoids: [0],
}
try {
const walkingResult: navi.RouteResult = await navi.getWalkingRoutes(walkingParams);
if (walkingResult.routes.length == 0) {
throw new Error("获取步行信息出现错误. ");
}
MapRouterUtils.SetResult(getWalkingResult, walkingResult.routes[0]);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取步行信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getWalkingResult;
}
/**
* 计算骑行路径
* @param start
* @param end
* @returns
*/
public static async CyclingRoute(start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult> {
let getCyclingResult: GetRouteResult = new GetRouteResult(RouterType.Cycling);
let cyclingParams: navi.RouteParams = {
origins: [{
latitude: start.latitude,
longitude: start.longitude
}],
destination: {
latitude: end.latitude,
longitude: end.longitude
},
language: "zh_CN",
avoids: [0],
}
try {
const cyclingResult: navi.RouteResult = await navi.getCyclingRoutes(cyclingParams);
if (cyclingResult.routes.length == 0) {
throw new Error("获取骑行信息出现错误. ");
}
MapRouterUtils.SetResult(getCyclingResult, cyclingResult.routes[0]);
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取骑行信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getCyclingResult;
}
/**
* 计算公共交通
* @param context
* @param start
* @param end
* @returns
*/
public static async TransitRoute(context: Context, start: mapCommon.LatLng,
end: mapCommon.LatLng): Promise<GetRouteResult> {
let transitParams: navi.TransitRouteParams = {
//起点
origin: {
longitude: start.longitude,
latitude: start.latitude
},
//终点
destination: {
longitude: end.longitude,
latitude: end.latitude
},
//自选设置
// 0:推荐(默认值)。
// 1:地铁优先。
// 2:少走路。
// 3:少中转。
// 4:时间短。
// 5:不坐地铁。
preference: 0
}
let getTransitResult: GetRouteResult = new GetRouteResult(RouterType.Transit);
try {
const transitResult: navi.TransitRouteResult = await navi.getTransitRoutes(context, transitParams);
if (!transitResult.routes || transitResult.routes.length == 0) {
throw new Error("获取公交信息出现错误. ");
}
if (transitResult.routes) {
const route: navi.TransitRoute =
transitResult.routes.sort((a, b) => ((a.busSortInfo as navi.BusSortInfo).totalCost as number) -
((b.busSortInfo as navi.BusSortInfo).totalCost as number))[0];
getTransitResult.Duration = route.busSortInfo?.totalCost;
getTransitResult.DurationDescription = DateUtils.FormatDuration(route.busSortInfo?.totalCost as number);
let distance: number = 0;
route.sections.forEach((section, index) => {
let wapPoints: mapCommon.LatLng[] = []
if (section.type == "pedestrian" && section.pedestrianSection) {
wapPoints = section.pedestrianSection.polyline as mapCommon.LatLng[]
distance += (section.pedestrianSection.travelSummary as navi.BaseSummary).length;
} else if (section.type == "transit" && section.transitSection) {
wapPoints = section.transitSection.polyline as mapCommon.LatLng[]
distance += (section.transitSection.travelSummary as navi.BaseSummary).length;
}
getTransitResult.WayPoint = getTransitResult.WayPoint.concat(wapPoints);
})
getTransitResult.Distance = distance;
getTransitResult.DistanceDescription = `${(distance / 1000).toFixed(2)}公里`
}
} catch (error) {
const err: BusinessError = error as BusinessError;
console.error(`获取公交信息出现错误. 错误代码:${err.code}, 错误信息: ${err.message}`);
}
return getTransitResult;
}
/**
* 填写路线内容
* @param getResult
* @param route
*/
private static SetResult(getResult: GetRouteResult, route: navi.Route) {
const routeStep = route.steps[0];
getResult.Distance = routeStep.distance;
getResult.DistanceDescription = routeStep.distanceDescription;
getResult.Duration = routeStep.duration;
getResult.DurationDescription = routeStep.durationDescription;
getResult.WayPoint = route.overviewPolyline as mapCommon.LatLng[]
}
}
SelectRouteParam.ets
路线选择交互类
arkts
import { GetRouteResult } from "./GetRouterResult";
@ObservedV2
export class SelectRouteParam {
/**
* 交通方式集合
*/
@Trace RouteResult: GetRouteResult[] = []
/**
* 选择交通方式索引
*/
@Trace SelectIndex: number = 0
/**
* 出发时间
*/
@Trace DepartTime: Date = new Date();
/**
* 选择修改
*/
@Trace SelectChange: (index: number) => void = (i) => {
this.SelectIndex = i;
}
}
PromptActionClass.ets
弹窗辅助类
arkts
import { promptAction } from "@kit.ArkUI";
import { hilog } from "@kit.PerformanceAnalysisKit";
import { BusinessError } from "@kit.BasicServicesKit";
export class PromptActionClass {
static ctx: UIContext;
static ContentNode: ComponentContent<Object>;
static Options: promptAction.BaseDialogOptions;
static SetContext(context: UIContext) {
PromptActionClass.ctx = context;
}
static SetContentNode(node: ComponentContent<Object>) {
PromptActionClass.ContentNode = node;
}
static SetOptions(options: promptAction.BaseDialogOptions) {
PromptActionClass.Options = options;
}
static OpenDialog() {
if (PromptActionClass.ContentNode !== null) {
PromptActionClass.ctx.getPromptAction()
.openCustomDialog(PromptActionClass.ContentNode, PromptActionClass.Options)
.then(() => {
hilog.info(0xFF00, 'TAG', '打开弹窗已完成');
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
hilog.info(0xFF00, 'TAG', `弹窗打开失败,错误代码: ${code}, 错误信息:${message}`);
})
}
}
static CloseDialog(contentNode?: ComponentContent<Object>) {
if (PromptActionClass.ContentNode !== null) {
PromptActionClass.ctx.getPromptAction()
.closeCustomDialog(contentNode)
.then(() => {
hilog.info(0xFF00, 'TAG', '关闭弹窗顺利运行');
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
hilog.info(0xFF00, 'TAG', `关闭弹窗错误,错误代码: ${code}, 错误信息:${message}`);
})
}
}
static UpdateDialog(options: promptAction.BaseDialogOptions) {
if (PromptActionClass.ContentNode !== null) {
PromptActionClass.ctx.getPromptAction()
.updateCustomDialog(PromptActionClass.ContentNode, options)
.then(() => {
hilog.info(0xFF00, 'TAG', '更新弹窗顺利运行');
})
.catch((error: BusinessError) => {
let message = (error as BusinessError).message;
let code = (error as BusinessError).code;
hilog.info(0xFF00, 'TAG', `更新弹窗出错,错误代码:${code}, 错误信息:${message}`);
})
}
}
}
LocationUtil
用户定位信息辅助类
arkts
import { common, Permissions } from "@kit.AbilityKit";
import { geoLocationManager } from "@kit.LocationKit";
import { map, mapCommon, sceneMap } from "@kit.MapKit";
export class LocationUtil {
/**
* 当前位置的维度
*/
public static LocationLatitude: number = 39.9;
/**
* 当前位置的经度
*/
public static LocationLongitude: number = 116.4;
/**
* 所需要得权限
*/
public static MapPermissions: Permissions[] =
[
'ohos.permission.INTERNET',
'ohos.permission.LOCATION',
'ohos.permission.APPROXIMATELY_LOCATION'
]
/**
* 更新用户定位
*/
public static async UpdateLocation() {
// 获取用户位置坐标
let location: geoLocationManager.Location = await geoLocationManager.getCurrentLocation();
const convertPoint = map.convertCoordinateSync(mapCommon.CoordinateType.WGS84,
mapCommon.CoordinateType.GCJ02, {
longitude: location.longitude,
latitude: location.latitude
});
LocationUtil.LocationLongitude = convertPoint.longitude
LocationUtil.LocationLatitude = convertPoint.latitude;
}
/**
* 选择地图
* @param uiContext
* @returns
*/
public static async SelectLocation(uiContext: common.UIAbilityContext): Promise<sceneMap.LocationChoosingResult | undefined> {
try {
await LocationUtil.UpdateLocation();
let locationChoosingOptions: sceneMap.LocationChoosingOptions = {
// 地图中心点坐标
location: { latitude: LocationUtil.LocationLatitude, longitude: LocationUtil.LocationLongitude },
language: 'zh_cn',
// 展示搜索控件
searchEnabled: true,
// 展示附近Poi
showNearbyPoi: true
};
// 拉起地点选取页
let result: sceneMap.LocationChoosingResult =
await sceneMap.chooseLocation(uiContext, locationChoosingOptions);
return result;
} catch (e) {
console.info(e)
return undefined;
}
}
public static async SelectLocationPoint(uiContext: common.UIAbilityContext,
originLocationPoint: mapCommon.LatLng): Promise<mapCommon.LatLng | undefined> {
try {
let locationChoosingOptions: sceneMap.LocationChoosingOptions = {
// 地图中心点坐标
location: originLocationPoint,
language: 'zh_cn',
// 展示搜索控件
searchEnabled: true,
// 展示附近Poi
showNearbyPoi: true
};
// 拉起地点选取页
let result: sceneMap.LocationChoosingResult =
await sceneMap.chooseLocation(uiContext, locationChoosingOptions);
if (result) {
return result.location;
}
return undefined;
} catch (e) {
console.error(`地点选择错误,错误代码:${e}`);
return undefined;
}
}
}
PermissionUtils.ets
权限判断辅助类
arkts
import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit';
/**
*权限封装类
*/
export class PermissionUtils {
/**
* 检查权限是否授权(完全授权)
* @param permissionsArr 权限集合
* @returns true:已经全部授权;false:没有全部授权
*/
public static CheckPermissions(permissionsArr: Permissions[]): boolean {
const atManager = abilityAccessCtrl.createAtManager();
//获取bundle信息
const bundleInfo =
bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
// 拿到当前应用的tokenID 标识
const tokenID = bundleInfo.appInfo.accessTokenId
//校验应用是否被授予权限
let result: boolean = true;
permissionsArr.forEach((x: Permissions, index: number) => {
if (!(atManager.checkAccessTokenSync(tokenID, x) === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)) {
result = false;
return;
}
})
return result;
}
/**
* 申请授权(首次弹窗申请)
* @param permissionList 权限集合
* @returns true:权限完全加载;false:有权限没有加载
*/
public static async RequestPermissions(permissionList: Permissions[]): Promise<boolean> {
// 程序访问控制管理
const atManager = abilityAccessCtrl.createAtManager();
// 拉起弹框请求用户授权
const grantStatus = await atManager.requestPermissionsFromUser(getContext(), permissionList)
// 获取请求权限的结果
const isAuth = grantStatus.authResults.every(v => v === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED)
// 返回 Promise 授权结果
return isAuth ? Promise.resolve(true) : Promise.reject(false)
}
/**
* 打开系统设置的权限管理页面
*/
public static OpenPermissionSettingsPage() {
// 获取上下文
const context = getContext() as common.UIAbilityContext
// 获取包信息
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
// 打开系统设置页
context.startAbility({
bundleName: 'com.huawei.hmos.settings',
abilityName: 'com.huawei.hmos.settings.MainAbility',
uri: 'application_info_entry',
parameters: {
// 按照包名打开对应设置页
pushParams: bundleInfo.name
}
})
}
}
MapRouteViewModel.ets
页面交互类
arkts
import { map, mapCommon } from "@kit.MapKit"
import { AsyncCallback } from "@kit.BasicServicesKit"
import { LocationUtil } from "../Utils/LocationUtil"
import { common, Permissions } from "@kit.AbilityKit"
import { geoLocationManager } from "@kit.LocationKit"
import { PermissionUtils } from "../Utils/PermissionUtils"
import { SelectRouteParam } from "../Models/SelectRouteParam"
import { ComponentContent } from "@kit.ArkUI"
import { PromptActionClass } from "../Utils/PromptActionClass"
import { GetRouteResult, RouterType } from "../Models/GetRouterResult"
import { MapRouterUtils } from "../Utils/MapRouterUtils"
@ObservedV2
export class MapRouteViewModel {
/**
* 地图初始化参数设置
*/
MapOption?: mapCommon.MapOptions
/**
* 地图回调方法
*/
MapCallBack?: AsyncCallback<map.MapComponentController>
/**
* 地图控制器
*/
MapController?: map.MapComponentController
/**
* 地图监听管理器
*/
MapEventManager?: map.MapEventManager
/**
* 所需要得权限
*/
MapPermissions: Permissions[] =
[
'ohos.permission.INTERNET',
'ohos.permission.LOCATION',
'ohos.permission.APPROXIMATELY_LOCATION'
]
/**
* 当前位置的维度
*/
@Trace LocationLatitude: number = 39.9;
/**
* 当前位置的经度
*/
@Trace LocationLongitude: number = 116.4;
MyUIContext?: UIContext
/**
* 标记builder
*/
@Trace MarkerBuilder?: CustomBuilder
/**
* 标记集合
*/
@Trace MarkerList: map.Marker[] = []
/**
* 路线规划集合
*/
@Trace RouteResult?: SelectRouteParam;
/**
* 选择路径模拟器
*/
@Trace SelectRouteType?: ComponentContent<object>
constructor() {
this.UpdateLocation()
this.MapOption = {
//相机位置
position: {
target: {
latitude: this.LocationLatitude,
longitude: this.LocationLongitude
},
zoom: 15
},
//地图类型
mapType: mapCommon.MapType.STANDARD,
//地图最小图层,默认值为2
minZoom: 2,
//地图最大图层,默认值为20
maxZoom: 20,
//是否支持旋转手势
rotateGesturesEnabled: true,
//是否支持滑动手势
scrollGesturesEnabled: true,
//是否支持缩放手势
zoomGesturesEnabled: true,
//是否支持倾斜手势
tiltGesturesEnabled: true,
//是否展示缩放控件
zoomControlsEnabled: true,
//是否展示定位按钮
myLocationControlsEnabled: true,
//是否展示指南针控件
compassControlsEnabled: false,
//是否展示比例尺
scaleControlsEnabled: true,
//是否一直显示比例尺,只有比例尺启用时该参数才生效。
alwaysShowScaleEnabled: true
};
this.MapCallBack = async (err, mapController) => {
if (!err) {
this.MapController = mapController;
//启用我的位置图层
mapController.setMyLocationEnabled(true);
//设置我的位置跟随设备移动
mapController.setMyLocationStyle({
displayType: mapCommon.MyLocationDisplayType.LOCATE
})
//启用我的位置按钮
mapController.setMyLocationControlsEnabled(true);
//地图监听时间管理器
this.MapEventManager = this.MapController.getEventManager();
this.MapEventManager.on("markerClick", (marker: map.Marker) => {
this.MarkerClickCallBack(marker);
});
}
}
}
public async Init(uiContext: UIContext, builder: WrappedBuilder<[SelectRouteParam]>,
markerBuilder: CustomBuilder) {
this.MyUIContext = uiContext;
this.SelectRouteType = new ComponentContent(uiContext, builder, new SelectRouteParam());
this.MarkerBuilder = markerBuilder;
//识别权限是否赋予
if (!PermissionUtils.CheckPermissions(this.MapPermissions)) {
const perResult: boolean = await PermissionUtils.RequestPermissions(this.MapPermissions);
if (!perResult) {
console.error("用户未授权");
return;
}
}
}
/**
* 更新用户定位
*/
public async UpdateLocation() {
// 获取用户位置坐标
try {
let location: geoLocationManager.Location = await geoLocationManager.getCurrentLocation();
const convertPoint = map.convertCoordinateSync(mapCommon.CoordinateType.WGS84,
mapCommon.CoordinateType.GCJ02, {
longitude: location.longitude,
latitude: location.latitude
});
this.LocationLongitude = convertPoint.longitude;
this.LocationLatitude = convertPoint.latitude;
} catch (error) {
console.error("更新用户坐标出错");
}
}
/**
* 在当前位置添加一个标记
* @param point
*/
public async CreateLocationMarker() {
if (this.MapController) {
// Marker初始化参数
let markerOptions: mapCommon.MarkerOptions = {
//定位点
position: {
latitude: this.LocationLatitude,
longitude: this.LocationLongitude
},
clickable: true
};
try {
let marker: map.Marker = await this.MapController.addMarker(markerOptions);
} catch (error) {
console.error(error)
}
}
}
/**
* 选择地点,并生成标记
*/
public async SelectedLocationCreateMarker() {
if (this.MyUIContext && this.MapController) {
await LocationUtil.UpdateLocation()
let selectResult =
await LocationUtil.SelectLocation(this.MyUIContext.getHostContext() as common.UIAbilityContext)
if (selectResult) {
// Marker初始化参数
let markerOptions: mapCommon.MarkerOptions = {
//定位点
position: selectResult.location,
iconBuilder: this.MarkerBuilder,
clickable: true
};
try {
let marker: map.Marker = await this.MapController.addMarker(markerOptions);
marker.setTitle(selectResult.name)
marker.setSnippet(selectResult.address)
//如果不是第一个,会涉及到计算行程
if (this.MarkerList.length != 0) {
await this.SimpleCalculate(this.MarkerList[this.MarkerList.length-1].getPosition(), marker.getPosition());
}
this.MarkerList.push(marker);
} catch (error) {
console.error(error)
}
}
}
}
/**
* 移动视图相机
* @param latitude 维度
* @param longitude 经度
*/
public async MoveCamera(latitude: number, longitude: number) {
if (this.MapController) {
//将视图移动到标点位置
let nwPosition = map.newCameraPosition({
target: {
latitude: latitude,
longitude: longitude
},
zoom: 18
})
// 以动画方式移动地图相机
this.MapController.animateCamera(nwPosition, 1000);
}
}
/**
* 标记点击事件
* @param marker
*/
public async MarkerClickCallBack(marker: map.Marker) {
let locationPoint: mapCommon.LatLng = marker.getPosition();
this.MoveCamera(locationPoint.latitude, locationPoint.longitude);
}
/**
* 弹出路径选择页面
* @param modelIndex
*/
public async ShowSelectRoute(modelIndex: number) {
if (this.SelectRouteType && this.MyUIContext) {
this.SelectRouteType.update(this.RouteResult)
PromptActionClass.SetContext(this.MyUIContext);
PromptActionClass.SetOptions({
alignment: DialogAlignment.Bottom
})
PromptActionClass.SetContentNode(this.SelectRouteType);
PromptActionClass.OpenDialog();
//说明是之前的简单计算,需要重新计算一下
if (this.RouteResult && this.RouteResult.RouteResult.length == 1) {
const param = this.MarkerList[modelIndex];
const nextModel = this.MarkerList[modelIndex+1];
await this.Calculate(param.getPosition(), nextModel.getPosition());
//更新页面
this.SelectRouteType.update(this.RouteResult);
}
}
}
/**
* 计算全部路径
* @param start
* @param end
*/
public async Calculate(start: mapCommon.LatLng, end: mapCommon.LatLng) {
let selectRoute: SelectRouteParam = new SelectRouteParam();
if (this.MyUIContext) {
const routeResults: GetRouteResult[] =
await MapRouterUtils.CalculateRouter(this.MyUIContext.getHostContext() as common.Context, start, end)
selectRoute.RouteResult = routeResults;
selectRoute.SelectIndex = 0;
selectRoute.SelectChange = (index: number) => {
this.RouteTypeSelectChange(selectRoute, index);
}
}
this.RouteResult = selectRoute;
}
/**
* 简单计算(用于快速生成)
* @param start 开始地点
* @param end 结束地点
* @returns
*/
public async SimpleCalculate(start: mapCommon.LatLng, end: mapCommon.LatLng) {
let selectRoute: SelectRouteParam = new SelectRouteParam();
if (this.MyUIContext) {
const routeResult: GetRouteResult =
await MapRouterUtils.SimpleCalculateRouter(this.MyUIContext.getHostContext() as common.Context,
start,
end, RouterType.Driving)
selectRoute.RouteResult = [routeResult];
selectRoute.SelectIndex = 0;
selectRoute.SelectChange = (index: number) => {
this.RouteTypeSelectChange(selectRoute, index);
}
}
this.RouteResult = selectRoute;
}
/**
* 交通方式选择
* @param index
*/
public async RouteTypeSelectChange(selectRoute: SelectRouteParam, index: number) {
selectRoute.SelectIndex = index;
this.RouteResult = selectRoute;
this.SelectRouteType?.update(selectRoute);
setTimeout(() => {
PromptActionClass.CloseDialog(this.SelectRouteType)
}, 50)
}
}
MapRoutePage.ets
主要显示页面
arkts
import { GetRouteResult, RouterType } from '../Models/GetRouterResult'
import { SelectRouteParam } from '../Models/SelectRouteParam'
import { MapRouteViewModel } from '../ViewModels/MapRouteViewModel'
import { map, MapComponent } from '@kit.MapKit';
@Entry
@ComponentV2
struct MapRoutePage {
@Local VM: MapRouteViewModel = new MapRouteViewModel()
aboutToAppear(): void {
this.VM.Init(this.getUIContext(), wrapBuilder(SelectRouteBuilder), () => {
this.MyMarkerBuilder(this.VM.MarkerList.length);
});
}
build() {
RelativeContainer() {
MapComponent({
mapOptions: this.VM.MapOption,
mapCallback: this.VM.MapCallBack
})
.width("100%")
.height("350")
.id("Map")
Column({ space: 10 }) {
List({ space: 10 }) {
ForEach(this.VM.MarkerList, (model: map.Marker, modelIndex: number) => {
ListItem() {
Column({ space: 10 }) {
Column({ space: 8 }) {
Row() {
Image($r('app.media.startIcon'))
.width(53)
.height(23)
Text(model.getTitle())
.fontSize(18)
.layoutWeight(1)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
.alignItems(VerticalAlign.Center)
.width("100%")
Row({ space: 2 }) {
SymbolGlyph($r('sys.symbol.local'))
.fontSize(14)
Text(model.getSnippet())
.fontSize(14)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.layoutWeight(1)
}
.padding({ left: 12 })
.alignItems(VerticalAlign.Center)
.width("100%")
}
.alignItems(HorizontalAlign.Start)
.padding(10)
.border({
width: 2,
color: $r('app.color.Main_Color'),
radius: 10
})
.backgroundColor($r('app.color.white'))
.width("100%")
Row() {
Button() {
Row({ space: 5 }) {
SymbolGlyph($r('sys.symbol.car'))
.fontWeight(FontWeight.Medium)
.fontSize(14)
.fontColor([$r('app.color.Main_Color')])
Text(`${this.VM.RouteResult?.RouteResult[this.VM.RouteResult.SelectIndex]?.DurationDescription} / ${this.VM.RouteResult?.RouteResult[this.VM.RouteResult.SelectIndex]?.DistanceDescription} `)
.fontSize(14)
.fontWeight(FontWeight.Medium)
.fontColor($r('app.color.Main_Color'))
Text(">> More")
.fontWeight(FontWeight.Medium)
.fontSize(10)
.fontColor($r('app.color.Main_Color'))
}
.alignItems(VerticalAlign.Center)
}
.type(ButtonType.Normal)
.backgroundColor($r('app.color.white'))
.onClick(() => {
if (this.VM.RouteResult) {
this.VM.ShowSelectRoute(modelIndex)
}
})
}
.padding({ left: 10, right: 10 })
.width("100%")
.justifyContent(FlexAlign.Start)
.visibility(this.VM.RouteResult && this.VM.MarkerList.length - 1 != modelIndex ? Visibility.Visible :
Visibility.None)
}
}
.width("100%")
.backgroundColor(Color.White)
})
}
.width("100%")
.height("100%")
.margin({ top: 10, bottom: 10 })
}
.alignRules({
top: { anchor: "Map", align: VerticalAlign.Bottom }
})
.padding({ top: 10, left: 10, right: 10 })
.width("100%")
Button() {
SymbolGlyph($r('sys.symbol.plus'))
.fontColor([Color.White])
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
.id("PlusButton")
.type(ButtonType.Circle)
.width(50)
.height(50)
.backgroundColor($r('app.color.Main_Color'))
.margin({ bottom: 20 })
.alignRules({
bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
middle: { anchor: "__container__", align: HorizontalAlign.Center }
})
.bindMenu([
{
value: "添加行程",
action: () => {
this.VM.SelectedLocationCreateMarker()
}
}
], {
enableArrow: true,
placement: Placement.Top
})
}
.height('100%')
.width('100%')
}
@Builder
MyMarkerBuilder(index: number) {
RelativeContainer() {
Image($r('app.media.app_icon'))
.width(30)
.height(30)
.syncLoad(true)
Text(`${index}`)
.fontSize(10)
.width(15)
.textAlign(TextAlign.Center)
.height(15)
.backgroundColor($r('app.color.Main_Color'))
.borderRadius(7.5)
.alignRules({
bottom: { anchor: "__container__", align: VerticalAlign.Bottom },
right: { anchor: "__container__", align: HorizontalAlign.End }
})
}
.width(30)
.height(30)
}
}
@Builder
function SelectRouteBuilder(param: SelectRouteParam) {
Column({ space: 10 }) {
Row() {
Text("选择交通方式")
.fontSize(20)
.fontWeight(FontWeight.Bold)
Text(`${param.DepartTime.getHours().toString().padStart(2, '0')}:${param.DepartTime.getMinutes()
.toString()
.padStart(2, '0')} 出发`)
.fontSize(12)
.fontColor("#9e9e9e")
.fontWeight(FontWeight.Bold)
}
.justifyContent(FlexAlign.SpaceBetween)
.width("100%")
.margin({ top: 10 })
Column() {
List() {
ForEach(param.RouteResult, (item: GetRouteResult, index) => {
ListItem() {
RelativeContainer() {
SymbolGlyph(item.Type == RouterType.Driving ? $r('sys.symbol.car') :
item.Type == RouterType.Walking ? $r('sys.symbol.figure_run') :
item.Type == RouterType.Cycling ? $r('sys.symbol.bicycle') :
$r('sys.symbol.bus_fill'))
.fontSize(20)
.fontColor([param.SelectIndex == index ? Color.Black : "#9e9e9e"])
.alignRules({
center: { anchor: "__container__", align: VerticalAlign.Center },
left: { anchor: "__container__", align: HorizontalAlign.Start }
})
.id("Icon")
Text(`${item.TypeName} ${item.DistanceDescription} | ${item.DurationDescription}`)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(param.SelectIndex == index ? Color.Black : "#9e9e9e")
.margin({ left: 10 })
.alignRules({
center: { anchor: "__container__", align: VerticalAlign.Center },
left: { anchor: "Icon", align: HorizontalAlign.End }
})
.id("Text")
Button() {
Row() {
SymbolGlyph($r('sys.symbol.paperplane_fill'))
.fontSize(20)
.fontColor([Color.White])
}
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.width("100%")
.height("100%")
}
.type(ButtonType.Normal)
.backgroundColor(Color.Black)
.visibility(param.SelectIndex == index ? Visibility.Visible : Visibility.None)
.borderRadius(15)
.width(40)
.height(30)
.alignRules({
center: { anchor: "__container__", align: VerticalAlign.Center },
right: { anchor: "__container__", align: HorizontalAlign.End }
})
.id("JumpButton")
}
.padding({ left: 20, right: 20 })
.border({
radius: 20,
width: 2,
color: param.SelectIndex == index ? Color.Black : Color.Transparent
})
.height(54)
.width("100%")
}
.onClick(() => {
param.SelectChange(index);
})
})
}
.width("100%")
.height("100%")
}
.width("100%")
.layoutWeight(1)
.visibility(param.RouteResult.length == 1 ? Visibility.None : Visibility.Visible)
.animation({
duration: 500
})
Column() {
LoadingProgress()
.color($r('app.color.Main_Color'))
.width(100)
}
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.width("100%")
.layoutWeight(1)
.visibility(param.RouteResult.length == 1 ? Visibility.Visible : Visibility.None)
.animation({
duration: 500
})
}
.borderRadius({ topLeft: 20, topRight: 20 })
.padding(10)
.width("100%")
.height(300)
.backgroundColor(Color.White)
}
总结
- 当前代码使用了新版本的弹窗类封装。
- 为了方便写文章和主要突出两个地点间的交通规划的计算和选择,并没有实现多个地点的规划操作,读者可以自己手动修改。