diff --git a/src/gis/ol/olSJFX.ts b/src/gis/ol/olSJFX.ts index 91cfff6c..39a35a48 100644 --- a/src/gis/ol/olSJFX.ts +++ b/src/gis/ol/olSJFX.ts @@ -1,11 +1,13 @@ -import type Map from 'ol/Map'; -import Feature from 'ol/Feature'; -import Point from 'ol/geom/Point'; -import VectorLayer from 'ol/layer/Vector'; -import { fromLonLat } from 'ol/proj'; -import VectorSource from 'ol/source/Vector'; -import { Circle, Fill, Stroke, Style, Text } from 'ol/style'; -import { obtainViewer2D } from '@/gis/ol/map'; +import type Map from "ol/Map"; +import Feature from "ol/Feature"; +import Point from "ol/geom/Point"; +import VectorLayer from "ol/layer/Vector"; +import { fromLonLat } from "ol/proj"; +import VectorSource from "ol/source/Vector"; +import { Circle, Fill, Stroke, Style, Text, Icon } from "ol/style"; +import { obtainViewer2D } from "@/gis/ol/map"; + +import { Point as OlPoint } from "ol/geom"; // 级别类型:可选属性,不存在则完全跳过 type PointLevel = { @@ -29,15 +31,24 @@ interface PointData { const CONFIG = { // 级别颜色(严格按存在的级别绘制) ringColors: { - level5: '#ff0000', // 最内层:红色(如存在) - level4: '#ff9933', // 内层:黄色(如存在) - level3: '#ffff00', // 中层:绿色(如存在) - level2: '#008000', // 外层:蓝色(如存在则绘制,否则跳过) - level1: '#1569c7' // 最外层:紫色(如存在) + level5: "#ff0000", // 最内层:红色(如存在) + level4: "#ff9933", // 内层:黄色(如存在) + level3: "#ffff00", // 中层:绿色(如存在) + level2: "#008000", // 外层:蓝色(如存在则绘制,否则跳过) + level1: "#1569c7", // 最外层:紫色(如存在) }, kmToPixel: 5, // 1公里=5像素(根据示例图比例调整) }; +const levelColorMap: Record = { + level5: "#ff0000", + level4: "#ff9933", + level3: "#ffff00", + level2: "#008000", + level1: "#1569c7", + level0: "#999999", // 兜底level0灰色 +}; + export class SjfxPointHandle { private map: Map; private baseLayer: VectorLayer; // 基础点+标签(最上层) @@ -46,7 +57,7 @@ export class SjfxPointHandle { private ringSource: VectorSource; constructor() { - this.map = obtainViewer2D('sjfxOlContainer'); + this.map = obtainViewer2D("sjfxOlContainer"); // 初始化数据源 this.baseSource = new VectorSource(); this.ringSource = new VectorSource(); @@ -65,13 +76,13 @@ export class SjfxPointHandle { // 基础点样式(白色+黑色边框,避免被级别圆覆盖后看不见) image: new Circle({ radius: 3, - fill: new Fill({ color: '#ffffff' }), + fill: new Fill({ color: "#ffffff" }), }), // 标签样式(右侧显示) text: new Text({ text: label, - fill: new Fill({ color: '#ffffff' }), - font: '12px sans-serif', + fill: new Fill({ color: "#ffffff" }), + font: "12px sans-serif", offsetX: 35, // 标签在点右侧35px offsetY: 0, }), @@ -81,64 +92,169 @@ export class SjfxPointHandle { /** * 严格按存在的级别创建圆环样式(不存在的级别完全跳过) */ -private createRingStyles(level: PointLevel): Style[] { - const ringStyles: Style[] = []; - // 1. 优先级改为「从外到内」(level1最外层 → level5最内层) - const levelOrder = ['level1', 'level2', 'level3', 'level4', 'level5'] as const; - - // 2. 筛选存在的级别(保留有效级别) - const existingLevels = levelOrder.filter(levelKey => - level[levelKey] !== undefined && level[levelKey]! > 0 - ); + private createRingStyles(level: PointLevel): Style[] { + const ringStyles: Style[] = []; + // 1. 优先级改为「从外到内」(level1最外层 → level5最内层) + const levelOrder = [ + "level1", + "level2", + "level3", + "level4", + "level5", + ] as const; - // 3. 计算总半径(最外层圆环的半径) - const totalKm = existingLevels.reduce((sum, key) => sum + level[key]!, 0); - const totalPixel = totalKm * CONFIG.kmToPixel; + // 2. 筛选存在的级别(保留有效级别) + const existingLevels = levelOrder.filter( + (levelKey) => level[levelKey] !== undefined && level[levelKey]! > 0, + ); - // 4. 从外到内绘制(外层先画,内层覆盖中间) - let currentInnerRadius = 0; // 当前圆环的内半径 - existingLevels.forEach(levelKey => { - const ringWidthKm = level[levelKey]!; - const ringWidthPixel = ringWidthKm * CONFIG.kmToPixel; - const outerRadius = totalPixel - currentInnerRadius; // 外层圆环的外半径 - currentInnerRadius += ringWidthPixel; + // 3. 计算总半径(最外层圆环的半径) + const totalKm = existingLevels.reduce((sum, key) => sum + level[key]!, 0); + const totalPixel = totalKm * CONFIG.kmToPixel; - // 实心圆环样式(外层先画,内层覆盖中间) - ringStyles.push(new Style({ - image: new Circle({ - radius: outerRadius, - fill: new Fill({ color: CONFIG.ringColors[levelKey] }), // 实心填充 - }) - })); - }); + // 4. 从外到内绘制(外层先画,内层覆盖中间) + let currentInnerRadius = 0; // 当前圆环的内半径 + existingLevels.forEach((levelKey) => { + const ringWidthKm = level[levelKey]!; + const ringWidthPixel = ringWidthKm * CONFIG.kmToPixel; + const outerRadius = totalPixel - currentInnerRadius; // 外层圆环的外半径 + currentInnerRadius += ringWidthPixel; - return ringStyles; -} + // 实心圆环样式(外层先画,内层覆盖中间) + ringStyles.push( + new Style({ + image: new Circle({ + radius: outerRadius, + fill: new Fill({ color: CONFIG.ringColors[levelKey] }), // 实心填充 + }), + }), + ); + }); + + return ringStyles; + } /** * 添加点(严格绘制存在的级别,跳过不存在的) */ + // addPoints(points: PointData[]): void { + // this.clearPoints(); // 清空已有数据 + + // points.forEach(point => { + // // 1. 添加基础点和标签 + // const baseFeature = new Feature({ + // geometry: new Point(fromLonLat([point.longitude, point.latitude])), + // id: `${point.id}_base` + // }); + // baseFeature.setStyle(this.createBaseStyle(point.label)); + // this.baseSource.addFeature(baseFeature); + + // // 2. 添加存在的级别圆环(完全跳过不存在的) + // const ringStyles = this.createRingStyles(point.level); + // if (ringStyles.length > 0) { + // const ringFeature = new Feature({ + // geometry: new Point(fromLonLat([point.longitude, point.latitude])), + // id: `${point.id}_rings` + // }); + // ringFeature.setStyle(ringStyles); // 应用存在的级别样式 + // this.ringSource.addFeature(ringFeature); + // } + // }); + // } + + /** + * 根据统计数据生成饼图Canvas + * @param data 分级统计数组 + * @param size 饼图尺寸(像素) + * @returns canvas元素 + */ + private createPieCanvas( + levelObj: Record, + size = 38, + ): HTMLCanvasElement { + const canvas = document.createElement("canvas"); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext("2d"); + if (!ctx) return canvas; + + // 1. 将level对象转为数组 + const pieData = Object.entries(levelObj) + .map(([levelKey, count]) => ({ + levelKey, + count, + color: levelColorMap[levelKey] || "#888888", + })) + .filter((item) => item.count > 0); + + const total = pieData.reduce((sum, item) => sum + item.count, 0); + const center = size / 2; + const radius = size / 2 - 2; + let startAngle = -Math.PI / 2; // 起始角度:正上方 + + // 总数为0 绘制灰色圆圈兜底 + if (total <= 0) { + ctx.beginPath(); + ctx.arc(center, center, radius, 0, Math.PI * 2); + ctx.fillStyle = "#aaaaaa"; + ctx.fill(); + return canvas; + } + + // 绘制各个扇区 + pieData.forEach((item) => { + const percent = item.count / total; + const endAngle = startAngle + Math.PI * 2 * percent; + + ctx.beginPath(); + ctx.moveTo(center, center); + ctx.arc(center, center, radius, startAngle, endAngle); + ctx.closePath(); + ctx.fillStyle = item.color; + ctx.fill(); + + startAngle = endAngle; + }); + + // 可选:增加空心圆环(甜甜圈效果,取消注释启用) + // ctx.beginPath(); + // ctx.arc(center, center, radius * 0.4, 0, Math.PI * 2); + // ctx.fillStyle = "#ffffff"; + // ctx.fill(); + + return canvas; + } addPoints(points: PointData[]): void { this.clearPoints(); // 清空已有数据 - points.forEach(point => { - // 1. 添加基础点和标签 - const baseFeature = new Feature({ - geometry: new Point(fromLonLat([point.longitude, point.latitude])), - id: `${point.id}_base` - }); - baseFeature.setStyle(this.createBaseStyle(point.label)); - this.baseSource.addFeature(baseFeature); + points.forEach((point) => { + // 生成饼图canvas + const pieCanvas = this.createPieCanvas(point.level, 78); - // 2. 添加存在的级别圆环(完全跳过不存在的) - const ringStyles = this.createRingStyles(point.level); - if (ringStyles.length > 0) { - const ringFeature = new Feature({ - geometry: new Point(fromLonLat([point.longitude, point.latitude])), - id: `${point.id}_rings` - }); - ringFeature.setStyle(ringStyles); // 应用存在的级别样式 - this.ringSource.addFeature(ringFeature); - } + const baseFeature = new Feature({ + geometry: new OlPoint( + fromLonLat([Number(point.longitude), Number(point.latitude)]), + ), + id: `${point.id}_pie`, + }); + + // 饼图样式 + 下方文字label + const pieStyle = new Style({ + image: new Icon({ + img: pieCanvas, + imgSize: [pieCanvas.width, pieCanvas.height], + anchor: [0.5, 0.5], // 圆心对准经纬度坐标 + }), + text: new Text({ + text: point.label, + offsetY: 24, // 文字向下偏移 + font: "16px", + fill: new Fill({ color: "#000" }), + stroke: new Stroke({ color: "#fff", width: 1.8 }) + }), + }); + + baseFeature.setStyle(pieStyle); + this.baseSource.addFeature(baseFeature); }); } @@ -150,4 +266,3 @@ private createRingStyles(level: PointLevel): Style[] { this.ringSource.clear(); } } -