diff --git a/package.json b/package.json index f757ede..e37a06b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@turf/turf": "^6.3.0", "ajv": "^8.6.2", "axios": "^0.21.1", + "coordtransform": "^2.1.2", "core-js": "^3.6.4", "echarts": "^3.8.5", "echarts-gl": "^2.0.2", diff --git a/public/config/widget.json b/public/config/widget.json index 4265596..35ee4f4 100644 --- a/public/config/widget.json +++ b/public/config/widget.json @@ -16,10 +16,6 @@ "disableOther": true }, "openAtStart": [ - { - "name": "POI查询", - "uri": "widgets/queryBaiduPOI/widget.js" - }, { "name": "右上角工具栏", "uri": "widgets/toolBarRight/widget.js", diff --git a/public/img/sousuokuang.png b/public/img/sousuokuang.png new file mode 100644 index 0000000..a4a6490 Binary files /dev/null and b/public/img/sousuokuang.png differ diff --git a/src/views/home/index.vue b/src/views/home/index.vue index 5eada7d..ca6eb51 100644 --- a/src/views/home/index.vue +++ b/src/views/home/index.vue @@ -48,6 +48,11 @@
历史标注
+ + +
+ +
@@ -239,7 +244,8 @@ import rightclicksetting from './widget/rightclick.vue'; import socketDiaBox from './widget/socketdiabox.vue'; import userpoint from './widget/userpoint.vue' import drag from '../../utils/draged'; -import editorbox from './widget/editorbox' +import editorbox from './widget/editorbox'; +import search from './widget/search' let BASE_URL = process.env.VUE_APP_BASE_API export default { @@ -267,7 +273,8 @@ export default { socketDiaBox, taskDelivery, userpoint, - editorbox + editorbox, + search }, data() { @@ -727,6 +734,7 @@ export default { // 地图构造完成回调 onMapload(map) { let _self = this; + window.globalmap = map; this.globalmap = map; map.on(this.mars3d.EventType.click,this.onMapClick) diff --git a/src/views/home/lib/routePath.js b/src/views/home/lib/routePath.js new file mode 100644 index 0000000..cabff7b --- /dev/null +++ b/src/views/home/lib/routePath.js @@ -0,0 +1,455 @@ +import axios from "axios"; +import { gcj02towgs84, wgs84togcj02 } from 'coordtransform' +import * as turf from '@turf/turf' +import { Url } from "../../../utils/urlFormat"; +import { getMethodCommon } from "../../../api/common"; +import { parse } from "../lib/handleGeojson"; +import { deepClone } from "../../../utils"; +const urls = new Url() +const GD_URL = "https://restapi.amap.com/v5/direction/driving" +const GD_KEY = "6af6a87038f44c8c793aa70331f2b7ca" + +//路线的图层 +let pathGraphicLayers = null + +//导航寻路 +export const getRouterFunc = (params, method = 'all') => { + /** + * method: + * 当为 postgis时,仅仅使用postgis导航 + * 默认:all :高德 + postgis + * gaode:高德 + */ + let { startlng, startlat, endlng, endlat } = params + + if (method == 'postgis') { + //使用gpostgis进行导航 + return new Promise((resolve, reject) => { + let postgisParams = { + startlng: startlng, + startlat: startlat, + endlng: endlng, + endlat: endlat, + areaname: localStorage.getItem("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: localStorage.getItem("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) + }) + }) + }) + } + let sql = ` + create table if not exists comment( + id int(11) unsigned not null auto_increment comment "主键ID", + pins_id int(11) not null comment "帖子ID", + content varchar(250) not null comment "评论内容", + create_time datatime not null comment "评论时间", + like int(11) not null default 0 comment "点赞数量", + state tinyint(1) not null default 1 comment "状态:1:正常;0:删除;", + primary key(id) + )ENGINE=innodb DEFAULT CHARSET=utf8 + ` + + if(method == 'gaode'){ + 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: localStorage.getItem("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) + }) + }) + }) + } +} + + + + +//高德路线导航 +export 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 + } + 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寻路 +export const getRouterByPostGis = (params) => { + return new Promise((resolve, reject) => { + getMethodCommon("/FirePrevention/LoadRoad", params).then((res) => { + if (res.data.length > 0) { + console.log('res.data::: ', res.data); + 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)) + }) + }) +} + +// 绘制线路 +export const drawRouterFunc = (pathObject) => { + let { allCoordinates, startLngLat, endLngLat, startRouterLngLat, endRouterLngLat, gdRoute, postGisRoute } = pathObject + // 添加graphic + if (pathGraphicLayers == null) { + pathGraphicLayers = new mars3d.layer.GraphicLayer(); + window.globalmap.addLayer(pathGraphicLayers); + + } else { + pathGraphicLayers.clear(); + } + //当只有两组数据时,说明没有导航的路线,直接使用虚线连接 + console.log('allCoordinates.length::: ', allCoordinates.length); + if (allCoordinates.length == 2) { + //只绘制开头到结尾的路线的路段 + let endPathGraphic = drawPolylineDashEntity([startLngLat, endLngLat], 'YELLOW') + pathGraphicLayers.addGraphic(endPathGraphic); + } else { + // 导航数据路段 + let pathGraphic = drawLineFlowEntity(allCoordinates, 'CYAN') + let gdPathGraphic = drawLineFlowEntity(gdRoute, 'YELLOW') + let postgisPathGraphic = drawLineFlowEntity(postGisRoute, 'YELLOW') + + //开始的路段 + let startPathGraphic = drawPolylineDashEntity([startLngLat, startRouterLngLat], 'YELLOW') + + //结尾的路段 + let endPathGraphic = drawPolylineDashEntity([endRouterLngLat, endLngLat], 'YELLOW') + + pathGraphicLayers.addGraphic(pathGraphic); + // pathGraphicLayers.addGraphic(gdPathGraphic); + // pathGraphicLayers.addGraphic(postgisPathGraphic); + pathGraphicLayers.addGraphic(startPathGraphic); + pathGraphicLayers.addGraphic(endPathGraphic); + } +} + +// 生成虚线实体 +const drawPolylineDashEntity = (positions, cesiumColor) => { + return new mars3d.graphic.PolylineEntity({ + positions: positions, + style: { + width: 5, + clampToGround: true, + materialType: mars3d.MaterialType.PolylineDash, + materialOptions: { + color: Cesium.Color[cesiumColor], + dashLength: 8.0, + }, + }, + }); +} +// 生成动态线实体 +const drawLineFlowEntity = (positions, cesiumColor) => { + return new mars3d.graphic.PolylineEntity({ + positions: positions, + style: { + width: 5, + clampToGround: true, + materialType: mars3d.MaterialType.LineFlow, + materialOptions: { + color: Cesium.Color[cesiumColor], + image: "/img/line-colour.png", + speed: 10, + }, + }, + }); +} + + + +//删除路线 +export const clearRouterFunc = () => { + if (pathGraphicLayers == null) { + return false + } else { + pathGraphicLayers.clear(); + } +} + +//计算最近路线 +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 }); + console.log('overlapping::: ', overlapping); + 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 + console.log('geometry::: ', geometry); + 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) => { + console.log('geojson::: ', geojson); + return geojson.features.map(feature => { + return feature.geometry.coordinates + }) + +} \ No newline at end of file diff --git a/src/views/home/widget/routebox.vue b/src/views/home/widget/routebox.vue index 36f2c0a..58251d7 100644 --- a/src/views/home/widget/routebox.vue +++ b/src/views/home/widget/routebox.vue @@ -29,7 +29,8 @@ import { parse } from '../lib/handleGeojson.js'; import {transformGCJ2WGS} from '../lib/GCJ02ToWGS84.js'; import {transformWGSGCJ2} from '../lib/WGS84ToGCJ02.js'; import * as turf from '@turf/turf' -import AppConfig from '../../../../public/config/app.json' +import AppConfig from '../../../../public/config/app.json'; +import { getRouterFunc, drawRouterFunc, clearRouterFunc } from '../lib/routePath' export default { name: 'routebox', props:['globalmap','routeInfo'], @@ -124,44 +125,61 @@ import AppConfig from '../../../../public/config/app.json' }) this.pathPointGraphicLayer.addGraphic(graphic) // 还可以另 }, - getRoutePath(){ // 获取路线数据 - if(this.startPoint&&this.endPoint){ - let startCoor = this.startPoint.split(","); - let endCoor = this.endPoint.split(","); - axios({ - method: 'get', - url: AppConfig[localStorage.getItem('areaName')].baseUrl+'/api/FirePrevention/LoadRoad', - params: { - startlng: parseFloat(startCoor[0]), - startlat: parseFloat(startCoor[1]), - endlng: parseFloat(endCoor[0]), - endlat: parseFloat(endCoor[1]), - } - }).then(data => { - if (data.data.data.length > 0) { - - let LineString = data.data.data[0].route; - - if(LineString == null || LineString == "null"){ - this.getGaoDePath(); - }else{ - let _this = this; - setTimeout(function () { - console.log("linestring",LineString) - _this.drawRoutePath(parse(LineString)); - // designPath(parse(LineString)); - }, 1000) - } - - }else{ - - } - }) - - }else{ - - } + getRoutePath() { + // 获取路线数据 + let startCoor = this.startPoint.split(",") + let endCoor = this.endPoint.split(","); + let params = { + startlng: parseFloat(startCoor[0]), + startlat: parseFloat(startCoor[1]), + endlng: parseFloat(endCoor[0]), + endlat: parseFloat(endCoor[1]), + areaname: localStorage.getItem("areaName"), + }; + getRouterFunc(params,).then(res => { + drawRouterFunc(res) + }).catch(err => { + clearRouterFunc() + }) }, + // getRoutePath(){ // 获取路线数据 + // if(this.startPoint&&this.endPoint){ + // let startCoor = this.startPoint.split(","); + // let endCoor = this.endPoint.split(","); + // axios({ + // method: 'get', + // url: AppConfig[localStorage.getItem('areaName')].baseUrl+'/api/FirePrevention/LoadRoad', + // params: { + // startlng: parseFloat(startCoor[0]), + // startlat: parseFloat(startCoor[1]), + // endlng: parseFloat(endCoor[0]), + // endlat: parseFloat(endCoor[1]), + // } + // }).then(data => { + // if (data.data.data.length > 0) { + + // let LineString = data.data.data[0].route; + + // if(LineString == null || LineString == "null"){ + // this.getGaoDePath(); + // }else{ + // let _this = this; + // setTimeout(function () { + // console.log("linestring",LineString) + // _this.drawRoutePath(parse(LineString)); + // // designPath(parse(LineString)); + // }, 1000) + // } + + // }else{ + + // } + // }) + + // }else{ + + // } + // }, getGaoDePath(){ let startLngLatArr = this.startPoint.split(","); let endLngLatArr = this.endPoint.split(","); @@ -336,6 +354,7 @@ import AppConfig from '../../../../public/config/app.json' _this.addContourLine(graphic.toGeoJSON({outline:true}).geometry.coordinates); },2000) }, + addContourLine(coordinates) { this.contourLine = new mars3d.thing.ContourLine({ shadingType:"slope", @@ -408,6 +427,8 @@ import AppConfig from '../../../../public/config/app.json' this.contourLine.removeArea(this.contourLineAreaObj); } + clearRouterFunc(); + this.startPoint = null; this.endPoint = null; }, diff --git a/src/views/home/widget/search.vue b/src/views/home/widget/search.vue new file mode 100644 index 0000000..66e9754 --- /dev/null +++ b/src/views/home/widget/search.vue @@ -0,0 +1,302 @@ + + +