Files
xj-screen/src/assets/mapUtils/map.ts
T
weixin c7abcf1f67 update
1.新需求2025.9.19新需求:
去掉"健康监测"副屏, 修改后健康终端报警点击查看详情弹出告警记录分页列表;
去掉【体检人数统计】【体检数据】【健康打卡数据】,增加【心血管监测】模块功能,【心血管监测】功能将原心血管监测大屏上的内容整理并移动到急救服务平台大屏中;
员工求助时,地图中推荐两个医院、两个健康小屋、两个AED坐标信息,并按距离排序,体现距离远近顺序;
地图中油田医院、健康小屋等各个图例,显示总数信息;
救护车坐标信息维护,并在大屏上标记;
右上角"增加【救助信息】板块, 当出现应急弹窗后或从今日应急选中应急事件 , 该板块需展示应急事件的相关信息(开发中);
2025-09-24 20:53:28 +08:00

658 lines
24 KiB
TypeScript

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: `<div class="alarm-content" >
<div>${str}</div>
</div>`,
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: `<div class="alarm-content" style="width: 180px;z-index: 99" >
<div style="text-align: center;padding-bottom: 6px">${name}</div>
<div style="text-wrap: wrap">${address}</div>
</div>`,
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;
}
},
};