const axios = require('axios'); const { gcj02towgs84, wgs84togcj02 } = require('coordtransform') const turf = require('@turf/turf'); const { Url } = require('./urlFormat'); const { parse } = require('./handleGeojson'); const { deepClone } = require('./tools'); const urls = new Url() const GD_URL = "https://restapi.amap.com/v5/direction/driving" const GD_KEY = "6af6a87038f44c8c793aa70331f2b7ca" const POSTGIS_SERVER = { 'pingyixian': 'http://123.132.248.154:9231/api/FirePrevention/LoadRoad', 'feixian': '', 'yishuixian': '', } //路线的图层 let pathGraphicLayers = null //导航寻路 const getRouterFunc = (params, method = 'all') => { /** * method: * 当为 postgis时,仅仅使用postgis导航 * 默认:all :高德 + postgis * gaode:高德 */ let { startlng, startlat, endlng, endlat,areaname } = params if (method == 'postgis') { //使用gpostgis进行导航 return new Promise((resolve, reject) => { let postgisParams = { startlng: startlng, startlat: startlat, endlng: endlng, endlat: endlat, areaname: areaname, } getRouterByPostGis(postgisParams).then(geojson => { //postGisCoordinates:postgis返回的geojson取出坐标数组 let postGisCoordinates = getOneLineCoordinatesFromGeometry(geojson) let startRouterLngLat = postGisCoordinates[0] let endRouterLngLat = postGisCoordinates.at(-1) let resObject = { allCoordinates: postGisCoordinates, //全部线路的合集 postGisRoute: postGisCoordinates, // postgis线路 gdRoute: [], startLngLat: [startlng, startlat], // 起点 endLngLat: [endlng, endlat], //终点 startRouterLngLat: startRouterLngLat, // 路线查询结果的起点 endRouterLngLat: endRouterLngLat, //路线查询结果的终点 } let simpleRoute = getMinimumRoute(resObject) resolve(simpleRoute) }) }).catch(err => { }) } if (method == 'all') { // 先用高德进行导航 return new Promise((resolve, reject) => { getRouterByGD(params).then(solution => { // solution 为多条线路的数组,现在先用第一条线路 solution.path[0] let gdRoute = solution.path[0] let postgisParams = { startlng: gdRoute.endCoordinates[0], startlat: gdRoute.endCoordinates[1], endlng: endlng, endlat: endlat, areaname: areaname, } //使用gpostgis求出剩下的路线 getRouterByPostGis(postgisParams).then(geojson => { //postGisCoordinates:postgis返回的geojson取出坐标数组 let postGisCoordinates = getOneLineCoordinatesFromGeometry(geojson) // 高德返回的第一条线路的坐标数组 let path_gd = gdRoute.path_polyline //合并高德和postgis的路线 let allCoordinates = path_gd.concat(postGisCoordinates) //导航线路的起点和终点 let startRouterLngLat = allCoordinates[0] let endRouterLngLat = allCoordinates.at(-1) let resObject = { allCoordinates: allCoordinates, //全部线路 gdRoute: path_gd, //高德的线路 postGisRoute: postGisCoordinates, // postgis的线路 startLngLat: [startlng, startlat], // 起点 endLngLat: [endlng, endlat], //终点 startRouterLngLat: startRouterLngLat, // 路线查询结果的起点 endRouterLngLat: endRouterLngLat, //路线查询结果的终点 } let simpleRoute = getMinimumRoute(resObject) resolve(simpleRoute) }) }) }) } } //高德路线导航 const getRouterByGD = (params) => { /** * type:Object */ let { startlng, startlat, endlng, endlat } = params //wgs84转火星坐标系 var gcj02StartLngLat = wgs84togcj02(startlng, startlat); var gcj02EndLngLat = wgs84togcj02(endlng, endlat); let gd_params = { origin: `${gcj02StartLngLat[0]},${gcj02StartLngLat[1]}`, destination: `${gcj02EndLngLat[0]},${gcj02EndLngLat[1]}`, show_fields: 'polyline', key: GD_KEY, strategy:2 } let new_url = urls.getUrl(GD_URL, gd_params) return new Promise((resolve, reject) => { axios({ method: "get", url: new_url, }).then((res) => { if (res.status === 200) { let solution = [] //处理数据 res.data.route.paths.map(path => { let route_len = path.distance let path_polyline = path.steps.map(step => { return step.polyline }) let router_path_str = [] //暂时存放 ['117.927498,35.263264'] path_polyline.forEach(polyline => { let step = polyline.split(';') router_path_str = router_path_str.concat(step) }); // 去掉重复点 let unique_router_path_str = [...new Set(router_path_str)] // 坐标转数组 let unique_router_path = unique_router_path_str.map(path_str => { let lng_lat_list = path_str.split(',') //高德坐标系转wgs84坐标系 var wgs84Coordinate = gcj02towgs84(...lng_lat_list); return wgs84Coordinate }) //高德导航的起点和终点 let startCoordinates = unique_router_path[0] let endCoordinates = unique_router_path.at(-1) // 高德地图返回的结果:方案一。长度,线 solution.push( { route_len: route_len, path_polyline: unique_router_path, startCoordinates: startCoordinates, endCoordinates: endCoordinates } ) }) let result = { routerCount: parseInt(res.data.count), path: solution } resolve(result) } else { reject(res) } }).catch(err => { reject(err) }) }) } //使用postgres + postgis寻路 const getRouterByPostGis = (params) => { return new Promise((resolve, reject) => { axios({ method: "get", url: POSTGIS_SERVER[params.areaname], params }).then((r) => { let res = r.data if (res.data.length > 0) { let LineString = res.data[0].route; if (LineString == null || LineString == "null") { //没有找到路线,返回空 console.log('PostGIS未找到合适了路线') resolve(parse(null)) } else { resolve(parse(LineString)) } } else { console.log('PostGIS未找到合适了路线') resolve(parse(null)) } }).catch(err => { console.log('PostGIS寻路算法服务端错误') resolve(parse(null)) }) }) } //计算最近路线 const getMinimumRoute = (pathObject) => { //备份Object let pathObjectClone = deepClone(pathObject) let { allCoordinates, startLngLat, endLngLat, startRouterLngLat, endRouterLngLat, gdRoute, postGisRoute } = pathObjectClone // 当只有一个点时(终点),说明高德地图和postgis都未查询到线路,直接返回两点 if (allCoordinates.length <= 1) { pathObjectClone.allCoordinates = [startLngLat, endLngLat] return pathObjectClone } //当postgis寻路时,计算两条线路的重叠之处 if (postGisRoute.length && gdRoute.length) { //实例化turf标准格式 let gdRouteLine = turf.lineString(gdRoute); let postGisRouteLine = turf.lineString(postGisRoute); //获取postgis和高德寻路的所有交点 let intersectsGeojson = turf.lineIntersect(gdRouteLine, postGisRouteLine); let intersectsCoordinates = getMultPointCoordinatesFromGeoJson(intersectsGeojson) //如果相交点大于1,说明路线有重复部分 if (intersectsCoordinates.length > 1) { let lastIntersectsCoordinates = intersectsCoordinates[0] let [slicedGdCoordinates, slicedPostGisCoordinates] = sliceByPoint(startRouterLngLat, gdRouteLine, endRouterLngLat, postGisRoute, lastIntersectsCoordinates) allCoordinates = slicedGdCoordinates.concat(slicedPostGisCoordinates) //处理后的结果赋值给pathObjectClone pathObjectClone.gdRoute = slicedGdCoordinates pathObjectClone.postGisRoute = slicedPostGisCoordinates gdRouteLine = turf.lineString(slicedGdCoordinates); postGisRouteLine = turf.lineString(slicedPostGisCoordinates); } // 阈值计算重复路线,去除重复线路 let overlapping = turf.lineOverlap(gdRouteLine, postGisRouteLine, { tolerance: 0.1 }); if (overlapping.features.length) { let lastOverlapPoint = overlapping.features.at(-1).geometry.coordinates[0] let [overlapGdCoordinates, overlapPostGisCoordinates] = sliceByPoint(startRouterLngLat, gdRouteLine, endRouterLngLat, postGisRoute, lastOverlapPoint) allCoordinates = overlapGdCoordinates.concat(overlapPostGisCoordinates) // 连接路段平滑过渡 } } // 转成turf标准线格式 let allRouteLine = turf.lineString(allCoordinates); // 转成turf标准点格式 let startLngLatPoint = turf.point(startLngLat); let startRouterLngLatPoint = turf.point(startRouterLngLat); let endLngLatPoint = turf.point(endLngLat); let endRouterLngLatPoint = turf.point(endRouterLngLat); //获取终点到导航线最近的点 let snappedGeojson = turf.nearestPointOnLine(allRouteLine, endLngLatPoint, { units: 'miles' }); let snappedCoordinates = getOnePointCoordinatesFromGeoJson(snappedGeojson) // 根据最近的点截取路线,取前半部分 let slicedGeojson = turf.lineSlice(startRouterLngLat, turf.point(snappedCoordinates), allRouteLine); let slicedCoordinates = getOnePointCoordinatesFromGeoJson(slicedGeojson) //把截取后的路线赋值给pathObjectClone pathObjectClone.allCoordinates = slicedCoordinates //计算出发地到目的地的图上距离(直线) let distanceStartToEnd = turf.distance(startLngLatPoint, endLngLatPoint) //计算出发点到出发导航路线出发点的步行距离 let distanceStartToStartRoute = turf.distance(startLngLatPoint, startRouterLngLatPoint) //计算终点到出发导航路线终点的步行距离 let distanceEndToEndRoute = turf.distance(endLngLatPoint, endRouterLngLatPoint) //如果出发点与目的地的实际距离小于步行的距离,直接使用出发点到目的地的距离,导航此时不适用 if (distanceStartToEnd < (distanceStartToStartRoute + distanceEndToEndRoute)) { pathObjectClone.allCoordinates = [startLngLat, endLngLat] } //把终点到导航终点改为距离线路的最近的的点 pathObjectClone.endRouterLngLat = snappedCoordinates return pathObjectClone } const sliceByPoint = (line1Start, line1, line2End, line2, point) => { /** * 根据点point把line1的前半部分和line2的后半部分进行拼接 * line1Start:line1的起始点 [lng.lat] * line2End:line2 的终止点 [lng,lat] */ //拷贝line2的坐标数组进行倒序排列 let line2Copy = [...line2] line2Copy.reverse() let line2CopyReverseLineString = turf.lineString(line2Copy); // 根据point截取路线,line1取前半部分 // 根据point截取路线,line2取后半部分 //然后将两部分拼接,让line1路线从第一个交点处转向line2路段 let slicedLine1Geojson = turf.lineSlice(line1Start, turf.point(point), line1); let slicedLine1Coordinates = getOnePointCoordinatesFromGeoJson(slicedLine1Geojson) let slicedLine2Geojson = turf.lineSlice(line2End, turf.point(point), line2CopyReverseLineString); let slicedLine2Coordinates = getOnePointCoordinatesFromGeoJson(slicedLine2Geojson) slicedLine2Coordinates.reverse() return [slicedLine1Coordinates, slicedLine2Coordinates] } // 坐标转geoJson const comLineStringGeoJson = (coordinates) => { return { "type": "Feature", "properties": {}, "geometry": { "coordinates": coordinates, "type": "LineString" } } } // 从一条线的geometry中获取坐标 const getOneLineCoordinatesFromGeometry = (geometry) => { let coordinates = geometry.coordinates let list = [] if (geometry.type == "MultiLineString") { coordinates.map(coord => { list = list.concat(coord) }) } else if (geometry.type == 'LineString') { list = list.concat(geometry.coordinates) } else { list = [] } return list } //从一个点的geojson中返回坐标点 const getOnePointCoordinatesFromGeoJson = (geojson) => { return geojson.geometry.coordinates } //从多个点的geojson中返回坐标点 const getMultPointCoordinatesFromGeoJson = (geojson) => { return geojson.features.map(feature => { return feature.geometry.coordinates }) } module.exports = { getRouterFunc }