@jcmap-sdk-web/navegador
Version:
甲虫室内定位导航引擎
521 lines (479 loc) • 15.6 kB
text/typescript
import {
lineString as turfLineString,
point as turfPoint,
featureCollection as turfFeatureCollection,
} from "@turf/helpers";
import { getCoord as turfGetCoord } from "@turf/invariant";
import { range } from "ramda";
import type {
NavigationModule,
Emscripten,
} from "@jcmap-sdk-web/navegador-core";
import { JCMapNavigadorCoreNavigation } from "@jcmap-sdk-web/navegador-core";
import type {
Cartogram,
CartogramCollection,
CartogramPosition,
} from "@jcmap-sdk-web/jcmap";
import type {
NavigationSegment,
NavigationChain,
NavigationMode,
NavigationPositionOnChain,
NavigationPositionOnRoad,
Direction,
NavigationStatus,
} from "./types";
import { NavigationModes } from "./types";
import type { NavigationPosition } from "@jcmap-sdk-web/locator";
export interface GetNavigationChainOptions {}
export interface GetWalkingNavigationChainOptions
extends GetNavigationChainOptions {
lift?: "yes" | "no";
escalator?: "yes" | "no";
stair?: "yes" | "no";
}
export interface GetDrivingNavigationChainOptions
extends GetNavigationChainOptions {}
export interface StartNavigationTaskOption {
/**
* 重新规划路线前所需偏离判定的次数
*/
offRoadCountLimit?: number;
/**
* 重新规划路线前所需后退判定的次数
*/
backwardCountLimit?: number;
/**
* 判定到达终点的距离
*
* default: 8
*/
arrivalDistance?: number;
}
/**
* 导航器
*/
export class Navegador {
private _core: Emscripten & NavigationModule;
private _world: number;
private _lastNavigationChainRef?: number;
_cartograms: {
[key: string]: Cartogram;
} = {};
/**
* 实例化一个导航器
* @param cartogramCollection 甲虫地图集
*/
constructor() {
// 初始化核心
this._core = JCMapNavigadorCoreNavigation();
this._world = this._core.core_world_new();
}
addBuilding(cartogramCollection: CartogramCollection): this {
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;
}
private _createBuilding(cartogramCollection: CartogramCollection): number {
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;
}
private _createPoint(position: CartogramPosition): number {
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;
}
private _getNavigationSegmentByIndex(
navigationChainRef: number,
navigationSegmentIndex: number
): NavigationSegment {
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: Direction = this._core.core_navichain_seg_next_direction(
segmentRef
) as Direction;
const startingCartogram = this._cartograms[startingCartogramId];
const finishingCartogram = this._cartograms[finishingCartogramId];
const startingPointOnRoad = this.projectionPositionOnRoad(
turfPoint<CartogramPosition["properties"]>(
[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"],
}
) as CartogramPosition
);
const finishingPointOnRoad = this.projectionPositionOnRoad(
turfPoint<CartogramPosition["properties"]>(
[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"],
}
) as CartogramPosition
);
return turfLineString<NavigationSegment["properties"]>(
[
[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,
}
) as NavigationSegment;
}
_createNavigationChain(worldRef: number, routePlanRef: number, mode: number) {
// 在创建新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: CartogramPosition,
finishingPoint: CartogramPosition,
options?: GetWalkingNavigationChainOptions
): NavigationChain | undefined {
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 turfFeatureCollection(navigationEdges) as NavigationChain;
} 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: NavigationMode = NavigationModes.Walking,
startingPoint: CartogramPosition,
finishingPoint: CartogramPosition,
routeOptions?: GetNavigationChainOptions
) {
let navigationPath;
switch (navigationMode) {
case NavigationModes.Walking:
default:
// 起点和终点已经过线路预览确保可以导航,所以强制转换为 NavigationChain
navigationPath = this.getWalkingNavigationChain(
startingPoint,
finishingPoint,
routeOptions
);
}
return navigationPath;
}
/**
* 获取某个位置映射到导航链上后,所在的导航段的索引,在没有导航链或映射失败时返回undefined
* @param position 地图位置
*/
getSegmentIndexOfPositionOnChain(
position: NavigationPosition
): number | undefined {
if (!this._lastNavigationChainRef) {
return;
}
const [lng, lat] = turfGetCoord(position);
return this._core.core_navigate_get_projection_segindex(
this._lastNavigationChainRef,
position.properties["cartogram:id"],
lng,
lat
);
}
/**
* 映射某个位置到道路上
* @param position 地图位置
*/
projectionPositionOnRoad(
position: CartogramPosition
): NavigationPositionOnRoad | undefined {
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 turfPoint<NavigationPositionOnRoad["properties"]>(
[
this._core.getValue(projectedLngBuf, "double"),
this._core.getValue(projectedLatBuf, "double"),
],
{
...position.properties,
accuracy: 0,
road_index: roadIndex,
timestamp: Date.now(),
source: "pseudo",
}
) as NavigationPositionOnRoad;
} catch (err) {
console.error(err);
return;
} finally {
this._core._free(projectedLngBuf);
this._core._free(projectedLatBuf);
}
}
/**
* 映射某个位置到当前导航链上,在还没有导航链或者映射失败时返回undefined
* @param position 地图位置
*/
projectionPositionOnChain(
position: NavigationPosition
): NavigationPositionOnChain | undefined {
let projectedLocationRef = 0;
try {
if (!this._lastNavigationChainRef) {
return;
}
const [oLng, oLat] = turfGetCoord(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 = turfPoint<NavigationPositionOnChain["properties"]>(
[lng, lat],
{
...position.properties,
segment_index: segmentIndex,
road_index: pointOnRoad.properties.road_index,
}
) as NavigationPositionOnChain;
return pointOnPath;
} catch (err) {
console.error(err);
} finally {
if (projectedLocationRef) {
this._core.core_point_free(projectedLocationRef);
}
}
}
/**
* 获取某个位置在当前导航图中的状态
* @param position 某个位置
*/
getNavigationStatus(
position: NavigationPosition
): NavigationStatus | undefined {
if (!this._lastNavigationChainRef) {
return;
}
const [lng, lat] = turfGetCoord(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
) as Direction;
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: NavigationPosition
): number | undefined {
if (!this._lastNavigationChainRef) {
return;
}
const [lng, lat] = turfGetCoord(position);
return this._core.core_navigate_get_distance_to_end(
this._lastNavigationChainRef,
position.properties["cartogram:id"],
lng,
lat
);
}
/**
* 设置当前导航链的到达判定距离
* @param distance 判定距离,默认值:人行模式 8 米,车行模式 22 米。
*/
setCurrentArrivalDistance(distance: number) {
if (!this._lastNavigationChainRef) {
return;
}
this._core.core_navigate_set_arrive_distance(
this._lastNavigationChainRef,
distance
);
}
/**
* 设置上报转弯状态的判定距离
* @param distance 判定距离,默认值:人行模式 8 米,车行模式 22 米。
*/
setCornerDistance(distance: number) {
if (!this._lastNavigationChainRef) {
return;
}
this._core.core_navigate_set_corner_distance(
this._lastNavigationChainRef,
distance
);
}
}
export default Navegador;