import AMapLoader from '@amap/amap-jsapi-loader'; import china from '/@/assets/lngLat/china.ts'; import chinaInner from '/@/assets/lngLat/chinaInner.ts'; import { createCircle, createIcon, getCenter, getLonLat, typeToIcon, typeToIcon1 } from '/@/assets/mapUtils/commonFun.ts'; import { aircraftContent, alarmIcon, female, male, watchIconR } from '/@/components/chinaMap/chinaHooks.ts'; import { isFunction } from '/@/utils/is.ts'; import { Callback, ExtDataType, InitMap, MarkerOption, ResultType } from '/@/assets/mapUtils/mapFunTypes.ts'; import { defaultOption, getZoom, mapKey, mapPlugin } from '/@/assets/mapUtils/mapConstant.ts'; let AMap = null; export const Map = { aMap: null, map: null, overlayGroup: null, // 地图中marker circleGroup: null, //直升机背景圆 cluster: null, //直升机marker点聚合图层 airMarkerList: [], //直升机markerList placeSearch: null, //地点查询 marker: null, // 健康终端报警 infoWindow: null, //地图弹窗 driveLine: null, // 导航轨迹线 emergencyMarker: null, // 应急人员marker startMarker: null, endMarker: null, /** * @desc 地图舒适化 * @param el dom元素 id选择器 * @param option 地图配置 * @param innerLineOption 内轨迹线配置 * @param outlineOption 外轨迹线配置 */ initMap({ el, option, innerLineOption, outerLineOption }: InitMap) { return new Promise((resolve) => { AMapLoader.load({ key: mapKey, // 申请好的Web端开发者Key,首次调用 load 时必填 version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15 plugins: mapPlugin, // 需要使用的的插件列表,如比例尺'AMap.Scale'等 }).then((res) => { AMap = res; this.aMap = res; const { zoom, center, mapStyle } = { ...defaultOption, ...option }; //基本地图加载 this.map = new AMap.Map(el, { resizeEnable: true, center: center, zoom: zoom, mapStyle: 'amap://styles/' + mapStyle, version: '2.0', }); // 异步同时加载多个插件 new AMap.plugin(mapPlugin, () => { if (outerLineOption) { this.drawInnerLine(innerLineOption); this.drawOutLine(outerLineOption); } else { this.drawInnerLine(); this.drawOutLine(); } }); resolve(); }); }); }, /** *#desc 获取地图中心点 */ getMapCenter() { return this.map.getCenter(); }, /** * @desc 中国外层边界线 */ drawOutLine(outerLineOption = {}) { let line = { strokeColor: '#2384dd', strokeWeight: 3, isOutline: false, }; let geoJson = new AMap.GeoJSON({ geoJSON: china, getPolygon: function (geoJson: string, lngLats: any) { return new AMap.Polyline({ path: lngLats[0], ...line, ...outerLineOption, }); }, }); this.map.add(geoJson); } /** * @desc 中国内层边界线 */, drawInnerLine(innerLineOption = {}) { let line = { isOutline: true, outlineColor: '#2692fc', borderWeight: 1, strokeColor: '#88b9f8', strokeWeight: 1, strokeOpacity: 0.8, }; let geoJson = new AMap.GeoJSON({ geoJSON: chinaInner, getPolygon: function (geoJson: string, lngLats: any) { return new AMap.Polyline({ path: lngLats[0], ...line, ...innerLineOption }); }, }); this.map.add(geoJson); }, /** * @desc 设置地图级别与中心点 * @param center 【lon,lat】 * @param zoom 地图层级 * */ setZoomCenter(center, zoom = 12) { this.map && this.map.setZoomAndCenter(zoom, center); }, /** * @desc 清空当前地图上除了边界线的覆盖物 */ removeOverlayGroup() { this.clearAircraft(); this.clearOverlay(); this.clearAirRange(); this.clearMarker(); // if (this.airMarkerList) { this.airMarkerList = []; } this.clearActiveHospital(); this.clearInfoModal(); // this.map.remove(this.airMarkerList); }, // /** * @desc 创建覆盖物组,用于批量处理点 (控制显隐,点击事件等) * @param result * @param extData * @param callback * @param computedCenter 是否计算中心点 */ createOverlay(result: ResultType[], extData?: ExtDataType, callback?: Callback, computedCenter?: boolean, iconType?: string) { this.removeOverlayGroup(); this.overlayGroup = new AMap.OverlayGroup(); // const list: any[] = getLonLat(result); // const center = getCenter(list); // const zoom = getZoom(list); let arr: any[] = []; for (let i = 0; i < result.length; i++) { let { type, lon, lat, longitude, latitude, lng, centerResource } = result[i]; if (longitude) { lon = longitude; } if (latitude) { lat = latitude; } if (lng) { lon = lng; } if (!lon || !lat || !type) { continue; } // 创建一个 Icon let startIcon = typeToIcon1(type); // 将 icon 传入 marker let startMarker = new AMap.Marker({ // 点的坐标 position: [lon, lat], // 指定点的icon,也可以是个url ,但这样就不能对图片进行设置了 icon: startIcon, //启用点击事件 如果需要点击事件,必须开启 clickable: true, //点额外携带的数据,用户自定义属性,支持JavaScript API任意数据类型 offset: [-18, -32], maxZoom: 14, extData: result[i], label: { content: isFunction(extData?.content) && extData?.content(result[i]), }, ...extData?.extOption, }); arr.push(startMarker); // 可以调用setExtData()设置自定义属性 //将组 挂载到地图上 this.overlayGroup && this.overlayGroup.setMap(this.map); //将需要批量控制的点放到一个组中,通过控制组,就可以操作组内所有所有覆盖物 this.overlayGroup && this.overlayGroup.addOverlay(startMarker); } this.overlayGroup && this.overlayGroup.on('click', function (e: any) { //拿到点中携带的数据 if (!isFunction(callback)) return; callback(e.target.getExtData()); }); if (computedCenter) { this.map && this.map.setFitView(arr, false, [50, 50, 0, 50]); } return arr; }, /** * @desc 创建覆盖物组,用于批量处理点 (控制显隐,点击事件等) * @param result * @param extData * @param callback * @param computedCenter 是否计算中心点 */ async createOverlay1(result: ResultType[], extData?: ExtDataType, callback?: Callback, computedCenter?: boolean, iconType?: string) { this.removeOverlayGroup(); this.overlayGroup = new AMap.OverlayGroup(); const list: any[] = getLonLat(result); const center = getCenter(list); const zoom = getZoom(list); if (computedCenter) { this.setZoomCenter(center, zoom); } function createTextOverlayIcon(originalIconUrl, text, options = {}) { return new Promise((resolve) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); img.crossOrigin = 'anonymous'; img.onload = function () { // 设置画布大小(比原图稍大以容纳文字) canvas.width = 28; canvas.height = 49 + 15; // 为文字留出空间 // 绘制原始图标 ctx.drawImage(img, 0, 10, 28, 49); // 绘制圆形文字背景 const textX = 22; // 文字X坐标(居右对齐) const textY = 11; // 文字Y坐标 const textWidth = ctx.measureText(text).width; const circleRadius = Math.max(textWidth / 2 + 1, 6); // 根据文字宽度计算半径 ctx.beginPath(); ctx.arc(textX, textY, circleRadius, 0, Math.PI * 2); ctx.fillStyle = 'red'; ctx.fill(); // 绘制文字背景(可选) // ctx.fillStyle = 'red'; // ctx.fillRect(0, 0, 28, 15); // 绘制文字 ctx.font = `bold 10px Arial`; ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.fillStyle = '#ffffff'; ctx.fillText(text, 25, 10); resolve(canvas.toDataURL()); }; img.src = originalIconUrl; }); } let arr = []; for (let i = 0; i < result.length; i++) { let { type, lon, lat, longitude, latitude, lng, centerResource } = result[i]; if (longitude) { lon = longitude; } if (latitude) { lat = latitude; } if (lng) { lon = lng; } if (!lon || !lat || !type) { continue; } // 创建一个 Icon const startIcon: any = typeToIcon(type); const newIconUrl = await createTextOverlayIcon(startIcon, i + 1 + ''); // 将 icon 传入 marker const startMarker = new AMap.Marker({ // 点的坐标 position: [lon, lat], // 指定点的icon,也可以是个url ,但这样就不能对图片进行设置了 icon: newIconUrl, //启用点击事件 如果需要点击事件,必须开启 clickable: true, //点额外携带的数据,用户自定义属性,支持JavaScript API任意数据类型 offset: [-18, -32], maxZoom: 14, extData: result[i], label: { content: isFunction(extData?.content) && extData?.content(result[i]), }, ...extData?.extOption, }); arr.push(startMarker); // 可以调用setExtData()设置自定义属性 //将组 挂载到地图上 this.overlayGroup.setMap(this.map); //将需要批量控制的点放到一个组中,通过控制组,就可以操作组内所有所有覆盖物 this.overlayGroup.addOverlay(startMarker); } this.overlayGroup.on('click', function (e) { //拿到点中携带的数据 if (!isFunction(callback)) return; callback(e.target.getExtData()); }); return arr; }, clearOverlay() { if (this.overlayGroup) { this.map.remove(this.overlayGroup); this.overlayGroup.setMap(null); this.overlayGroup = null; } }, /** * @desc 创建直升机marker * @param result */ createAircraft(result: ResultType[]) { this.removeOverlayGroup(); this.clearActiveHospital(); if (!Array.isArray(result) && result.length == 0) return; this.createAirRange(result); let that = this; let arr = result.map((item) => ({ weight: 4, lnglat: [item.lon, item.lat], params: item.name })); let count = 4; this.cluster = new AMap.MarkerClusterer( that.map, // 地图实例 arr, { renderMarker: (context) => { let factor = Math.pow(context.count / count, 1 / 18); let div = document.createElement('div'); div.className = 'render-air'; let Hue = 180 - factor * 180; let size = Math.round(30 + Math.pow(context.count / count, 1 / 5) * 20); div.style.width = div.style.height = size + 'px'; div.innerHTML = aircraftContent() || context.count; div.style.color = '#fff'; div.style.fontSize = '14px'; context.marker.setOffset(new AMap.Pixel(-size / 2, -size / 2)); context.marker.setContent(div); // this.airMarkerList.push(context.marker); // context.marker.on('click', () => { // console.log('data', context); // }); }, } ); // this.cluster.setMap(this.map); }, clearAircraft() { if (this.cluster) { this.cluster.setMap(null); // this.map.remove(this.cluster); // this.cluster = null; } } /** * @desc 飞机范围 * @param list */, createAirRange(list: ResultType[]) { this.removeOverlayGroup(); this.circleGroup = new AMap.OverlayGroup(); for (let i = 0; i < list.length; i++) { let circle = createCircle({ lon: list[i].lon, lat: list[i].lat }); this.circleGroup.setMap(this.map); this.circleGroup.addOverlay(circle); } }, clearAirRange() { if (this.circleGroup) { this.map.remove(this.circleGroup); this.circleGroup = null; } } /** * @desc 地方医院医疗点显示 * @param cityCode 城市code * @param keywords 搜索名称 */, createActiveHospital(cityCode: string, keywords = '医院') { this.removeOverlayGroup(); this.clearActiveHospital(); if (!cityCode) return; if (!this.placeSearch) { this.placeSearch = new AMap.PlaceSearch({ pageSize: 30, // 单页显示结果条数 pageIndex: 1, // 页码 city: cityCode, // 兴趣点城市 citylimit: true, //是否强制限制在设置的城市内搜索 map: this.map, // 展现结果的地图实例 // panel: 'map_hospital', // 结果列表将在此容器中进行展示。 autoFitView: false, // 是否自动调整地图视野使绘制的 Marker点都处于视口的可见范围 }); } this.placeSearch.setCity(cityCode); //关键字查询 this.placeSearch.search(keywords); }, clearActiveHospital() { if (this.placeSearch) { this.placeSearch.clear(); } }, createMarker(option: MarkerOption, callbck?: Callback) { this.clearMarker(); let { lon, lat, zoom = 12 } = option; const icon = createIcon({ icon: alarmIcon, iconSize: [36, 42], imageSize: [36, 42] }); let str = option?.realName && option?.eventType_dictText ? `${option?.realName},${option?.eventType_dictText}` : `${option?.realName}`; this.marker = new AMap.Marker({ position: [lon, lat], icon: icon, offset: new AMap.Pixel(0, 0), //设置偏移量 label: { content: `
${str}
`, offset: new AMap.Pixel(0, -9), direction: 'top', }, }); this.map.add(this.marker); this.map.setZoomAndCenter(zoom, [lon, lat]); this.marker.on('click', callbck); }, clearMarker() { if (this.marker) { this.marker?.setMap(null); this.marker = null; } }, /** * @desc 创建地图详情弹窗 * * */ createInfoModal({ option, content }) { let { latitude, longitude } = option; this.infoWindow = new AMap.InfoWindow({ position: [longitude, latitude], offset: new AMap.Pixel(-8, -25), content: content, closeWhenClickMap: true, }); this.infoWindow.open(this.map); }, clearInfoModal() { if (this.infoWindow) { this.infoWindow?.setMap(null); this.infoWindow = null; } }, /** * @desc 计算两个坐标点的距离 * @param {Array} listA 坐标点数组 * @param {Array} listB 坐标点数组 * @return {Number} 距离 */ getDistance(listA: number[], listB: number[]) { let lnglatA = new AMap.LngLat(listA[0], listA[1]); let lnglatB = new AMap.LngLat(listB[0], listB[1]); return lnglatA.distance(lnglatB); }, /** * @desc 计算两个坐标点的驾车时间 * @param {Array} listA 坐标点数组 * @param {Array} listB 坐标点数组 * @param {Function} callback 回调 * @return {Number} 时间 */ getDriveTime(listA: number[], listB: number[], callback?: Callback) { let lnglatA = new AMap.LngLat(listA[0], listA[1]); let lnglatB = new AMap.LngLat(listB[0], listB[1]); const drivingOptions = { policy: AMap.DrivingPolicy.LEAST_TIME, }; let driving = new AMap.Driving(drivingOptions); return new Promise((resolve, reject) => { driving.search(lnglatA, lnglatB, (status, result) => { if (status === 'complete') { // console.log('result', result); if (callback && isFunction(callback)) { callback(result); } return resolve(result.routes[0]); } }); }); }, /** * @desc 计算两个坐标点的步行时间 * @param {Array} listA 坐标点数组 * @param {Array} listB 坐标点数组 * @param {Function} callback 回调 * @return {Number} 时间 */ getWalkingTime(listA: number[], listB: number[], callback?: Callback) { let lnglatA = new AMap.LngLat(listA[0], listA[1]); let lnglatB = new AMap.LngLat(listB[0], listB[1]); const drivingOptions = { policy: AMap.DrivingPolicy.LEAST_TIME, }; let walking = new AMap.Walking(drivingOptions); return new Promise((resolve, reject) => { walking.search(lnglatA, lnglatB, (status, result) => { if (status === 'complete') { // console.log('result', result); if (callback && isFunction(callback)) { callback(result); } return resolve(result.routes[0]); } }); }); }, /** * @desc 创建导航轨迹线路 * @param {Array} listA 坐标点数组 * @param {Array} listB 坐标点数组 * */ createNaviLine(listA: number[], listB: number[]) { this.clearNaviLine(); let lnglatA = new AMap.LngLat(listA[0], listA[1]); let lnglatB = new AMap.LngLat(listB[0], listB[1]); const drivingOptions = { policy: AMap.DrivingPolicy.LEAST_TIME, map: this.map, autoFitView: true, // panel: 'driving-panel', }; this.driveLine = new AMap.Driving(drivingOptions); this.driveLine.search(lnglatA, lnglatB, (status, result) => { if (status === 'complete') { // console.log('result', result); } }); this.driveLine.on('error', function (result) { let str = '定位失败:'; switch (result.info) { case 'PERMISSION_DENIED': str += '浏览器阻止了定位操作'; break; case 'POSITION_UNAVAILBLE': str += '无法获得当前位置'; break; case 'TIMEOUT': str += '定位超时'; break; default: str += '未知错误'; break; } return false; }); }, /** * @desc 清除导航轨迹线路 */ clearNaviLine() { if (this.driveLine) { this.driveLine.clear(); } }, /** * @desc 创建应急人员Marker */ createEmergencyMarker(option) { this.clearEmergencyMarker(); const { sex, address, name, position } = option; const icon = sex == '1' ? female : male; this.emergencyMarker = new AMap.Marker({ position: position, icon: createIcon({ icon: watchIconR, iconSize: [80, 80], imageSize: [80, 80] }), offset: new AMap.Pixel(-40, -40), //设置偏移量 label: { content: `
${name}
${address}
`, offset: new AMap.Pixel(-120, -70), }, }); this.map.add(this.emergencyMarker); // this.setZoomCenter(position); }, /** * @desc 清除应急人员Marker.. * */ clearEmergencyMarker() { if (this.emergencyMarker) { this.emergencyMarker?.setMap(null); this.emergencyMarker = null; } }, /** * @desc 根据坐标获取地址信息 * @param {Array} list 坐标点数组 */ getAddressByPoint(list: number[]) { const geocoder = new AMap.Geocoder({ city: '全国', radius: 1000, }); let lnglat = new AMap.LngLat(list[0], list[1]); return new Promise((resolve, reject) => { geocoder.getAddress(lnglat, (status, result) => { if (status === 'complete' && result.regeocode) { return resolve(result.regeocode.formattedAddress, result.regeocode); } return reject(result); }); }); }, /** * @desc 清除导航元素 */ clearNavigationElements() { this.clearNaviLine(); this.clearEmergencyMarker(); }, createSMarker(position: any) { if (this.startMarker) { this.startMarker?.setMap(null); this.startMarker = null; } this.startMarker = new AMap.Marker({ position: position, offset: new AMap.Pixel(0, 0), }); this.map.add(this.startMarker); this.setZoomCenter(position); }, createEMarker(position: any) { if (this.endMarker) { this.endMarker?.setMap(null); this.endMarker = null; } this.endMarker = new AMap.Marker({ position: position, offset: new AMap.Pixel(0, 0), }); this.map.add(this.endMarker); this.setZoomCenter(position); }, clearSOrE() { if (this.startMarker) { this.startMarker?.setMap(null); this.startMarker = null; } if (this.endMarker) { this.endMarker?.setMap(null); this.endMarker = null; } }, };