完成时间轴前后端数据连通
This commit is contained in:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user