UNPKG

@jcmap-sdk-web/navegador

Version:

甲虫室内定位导航引擎

521 lines (479 loc) 15.6 kB
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;