数据分析
This commit is contained in:
parent
10a7cca13e
commit
3e32e1a383
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user