数据分析

This commit is contained in:
冯少康 2026-08-03 18:13:20 +08:00
parent 10a7cca13e
commit 3e32e1a383

View File

@ -1,11 +1,13 @@
import type Map from 'ol/Map'; import type Map from "ol/Map";
import Feature from 'ol/Feature'; import Feature from "ol/Feature";
import Point from 'ol/geom/Point'; import Point from "ol/geom/Point";
import VectorLayer from 'ol/layer/Vector'; import VectorLayer from "ol/layer/Vector";
import { fromLonLat } from 'ol/proj'; import { fromLonLat } from "ol/proj";
import VectorSource from 'ol/source/Vector'; import VectorSource from "ol/source/Vector";
import { Circle, Fill, Stroke, Style, Text } from 'ol/style'; import { Circle, Fill, Stroke, Style, Text, Icon } from "ol/style";
import { obtainViewer2D } from '@/gis/ol/map'; import { obtainViewer2D } from "@/gis/ol/map";
import { Point as OlPoint } from "ol/geom";
// 级别类型:可选属性,不存在则完全跳过 // 级别类型:可选属性,不存在则完全跳过
type PointLevel = { type PointLevel = {
@ -29,15 +31,24 @@ interface PointData {
const CONFIG = { const CONFIG = {
// 级别颜色(严格按存在的级别绘制) // 级别颜色(严格按存在的级别绘制)
ringColors: { ringColors: {
level5: '#ff0000', // 最内层:红色(如存在) level5: "#ff0000", // 最内层:红色(如存在)
level4: '#ff9933', // 内层:黄色(如存在) level4: "#ff9933", // 内层:黄色(如存在)
level3: '#ffff00', // 中层:绿色(如存在) level3: "#ffff00", // 中层:绿色(如存在)
level2: '#008000', // 外层:蓝色(如存在则绘制,否则跳过) level2: "#008000", // 外层:蓝色(如存在则绘制,否则跳过)
level1: '#1569c7' // 最外层:紫色(如存在) level1: "#1569c7", // 最外层:紫色(如存在)
}, },
kmToPixel: 5, // 1公里=5像素根据示例图比例调整 kmToPixel: 5, // 1公里=5像素根据示例图比例调整
}; };
const levelColorMap: Record<string, string> = {
level5: "#ff0000",
level4: "#ff9933",
level3: "#ffff00",
level2: "#008000",
level1: "#1569c7",
level0: "#999999", // 兜底level0灰色
};
export class SjfxPointHandle { export class SjfxPointHandle {
private map: Map; private map: Map;
private baseLayer: VectorLayer; // 基础点+标签(最上层) private baseLayer: VectorLayer; // 基础点+标签(最上层)
@ -46,7 +57,7 @@ export class SjfxPointHandle {
private ringSource: VectorSource; private ringSource: VectorSource;
constructor() { constructor() {
this.map = obtainViewer2D('sjfxOlContainer'); this.map = obtainViewer2D("sjfxOlContainer");
// 初始化数据源 // 初始化数据源
this.baseSource = new VectorSource(); this.baseSource = new VectorSource();
this.ringSource = new VectorSource(); this.ringSource = new VectorSource();
@ -65,13 +76,13 @@ export class SjfxPointHandle {
// 基础点样式(白色+黑色边框,避免被级别圆覆盖后看不见) // 基础点样式(白色+黑色边框,避免被级别圆覆盖后看不见)
image: new Circle({ image: new Circle({
radius: 3, radius: 3,
fill: new Fill({ color: '#ffffff' }), fill: new Fill({ color: "#ffffff" }),
}), }),
// 标签样式(右侧显示) // 标签样式(右侧显示)
text: new Text({ text: new Text({
text: label, text: label,
fill: new Fill({ color: '#ffffff' }), fill: new Fill({ color: "#ffffff" }),
font: '12px sans-serif', font: "12px sans-serif",
offsetX: 35, // 标签在点右侧35px offsetX: 35, // 标签在点右侧35px
offsetY: 0, offsetY: 0,
}), }),
@ -81,64 +92,169 @@ export class SjfxPointHandle {
/** /**
* *
*/ */
private createRingStyles(level: PointLevel): Style[] { private createRingStyles(level: PointLevel): Style[] {
const ringStyles: Style[] = []; const ringStyles: Style[] = [];
// 1. 优先级改为「从外到内」level1最外层 → level5最内层 // 1. 优先级改为「从外到内」level1最外层 → level5最内层
const levelOrder = ['level1', 'level2', 'level3', 'level4', 'level5'] as const; const levelOrder = [
"level1",
"level2",
"level3",
"level4",
"level5",
] as const;
// 2. 筛选存在的级别(保留有效级别) // 2. 筛选存在的级别(保留有效级别)
const existingLevels = levelOrder.filter(levelKey => const existingLevels = levelOrder.filter(
level[levelKey] !== undefined && level[levelKey]! > 0 (levelKey) => level[levelKey] !== undefined && level[levelKey]! > 0,
); );
// 3. 计算总半径(最外层圆环的半径) // 3. 计算总半径(最外层圆环的半径)
const totalKm = existingLevels.reduce((sum, key) => sum + level[key]!, 0); const totalKm = existingLevels.reduce((sum, key) => sum + level[key]!, 0);
const totalPixel = totalKm * CONFIG.kmToPixel; const totalPixel = totalKm * CONFIG.kmToPixel;
// 4. 从外到内绘制(外层先画,内层覆盖中间) // 4. 从外到内绘制(外层先画,内层覆盖中间)
let currentInnerRadius = 0; // 当前圆环的内半径 let currentInnerRadius = 0; // 当前圆环的内半径
existingLevels.forEach(levelKey => { existingLevels.forEach((levelKey) => {
const ringWidthKm = level[levelKey]!; const ringWidthKm = level[levelKey]!;
const ringWidthPixel = ringWidthKm * CONFIG.kmToPixel; const ringWidthPixel = ringWidthKm * CONFIG.kmToPixel;
const outerRadius = totalPixel - currentInnerRadius; // 外层圆环的外半径 const outerRadius = totalPixel - currentInnerRadius; // 外层圆环的外半径
currentInnerRadius += ringWidthPixel; currentInnerRadius += ringWidthPixel;
// 实心圆环样式(外层先画,内层覆盖中间) // 实心圆环样式(外层先画,内层覆盖中间)
ringStyles.push(new Style({ ringStyles.push(
image: new Circle({ new Style({
radius: outerRadius, image: new Circle({
fill: new Fill({ color: CONFIG.ringColors[levelKey] }), // 实心填充 radius: outerRadius,
}) fill: new Fill({ color: CONFIG.ringColors[levelKey] }), // 实心填充
})); }),
}); }),
);
});
return ringStyles; 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<string, number>,
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 { addPoints(points: PointData[]): void {
this.clearPoints(); // 清空已有数据 this.clearPoints(); // 清空已有数据
points.forEach(point => { points.forEach((point) => {
// 1. 添加基础点和标签 // 生成饼图canvas
const baseFeature = new Feature({ const pieCanvas = this.createPieCanvas(point.level, 78);
geometry: new Point(fromLonLat([point.longitude, point.latitude])),
id: `${point.id}_base`
});
baseFeature.setStyle(this.createBaseStyle(point.label));
this.baseSource.addFeature(baseFeature);
// 2. 添加存在的级别圆环(完全跳过不存在的) const baseFeature = new Feature({
const ringStyles = this.createRingStyles(point.level); geometry: new OlPoint(
if (ringStyles.length > 0) { fromLonLat([Number(point.longitude), Number(point.latitude)]),
const ringFeature = new Feature({ ),
geometry: new Point(fromLonLat([point.longitude, point.latitude])), id: `${point.id}_pie`,
id: `${point.id}_rings` });
});
ringFeature.setStyle(ringStyles); // 应用存在的级别样式 // 饼图样式 + 下方文字label
this.ringSource.addFeature(ringFeature); 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(); this.ringSource.clear();
} }
} }