完成时间轴前后端数据连通

This commit is contained in:
JIANG
2025-10-10 15:12:23 +08:00
parent 5d54ad11d4
commit fa0970bd79
13 changed files with 416 additions and 285 deletions

View File

@@ -19,11 +19,14 @@ import {
// 导入OpenLayers样式相关模块
import WebGLVectorTileLayer from "ol/layer/WebGLVectorTile";
import { useMap } from "../MapComponent";
import { useData, useMap } from "../MapComponent";
import StyleLegend, { LegendStyleConfig } from "./StyleLegend";
import { FlatStyleLike } from "ol/style/flat";
import { calculateClassification } from "@utils/breaks_classification";
import { parseColor } from "@utils/parseColor";
interface StyleConfig {
property: string;
classificationMethod: string; // 分类方法
@@ -96,6 +99,22 @@ const CLASSIFICATION_METHODS = [
const StyleEditorPanel: React.FC = () => {
const map = useMap();
const data = useData();
if (!data) {
return <div>Loading...</div>; // 或其他占位符
}
const {
junctionData,
pipeData,
setShowJunctionText,
setShowPipeText,
setJunctionText,
setPipeText,
} = data;
const [applyJunctionStyle, setApplyJunctionStyle] = useState(false);
const [applyPipeStyle, setApplyPipeStyle] = useState(false);
const [renderLayers, setRenderLayers] = useState<WebGLVectorTileLayer[]>([]);
const [selectedRenderLayer, setSelectedRenderLayer] =
useState<WebGLVectorTileLayer>();
@@ -134,75 +153,10 @@ const StyleEditorPanel: React.FC = () => {
breaks: [],
}
);
// 样式状态管理 - 存储多个图层的样式状态
const [layerStyleStates, setLayerStyleStates] = useState<LayerStyleState[]>(
[]
);
// 通用颜色解析函数
const parseColor = useCallback((color: string) => {
// 解析 rgba 格式的颜色
const match = color.match(
/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/
);
if (match) {
return {
r: parseInt(match[1], 10),
g: parseInt(match[2], 10),
b: parseInt(match[3], 10),
// 如果没有 alpha 值,默认为 1
a: match[4] ? parseFloat(match[4]) : 1,
};
}
// 如果还是十六进制格式,保持原来的解析方式
const hex = color.replace("#", "");
return {
r: parseInt(hex.slice(0, 2), 16),
g: parseInt(hex.slice(2, 4), 16),
b: parseInt(hex.slice(4, 6), 16),
};
}, []);
// 获取数据分段分类结果
const fetchClassification = async (
layer_name: string,
prop: string,
n_classes: number,
algorithm: string
) => {
if (!algorithm) {
algorithm = "pretty_breaks"; // 默认算法
}
const response = await fetch("http://localhost:8000/jenks-classification", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
layer_name: layer_name, // 图层名称
prop: prop, // 属性名称
n_classes: n_classes, // 分段数
algorithm: algorithm,
// algorithm: "pretty_breaks",
// algorithm: "hybrid_jenks"
}),
});
if (!response.ok) {
console.error("API 请求失败:", response.status, response.statusText);
return false;
}
const data = await response.json();
const breaks = data.breaks; // 从响应对象中提取 breaks 数组
// console.log(breaks);
// 验证返回的数据
if (!Array.isArray(breaks) || breaks.length === 0) {
console.error("API 返回的 breaks 不是有效数组:", breaks);
return false;
}
return breaks;
};
// 颜色方案选择
const [singlePaletteIndex, setSinglePaletteIndex] = useState(0);
const [gradientPaletteIndex, setGradientPaletteIndex] = useState(0);
@@ -232,21 +186,45 @@ const StyleEditorPanel: React.FC = () => {
[gradientPaletteIndex, parseColor]
);
// 应用分类样式
const applyStyle = (breaks?: number[]) => {
const setStyleState = (layer: any) => {
if (
layer.get("value") !== undefined &&
styleConfig.property !== undefined
) {
// 更新文字标签设置
if (layer.get("value") === "junctions") {
if (setJunctionText && setShowJunctionText) {
setJunctionText(styleConfig.property);
setShowJunctionText(styleConfig.showLabels);
setApplyJunctionStyle(true);
}
}
if (layer.get("value") === "pipes") {
if (setPipeText && setShowPipeText) {
setPipeText(styleConfig.property);
setShowPipeText(styleConfig.showLabels);
setApplyPipeStyle(true);
}
}
}
};
const applyStyle = (layerId: string, breaks?: number[]) => {
// 使用传入的 breaks 数据
if (!breaks) {
if (!breaks || breaks.length === 0) {
console.warn("没有有效的 breaks 数据");
return;
}
if (!selectedRenderLayer || !styleConfig.property) return;
const styleConfig = layerStyleStates.find(
(s) => s.layerId === layerId
)?.styleConfig;
const selectedRenderLayer = renderLayers.find(
(l) => l.get("id") === layerId
);
if (!selectedRenderLayer || !styleConfig?.property) return;
const layerType: string = selectedRenderLayer?.get("type");
const source = selectedRenderLayer.getSource();
if (!source) return;
if (breaks.length === 0) {
console.log("没有有效的 breaks 数据,无法应用样式");
return;
}
const breaksLength = breaks.length;
// 根据 breaks 计算每个分段的颜色,线条粗细
const colors: string[] =
@@ -385,8 +363,45 @@ const StyleEditorPanel: React.FC = () => {
setLayerStyleStates((prev) =>
prev.filter((state) => state.layerId !== layerId)
);
// 重置样式应用状态
if (layerId === "junctions") {
setApplyJunctionStyle(false);
} else if (layerId === "pipes") {
setApplyPipeStyle(false);
}
}
}, [selectedRenderLayer]);
useEffect(() => {
if (applyJunctionStyle && junctionData.length > 0) {
// 应用节点样式
const junctionStyleConfigState = layerStyleStates.find(
(s) => s.layerId === "junctions"
);
if (!junctionStyleConfigState) return;
const segments = junctionStyleConfigState?.styleConfig.segments;
const breaks = calculateClassification(
junctionData,
segments,
styleConfig.classificationMethod
);
applyStyle(junctionStyleConfigState.layerId, breaks);
}
if (applyPipeStyle && pipeData.length > 0) {
// 应用管道样式
const pipeStyleConfigState = layerStyleStates.find(
(s) => s.layerId === "pipes"
);
if (!pipeStyleConfigState) return;
const segments = pipeStyleConfigState?.styleConfig.segments;
const breaks = calculateClassification(
pipeData,
segments,
styleConfig.classificationMethod
);
applyStyle(pipeStyleConfigState.layerId, breaks);
}
}, [junctionData, pipeData, applyJunctionStyle, applyPipeStyle]);
// 样式状态管理功能
// 保存当前图层的样式状态
const saveLayerStyle = useCallback(
@@ -436,8 +451,9 @@ const StyleEditorPanel: React.FC = () => {
const updateVisibleLayers = () => {
const layers = map.getAllLayers();
// 筛选矢量瓦片图层
const webGLVectorTileLayers = layers.filter((layer) =>
layer.get("value")
const webGLVectorTileLayers = layers.filter(
(layer) =>
layer.get("value") === "junctions" || layer.get("value") === "pipes" // 暂时只处理这两个图层
) as WebGLVectorTileLayer[];
setRenderLayers(webGLVectorTileLayers);
@@ -505,6 +521,7 @@ const StyleEditorPanel: React.FC = () => {
}));
}
}, [styleConfig.colorType]);
// 获取所有激活的图例配置
const getActiveLegendConfigs = useCallback(() => {
return layerStyleStates
@@ -803,7 +820,7 @@ const StyleEditorPanel: React.FC = () => {
return (
<>
<div className="absolute top-20 left-4 bg-white p-4 rounded-xl shadow-lg opacity-95 hover:opacity-100 transition-opacity w-80">
<div className="absolute top-20 left-4 bg-white p-4 rounded-xl shadow-lg opacity-95 hover:opacity-100 transition-opacity w-80 z-10">
{/* 图层选择 */}
<FormControl variant="standard" fullWidth margin="dense">
<InputLabel></InputLabel>
@@ -948,25 +965,8 @@ const StyleEditorPanel: React.FC = () => {
<Button
variant="contained"
color="primary"
onClick={async () => {
// 获取分段数据后应用样式
if (
selectedRenderLayer &&
selectedRenderLayer.get("value") !== undefined &&
styleConfig.property !== undefined &&
styleConfig.segments !== undefined
) {
const newBreaks = await fetchClassification(
selectedRenderLayer?.get("value"),
styleConfig.property,
styleConfig.segments,
styleConfig.classificationMethod
);
if (newBreaks) {
applyStyle(newBreaks);
// setShowLegend(true); // 应用样式后显示图例
}
}
onClick={() => {
setStyleState(selectedRenderLayer);
}}
disabled={!selectedRenderLayer || !styleConfig.property}
startIcon={<ApplyIcon />}
@@ -989,7 +989,7 @@ const StyleEditorPanel: React.FC = () => {
</div>
{/* 显示多图层图例 */}
{getActiveLegendConfigs().length > 0 && (
<div className="fixed bottom-35 right-4 flex flex-row items-end max-w-screen-lg overflow-x-auto z-10">
<div className=" absolute bottom-40 right-4 shadow-lg flex flex-row items-end max-w-screen-lg overflow-x-auto z-10">
<div className="flex flex-row gap-3">
{getActiveLegendConfigs().map((config, index) => (
<StyleLegend key={`${config.layerId}-${index}`} {...config} />