337 lines
14 KiB
JavaScript
337 lines
14 KiB
JavaScript
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
|
||
} |