@jcmap-sdk-web/navegador
Version:
甲虫室内定位导航引擎
802 lines (797 loc) • 37 kB
JavaScript
import { EventEmitter } from 'events';
import { getCoord, getCoords } from '@turf/invariant';
import turfDistance from '@turf/distance';
import { propOr, pathOr, range } from 'ramda';
import turfAlong from '@turf/along';
import { lineString, convertLength, point, featureCollection } from '@turf/helpers';
import { JCMapNavigadorCoreNavigation } from '@jcmap-sdk-web/navegador-core';
const NavigationModes = {
Walking: 0,
Driving: 1,
};
const NavigationDisplayModes = {
ARROW_UP: "视角",
FACE_NORTH: "正北",
LINE_UP: "线路",
};
const Directions = {
Keep: 0,
Left: 11,
Right: 12,
Straight: 13,
Arrive: 14,
LeftArrive: 15,
RightArrive: 16,
LeftForward: 17,
RightForward: 18,
LeftLeft: 21,
LeftRight: 22,
RightRight: 23,
RightLeft: 24,
LeftChangeFloor: 25,
RightChangeFloor: 26,
LeftChangeFloorLeft: 27,
LeftChangeFloorRight: 28,
LeftChangeFloorStraight: 29,
RightChangeFloorLeft: 30,
RightChangeFloorRight: 31,
RightChangeFloorStraight: 32,
StraightChangeFloorLeft: 33,
StraightChangeFloorRight: 34,
ChangeFloor: 35,
StraightChangeFloorStraight: 36,
};
const WALK_SPEED_PER_MINUTE = 5000 / 60; // 约 5 km/h,参考 https://en.wikipedia.org/wiki/Preferred_walking_speed
/**
* 导航任务
*/
class NavegadorTask extends EventEmitter {
constructor(options) {
super();
/**
* 动画时间,因为行走速度很慢
*/
this.animaTime = 950;
const navegador = (this.navegador = options.navegador);
const navigationMode = (this.navigationMode = options.navigationMode);
const finishingPoint = (this.finishingPoint = options.finishingPoint);
const startingPoint = (this.startingPoint = options.startingPoint);
const routeOptions = (this.routeOptions = options.routeOptions);
this.offRoadCountLimit = propOr(5, "offRoadCountLimit", options);
this.backwardCountLimit = propOr(5, "backwardCountLimit", options);
this.offRoadCount = 0;
this.backwardCount = 0;
this.timeDiffCountAdjust = propOr(1, "timeDiffCountAdjust", options);
this.distanceCountAdjust = propOr(1, "distanceCountAdjust", options);
this.cartogramDiffCountAdjust = propOr(1, "cartogramDiffCountAdjust", options);
this.locationAccuracyAdjust = propOr(0.05, "locationAccuracyAdjust", options);
this.offRoadMinDistance = {
bluetooth: pathOr(8, ["offRoadMinDistance", "bluetooth"], options),
satellite: pathOr(8, ["offRoadMinDistance", "satellite"], options),
};
navegador.setCurrentArrivalDistance(options.arrivalDistance || 8);
navegador.setCornerDistance(options.cornerDistance || 8);
const startingPointOnRoad = navegador.projectionPositionOnRoad(startingPoint);
if (!startingPointOnRoad) {
throw new Error("starting point can not be projected on road");
}
const fullNavigationPath = navegador.getNavigationChain(navigationMode, startingPoint, finishingPoint, routeOptions);
if (!fullNavigationPath) {
throw new Error("can not get navigation path");
}
this.fullNavigationPath = fullNavigationPath;
const startingPointOnPath = navegador.projectionPositionOnChain(startingPointOnRoad);
if (!startingPointOnPath) {
throw new Error("starting point can not be projected on navigation path");
}
const restOfDistance = (this.lastDistToEnd = navegador.getDistanceToNavigationChainEnd(startingPointOnPath));
const navigationStatus = navegador.getNavigationStatus(startingPointOnRoad);
if (!navigationStatus) {
throw new Error("can not get navigation status");
}
this.lastLocationTime = Date.now();
this.animationDistance = 0;
this.animationChain = fullNavigationPath;
this.targetPositionOnPath = startingPointOnPath;
this.currentPositionOnPath = startingPointOnPath;
this.currentNavigationPath = fullNavigationPath;
this.restOfDistance = Math.floor(restOfDistance);
this.restOfTime = Math.ceil(restOfDistance / WALK_SPEED_PER_MINUTE);
this.navigationHint =
this.getNavigationStatusText({
currentCartogramId: startingPointOnPath.properties["cartogram:id"],
navegador,
navigationStatus,
}) || "";
this.nextDirection = navigationStatus.nextDirection;
}
sliceNavigationChain(nc, startPt) {
const lines = Array.from(nc.features);
if (!lines.length) {
return nc;
}
const [firstSegment, ...restFeatures] = nc.features.filter((f) => f.properties.segment_index >= startPt.properties.segment_index);
return {
...nc,
features: [
lineString([getCoord(startPt), getCoords(firstSegment)[1]], firstSegment.properties),
...restFeatures,
],
};
}
segmentDistance(segment) {
if (segment.properties["starting:cartogram:id"] ===
segment.properties["finishing:cartogram:id"]) {
const coords = getCoords(segment);
return turfDistance(coords[0], coords[1], {
units: "meters",
});
}
return segment.properties.distance;
}
alongNavigationPath(nc, distance, options) {
const lines = Array.from(nc.features);
let line = lines.shift();
if (!line) {
throw new Error("no segment in NavigationChain");
}
const units = propOr("kilometers", "units", options);
const ncDistanceUnit = "meters";
let remainDistance = convertLength(distance, units, ncDistanceUnit);
// 如果距离小于等于0,那么返回导航链的起点
if (remainDistance <= 0) {
const cartogramId = line.properties["starting:cartogram:id"];
const startCartogram = this.navegador._cartograms[cartogramId];
return point(line.geometry.coordinates[0], {
accuracy: 0,
"cartogram:id": cartogramId,
"cartogram:name": startCartogram.properties.name,
"cartogram:floor_number": startCartogram.properties.floor_number,
"cartogram:floor_label": startCartogram.properties.floor_label,
"building:id": startCartogram.properties["building:id"],
"building:name": startCartogram.properties["building:name"],
segment_index: line.properties.segment_index,
source: "pseudo",
timestamp: Date.now(),
road_index: line.properties["starting:road_index"],
});
}
let cartogramId = line.properties["starting:cartogram:id"];
let startCartogram = this.navegador._cartograms[cartogramId];
let point$1 = point(line.geometry.coordinates[0], {
accuracy: 0,
"cartogram:id": cartogramId,
"cartogram:name": startCartogram.properties.name,
"cartogram:floor_number": startCartogram.properties.floor_number,
"cartogram:floor_label": startCartogram.properties.floor_label,
"building:id": startCartogram.properties["building:id"],
"building:name": startCartogram.properties["building:name"],
segment_index: line.properties.segment_index,
source: "pseudo",
timestamp: Date.now(),
road_index: line.properties["starting:road_index"],
});
while (remainDistance && line) {
const lineDistance = this.segmentDistance(line);
if (remainDistance <= lineDistance) {
cartogramId = line.properties["starting:cartogram:id"];
startCartogram = this.navegador._cartograms[cartogramId];
point$1 = point(getCoord(turfAlong(line, remainDistance, { units: ncDistanceUnit })), {
accuracy: 0,
"cartogram:id": cartogramId,
"cartogram:name": startCartogram.properties.name,
"cartogram:floor_number": startCartogram.properties.floor_number,
"cartogram:floor_label": startCartogram.properties.floor_label,
"building:id": startCartogram.properties["building:id"],
"building:name": startCartogram.properties["building:name"],
segment_index: line.properties.segment_index,
source: "pseudo",
timestamp: Date.now(),
road_index: line.properties["starting:road_index"],
});
break;
}
remainDistance -= lineDistance;
line = lines.shift();
}
return point$1;
}
sliceNavigationChainAlong(nc, startDist, options) {
const lines = Array.from(nc.features);
if (!lines.length) {
return nc;
}
const _startPt = this.alongNavigationPath(nc, startDist, options);
const [firstSegment, ...restFeatures] = nc.features.filter((f) => f.properties.segment_index >= _startPt.properties.segment_index);
return {
...nc,
features: [
lineString([getCoord(_startPt), getCoords(firstSegment)[1]], firstSegment.properties),
...restFeatures,
],
};
}
getInfo() {
const startTime = this.lastLocationTime;
const animaTime = this.animaTime;
const now = Date.now();
const diff = now - startTime;
const ratio = diff >= animaTime ? 1 : diff / animaTime;
const moveDistance = this.animationDistance * ratio;
const nextPositionOnPath = ratio === 1
? this.targetPositionOnPath
: this.alongNavigationPath(this.animationChain, moveDistance, {
units: "meters",
});
const currentNavigationChain = this.sliceNavigationChainAlong(this.animationChain, moveDistance, { units: "meters" });
const navigationStatus = this.navegador.getNavigationStatus(nextPositionOnPath);
if (navigationStatus) {
this.currentPositionOnPath = nextPositionOnPath;
this.currentNavigationPath = currentNavigationChain;
this.navigationHint =
this.getNavigationStatusText({
currentCartogramId: nextPositionOnPath.properties["cartogram:id"],
navegador: this.navegador,
navigationStatus,
}) || "";
this.nextDirection = navigationStatus.nextDirection;
const restOfDistance = (this.restOfDistance = Math.floor(navigationStatus.distanceToEnd));
this.restOfTime = Math.ceil(restOfDistance / WALK_SPEED_PER_MINUTE);
}
return {
navigationMode: this.navigationMode,
finishingPoint: this.finishingPoint,
startingPoint: this.startingPoint,
fullNavigationPath: this.fullNavigationPath,
targetPositionOnPath: this.targetPositionOnPath,
currentPositionOnPath: this.currentPositionOnPath,
currentNavigationPath: this.currentNavigationPath,
restOfDistance: this.restOfDistance,
restOfTime: this.restOfTime,
navigationHint: this.navigationHint,
nextDirection: this.nextDirection,
};
}
setCurrentLocation(currentLocation) {
const { navegador, lastLocationTime, timeDiffCountAdjust, distanceCountAdjust, cartogramDiffCountAdjust, targetPositionOnPath, locationAccuracyAdjust, } = this;
const now = Date.now();
const currentLocationOnRoad = navegador.projectionPositionOnRoad(currentLocation);
if (!currentLocationOnRoad) {
return;
}
const currentLocationOnPath = navegador.projectionPositionOnChain(currentLocationOnRoad);
// 无法投影到导航路径上时,比如刚开始导航时就朝反方向移动的时候,此时会投影到导航路径延长线而不是导航路径上
if (!currentLocationOnPath) {
this.offRoadCount +=
1 * timeDiffCountAdjust * ((now - lastLocationTime) / 1000);
this.needReroute(currentLocation);
return;
}
const isCrossRoad = currentLocationOnRoad.properties.road_index !==
currentLocationOnPath.properties.road_index;
// 当映射到道路上的道路编号不同于映射到导航路径上的道路编号时,典型情况是处于路口时
// 或者是跨楼层时
if (isCrossRoad) {
const dist = turfDistance(currentLocationOnRoad, currentLocationOnPath) * 1000;
const isSameCartogram = targetPositionOnPath.properties["cartogram:id"] ===
currentLocationOnPath.properties["cartogram:id"];
const minDistance = currentLocationOnPath.properties.source === "satellite"
? this.offRoadMinDistance.satellite
: this.offRoadMinDistance.bluetooth;
// 增加偏离计数时考虑以下因素
// 1. 与上一次定位之间的时间差
// 2. 与上一次定位之间的距离
// 3. 本次定位的精度
this.offRoadCount +=
1 *
((now - lastLocationTime) / 1000) *
timeDiffCountAdjust *
(currentLocation.properties.accuracy * locationAccuracyAdjust) *
(isSameCartogram
? dist >= minDistance
? ((dist - 4) / 4) * distanceCountAdjust
: 0
: cartogramDiffCountAdjust);
if (dist > minDistance) {
this.needReroute(currentLocation);
}
return;
}
// 检测倒退的情况
const distToEnd = navegador.getDistanceToNavigationChainEnd(currentLocationOnPath);
const distDiff = distToEnd - this.lastDistToEnd;
if (distDiff > 8) {
const isSameCartogram = this.targetPositionOnPath.properties["cartogram:id"] ===
currentLocationOnPath.properties["cartogram:id"];
const minDistance = currentLocationOnPath.properties.source === "satellite"
? this.offRoadMinDistance.satellite
: this.offRoadMinDistance.bluetooth;
// 增加后退计数时考虑以下因素
// 1. 与上一次定位之间的时间差
// 2. 与上一次定位之间的距离
// 3. 本次定位的精度
this.backwardCount +=
1 *
((now - lastLocationTime) / 1000) *
timeDiffCountAdjust *
(currentLocation.properties.accuracy * locationAccuracyAdjust) *
(isSameCartogram
? distDiff >= minDistance
? ((distDiff - 4) / 4) * distanceCountAdjust
: 0
: cartogramDiffCountAdjust);
this.needReroute(currentLocation);
}
if (distDiff > 0) {
return;
}
this.targetPositionOnPath = currentLocationOnPath;
this.lastDistToEnd = distToEnd;
// 如果执行到此处,说明一切正常, 说明没有发生倒退也没有定位到其他道路上的情况
// 表示用户在沿着导航路径前进,此时重置 count,缩短导航路径
this.offRoadCount = 0;
this.backwardCount = 0;
this.lastLocationTime = now;
// 计算需要移动的距离
const sSegmentIndex = this.currentPositionOnPath.properties.segment_index;
const tSegmentIndex = currentLocationOnPath.properties.segment_index;
this.animationDistance =
sSegmentIndex === tSegmentIndex
? turfDistance(this.currentPositionOnPath, currentLocationOnPath, {
units: "meters",
})
: this.fullNavigationPath.features.reduce((d, f) => {
const fSegmentIndex = f.properties.segment_index;
if (fSegmentIndex === sSegmentIndex) {
const sp = this.currentPositionOnPath;
const tp = point(getCoords(f)[1]);
const ld = turfDistance(sp, tp, {
units: "meters",
});
return d + ld;
}
if (fSegmentIndex === tSegmentIndex) {
return (d +
turfDistance(point(getCoords(f)[0]), this.targetPositionOnPath, {
units: "meters",
}));
}
if (fSegmentIndex > sSegmentIndex &&
fSegmentIndex < tSegmentIndex) {
return d + this.segmentDistance(f);
}
return d;
}, 0);
this.animationChain = this.sliceNavigationChain(this.fullNavigationPath, this.currentPositionOnPath);
this.emit("info");
}
needReroute(currentLocation) {
if (this.offRoadCount >= this.offRoadCountLimit ||
this.backwardCount >= this.backwardCountLimit) {
try {
this.reroute(currentLocation);
}
catch (err) {
console.error(err);
}
}
}
reroute(currentLocation) {
const navegador = this.navegador;
const navigationMode = this.navigationMode;
const finishingPoint = this.finishingPoint;
const startingPoint = currentLocation;
const routeOptions = this.routeOptions;
const startingPointOnRoad = navegador.projectionPositionOnRoad(startingPoint);
if (!startingPointOnRoad) {
throw new Error("starting point can not be projected on road");
}
const fullNavigationPath = navegador.getNavigationChain(navigationMode, startingPoint, finishingPoint, routeOptions);
if (!fullNavigationPath) {
throw new Error("can not get navigation path");
}
const startingPointOnPath = navegador.projectionPositionOnChain(startingPointOnRoad);
if (!startingPointOnPath) {
throw new Error("starting point can not be projected on navigation path");
}
const navigationStatus = navegador.getNavigationStatus(startingPointOnRoad);
if (!navigationStatus) {
throw new Error("can not get navigation status");
}
this.fullNavigationPath = fullNavigationPath;
this.lastLocationTime = Date.now();
this.animationDistance = 0;
this.animationChain = fullNavigationPath;
this.targetPositionOnPath = startingPointOnPath;
this.currentPositionOnPath = startingPointOnPath;
this.currentNavigationPath = fullNavigationPath;
this.offRoadCount = 0;
this.backwardCount = 0;
const restOfDistance = (this.lastDistToEnd = navegador.getDistanceToNavigationChainEnd(startingPointOnPath));
this.restOfDistance = Math.floor(restOfDistance);
this.restOfTime = Math.ceil(restOfDistance / WALK_SPEED_PER_MINUTE);
this.navigationHint =
this.getNavigationStatusText({
currentCartogramId: startingPointOnPath.properties["cartogram:id"],
navegador: this.navegador,
navigationStatus,
}) || "";
this.nextDirection = navigationStatus.nextDirection;
this.emit("info");
this.emit("reroute");
}
getNavigationStatusText(params) {
const { navegador, currentCartogramId, navigationStatus: { nextCartogramId, nextDirection, text }, } = params;
let preActionText = "";
switch (nextDirection) {
case Directions.LeftChangeFloor:
case Directions.LeftChangeFloorLeft:
case Directions.LeftChangeFloorRight:
case Directions.LeftChangeFloorStraight:
preActionText = "左转";
break;
case Directions.RightChangeFloor:
case Directions.RightChangeFloorLeft:
case Directions.RightChangeFloorRight:
case Directions.RightChangeFloorStraight:
preActionText = "右转";
break;
case Directions.StraightChangeFloorLeft:
case Directions.StraightChangeFloorRight:
case Directions.StraightChangeFloorStraight:
default:
preActionText = "";
}
let postActionText = "";
switch (nextDirection) {
case Directions.LeftChangeFloorLeft:
case Directions.RightChangeFloorLeft:
case Directions.StraightChangeFloorLeft:
case Directions.LeftChangeFloorRight:
case Directions.RightChangeFloorRight:
case Directions.StraightChangeFloorRight:
case Directions.LeftChangeFloor:
case Directions.LeftChangeFloorStraight:
case Directions.RightChangeFloor:
case Directions.RightChangeFloorStraight:
case Directions.StraightChangeFloorStraight:
default:
postActionText = "";
break;
}
switch (nextDirection) {
case Directions.Left:
return "左转";
case Directions.Right:
return "右转";
case Directions.Straight:
return "直行";
case Directions.LeftArrive:
case Directions.RightArrive:
case Directions.Arrive:
return "目的地就在附近";
case Directions.LeftLeft:
return "连续左转";
case Directions.LeftRight:
return "左转后再右转";
case Directions.RightRight:
return "连续右转";
case Directions.RightLeft:
return "右转后再左转";
case Directions.ChangeFloor:
case Directions.LeftChangeFloor:
case Directions.LeftChangeFloorLeft:
case Directions.LeftChangeFloorRight:
case Directions.LeftChangeFloorStraight:
case Directions.RightChangeFloor:
case Directions.RightChangeFloorLeft:
case Directions.RightChangeFloorRight:
case Directions.RightChangeFloorStraight:
case Directions.StraightChangeFloorLeft:
case Directions.StraightChangeFloorRight:
case Directions.StraightChangeFloorStraight: {
const currentFloor = navegador._cartograms[currentCartogramId];
const nextFloor = navegador._cartograms[nextCartogramId];
const currentFloorNumber = currentFloor.properties
.floor_number;
const { floor_number: nextFloorNumber, floor_label, } = nextFloor.properties;
if (nextFloorNumber > currentFloorNumber) {
return `请${preActionText}上楼到 ${floor_label} ${postActionText}`.trim();
}
return `请${preActionText}下楼到 ${floor_label} ${postActionText}`.trim();
}
default:
return text;
}
}
}
/**
* 导航器
*/
class Navegador {
/**
* 实例化一个导航器
* @param cartogramCollection 甲虫地图集
*/
constructor() {
this._cartograms = {};
// 初始化核心
this._core = JCMapNavigadorCoreNavigation();
this._world = this._core.core_world_new();
}
addBuilding(cartogramCollection) {
const core = this._core;
const world = this._world;
core.core_world_add_building(world, this._createBuilding(cartogramCollection));
cartogramCollection.floors.forEach((cartogram) => {
this._cartograms[cartogram.id] = cartogram;
});
return this;
}
_createBuilding(cartogramCollection) {
const jsonStr = JSON.stringify(cartogramCollection);
const bufferSize = this._core.lengthBytesUTF8(jsonStr);
const buffer = this._core._malloc(bufferSize + 1);
this._core.stringToUTF8(jsonStr, buffer, bufferSize + 1);
const buildingRef = this._core.core_building_new();
this._core.core_parseBuildingByData(buffer, bufferSize, buildingRef);
this._core._free(buffer);
return buildingRef;
}
_createPoint(position) {
const core = this._core;
const [lng, lat] = position.geometry.coordinates;
const pointRef = this._core.core_point_new_by_wgs84(0, lng, lat);
core.core_point_set_floor_id(pointRef, position.properties["cartogram:id"]);
return pointRef;
}
_getNavigationSegmentByIndex(navigationChainRef, navigationSegmentIndex) {
const segmentRef = this._core.core_navichain_get_seg(navigationChainRef, navigationSegmentIndex);
const startingCartogramId = this._core.core_navichain_seg_get_start_floor_id(segmentRef);
const startingLongitude = this._core.core_navichain_seg_get_start_longitude(segmentRef);
const startingLatitude = this._core.core_navichain_seg_get_start_latitude(segmentRef);
const finishingCartogramId = this._core.core_navichain_seg_get_end_floor_id(segmentRef);
const finishingLongitude = this._core.core_navichain_seg_get_end_longitude(segmentRef);
const finishingLatitude = this._core.core_navichain_seg_get_end_latitude(segmentRef);
const distance = this._core.core_navichain_seg_get_distance(segmentRef);
const nextDirection = this._core.core_navichain_seg_next_direction(segmentRef);
const startingCartogram = this._cartograms[startingCartogramId];
const finishingCartogram = this._cartograms[finishingCartogramId];
const startingPointOnRoad = this.projectionPositionOnRoad(point([startingLongitude, startingLatitude], {
"cartogram:id": startingCartogramId,
"cartogram:name": startingCartogram.properties.name,
"cartogram:floor_number": startingCartogram.properties.floor_number,
"cartogram:floor_label": startingCartogram.properties.floor_label,
"building:id": startingCartogram.properties["building:id"],
"building:name": startingCartogram.properties["building:name"],
}));
const finishingPointOnRoad = this.projectionPositionOnRoad(point([finishingLongitude, finishingLatitude], {
"cartogram:id": finishingCartogramId,
"cartogram:name": finishingCartogram.properties.name,
"cartogram:floor_number": finishingCartogram.properties.floor_number,
"cartogram:floor_label": finishingCartogram.properties.floor_label,
"building:id": finishingCartogram.properties["building:id"],
"building:name": finishingCartogram.properties["building:name"],
}));
return lineString([
[startingLongitude, startingLatitude],
[finishingLongitude, finishingLatitude],
], {
segment_index: navigationSegmentIndex,
"starting:cartogram:id": startingCartogramId,
"starting:cartogram:floor_number": startingCartogram.properties.floor_number,
"starting:cartogram:floor_label": startingCartogram.properties.floor_label,
"starting:building:id": startingCartogram.properties["building:id"],
"starting:building:name": startingCartogram.properties["building:name"],
"starting:road_index": startingPointOnRoad
? startingPointOnRoad.properties.road_index
: -1,
"finishing:cartogram:id": finishingCartogramId,
"finishing:cartogram:floor_number": finishingCartogram.properties.floor_number,
"finishing:cartogram:floor_label": finishingCartogram.properties.floor_label,
"finishing:building:id": finishingCartogram.properties["building:id"],
"finishing:building:name": finishingCartogram.properties["building:name"],
"finishing:road_index": finishingPointOnRoad
? finishingPointOnRoad.properties.road_index
: -1,
distance,
next_direction: nextDirection,
});
}
_createNavigationChain(worldRef, routePlanRef, mode) {
// 在创建新navigation chain时确保释放上一个navigation chain
if (this._lastNavigationChainRef) {
this._core.core_navichain_free(this._lastNavigationChainRef);
}
this._lastNavigationChainRef = this._core.core_navichain_new(worldRef, routePlanRef, mode);
return this._lastNavigationChainRef;
}
/**
* 计算起点到终点的步行导航链
* @param startingPoint 起点
* @param finishingPoint 终点
*/
getWalkingNavigationChain(startingPoint, finishingPoint, options) {
const worldRef = this._world;
let startingPointRef;
let finishingPointRef;
let routePlanRef;
try {
startingPointRef = this._createPoint(startingPoint);
finishingPointRef = this._createPoint(finishingPoint);
routePlanRef = this._core.core_world_route_plan_by_walk_with_condition(worldRef, startingPointRef, finishingPointRef, JSON.stringify(options));
if (!routePlanRef) {
return;
}
const navigationChainRef = this._createNavigationChain(worldRef, routePlanRef, NavigationModes.Walking);
const navigationChainLength = this._core.core_navichain_get_seg_count(navigationChainRef);
const navigationEdges = range(0, navigationChainLength).map((segmentIndex) => this._getNavigationSegmentByIndex(navigationChainRef, segmentIndex));
return featureCollection(navigationEdges);
}
catch (err) {
console.error(err);
return;
}
finally {
// routePlanRef && this._core.seq_free(routePlanRef)
startingPointRef && this._core.core_point_free(startingPointRef);
finishingPointRef && this._core.core_point_free(finishingPointRef);
}
}
getNavigationChain(navigationMode = NavigationModes.Walking, startingPoint, finishingPoint, routeOptions) {
let navigationPath;
switch (navigationMode) {
case NavigationModes.Walking:
default:
// 起点和终点已经过线路预览确保可以导航,所以强制转换为 NavigationChain
navigationPath = this.getWalkingNavigationChain(startingPoint, finishingPoint, routeOptions);
}
return navigationPath;
}
/**
* 获取某个位置映射到导航链上后,所在的导航段的索引,在没有导航链或映射失败时返回undefined
* @param position 地图位置
*/
getSegmentIndexOfPositionOnChain(position) {
if (!this._lastNavigationChainRef) {
return;
}
const [lng, lat] = getCoord(position);
return this._core.core_navigate_get_projection_segindex(this._lastNavigationChainRef, position.properties["cartogram:id"], lng, lat);
}
/**
* 映射某个位置到道路上
* @param position 地图位置
*/
projectionPositionOnRoad(position) {
const projectedLngBuf = this._core._malloc(Float64Array.BYTES_PER_ELEMENT * 8);
const projectedLatBuf = this._core._malloc(Float64Array.BYTES_PER_ELEMENT * 8);
try {
const [lng, lat] = position.geometry.coordinates;
const roadIndex = this._core.core_world_get_near_road_point(this._world, position.properties["cartogram:id"], lng, lat, projectedLngBuf, projectedLatBuf);
// 表示投影失败
if (roadIndex < 0) {
return;
}
return point([
this._core.getValue(projectedLngBuf, "double"),
this._core.getValue(projectedLatBuf, "double"),
], {
...position.properties,
accuracy: 0,
road_index: roadIndex,
timestamp: Date.now(),
source: "pseudo",
});
}
catch (err) {
console.error(err);
return;
}
finally {
this._core._free(projectedLngBuf);
this._core._free(projectedLatBuf);
}
}
/**
* 映射某个位置到当前导航链上,在还没有导航链或者映射失败时返回undefined
* @param position 地图位置
*/
projectionPositionOnChain(position) {
let projectedLocationRef = 0;
try {
if (!this._lastNavigationChainRef) {
return;
}
const [oLng, oLat] = getCoord(position);
projectedLocationRef = this._core.core_navigate_get_projection(this._lastNavigationChainRef, position.properties["cartogram:id"], oLng, oLat);
// 表示投影失败
if (!projectedLocationRef) {
return;
}
const lng = this._core.core_point_get_lon(projectedLocationRef);
const lat = this._core.core_point_get_lat(projectedLocationRef);
const segmentIndex = this.getSegmentIndexOfPositionOnChain(position);
if (segmentIndex === undefined) {
return;
}
const pointOnRoad = this.projectionPositionOnRoad(position);
if (!pointOnRoad) {
return;
}
const pointOnPath = point([lng, lat], {
...position.properties,
segment_index: segmentIndex,
road_index: pointOnRoad.properties.road_index,
});
return pointOnPath;
}
catch (err) {
console.error(err);
}
finally {
if (projectedLocationRef) {
this._core.core_point_free(projectedLocationRef);
}
}
}
/**
* 获取某个位置在当前导航图中的状态
* @param position 某个位置
*/
getNavigationStatus(position) {
if (!this._lastNavigationChainRef) {
return;
}
const [lng, lat] = getCoord(position);
const navigationStatusRef = this._core.core_navigate_get_status(this._lastNavigationChainRef, position.properties["cartogram:id"], lng, lat);
if (!navigationStatusRef) {
return;
}
const nextDirection = this._core.core_navigate_status_get_next_direction(navigationStatusRef);
const nextCartogramId = this._core.core_navigate_status_get_next_floor_id(navigationStatusRef);
const distanceToCorner = this._core.core_navigate_status_get_distance_2_corner(navigationStatusRef);
const distanceToEnd = this._core.core_navigate_status_get_distance_2_end(navigationStatusRef);
const cornerNum = this._core.core_navigate_status_get_corners(navigationStatusRef);
this._core.core_navigate_status_free(navigationStatusRef);
return {
nextDirection,
nextCartogramId,
distanceToCorner,
distanceToEnd,
cornerNum,
};
}
/**
* 获取某个位置到当前导航图终点的距离
* @param position 某个位置
*/
getDistanceToNavigationChainEnd(position) {
if (!this._lastNavigationChainRef) {
return;
}
const [lng, lat] = getCoord(position);
return this._core.core_navigate_get_distance_to_end(this._lastNavigationChainRef, position.properties["cartogram:id"], lng, lat);
}
/**
* 设置当前导航链的到达判定距离
* @param distance 判定距离,默认值:人行模式 8 米,车行模式 22 米。
*/
setCurrentArrivalDistance(distance) {
if (!this._lastNavigationChainRef) {
return;
}
this._core.core_navigate_set_arrive_distance(this._lastNavigationChainRef, distance);
}
/**
* 设置上报转弯状态的判定距离
* @param distance 判定距离,默认值:人行模式 8 米,车行模式 22 米。
*/
setCornerDistance(distance) {
if (!this._lastNavigationChainRef) {
return;
}
this._core.core_navigate_set_corner_distance(this._lastNavigationChainRef, distance);
}
}
export { Directions, Navegador, NavegadorTask, NavigationDisplayModes, NavigationModes };