From 23154b2b5fef5e30a6cf7f35299a193d18a7521a Mon Sep 17 00:00:00 2001 From: JIANG Date: Thu, 20 Nov 2025 18:34:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/OlMap/Controls/StyleEditorPanel.tsx | 156 ++++++++++++++++++-- src/app/OlMap/MapComponent.tsx | 9 +- src/utils/layers.ts | 18 ++- 3 files changed, 167 insertions(+), 16 deletions(-) diff --git a/src/app/OlMap/Controls/StyleEditorPanel.tsx b/src/app/OlMap/Controls/StyleEditorPanel.tsx index 4fdf6c1..14120a9 100644 --- a/src/app/OlMap/Controls/StyleEditorPanel.tsx +++ b/src/app/OlMap/Controls/StyleEditorPanel.tsx @@ -49,6 +49,7 @@ interface StyleConfig { opacity: number; adjustWidthByProperty: boolean; // 是否根据属性调整线条宽度 customBreaks?: number[]; // 自定义断点(用于 custom_breaks) + customColors?: string[]; // 自定义颜色(用于 colorType="custom") } // 图层样式状态接口 @@ -149,6 +150,29 @@ const CLASSIFICATION_METHODS = [ { name: "自定义", value: "custom_breaks" }, ]; +const rgbaToHex = (rgba: string) => { + try { + const c = parseColor(rgba); + const toHex = (n: number) => { + const hex = Math.round(n).toString(16); + return hex.length === 1 ? "0" + hex : hex; + }; + return `#${toHex(c.r)}${toHex(c.g)}${toHex(c.b)}`; + } catch (e) { + return "#000000"; + } +}; + +const hexToRgba = (hex: string) => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return result + ? `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt( + result[3], + 16 + )}, 1)` + : "rgba(0, 0, 0, 1)"; +}; + const StyleEditorPanel: React.FC = ({ layerStyleStates, setLayerStyleStates, @@ -199,6 +223,7 @@ const StyleEditorPanel: React.FC = ({ opacity: 0.9, adjustWidthByProperty: true, customBreaks: [], + customColors: [], }); // 根据分段数生成相应数量的渐进颜色 @@ -467,7 +492,18 @@ const StyleEditorPanel: React.FC = ({ }) : styleConfig.colorType === "gradient" ? generateGradientColors(breaksLength) - : generateRainbowColors(breaksLength); + : styleConfig.colorType === "rainbow" + ? generateRainbowColors(breaksLength) + : (() => { + // 自定义颜色 + const custom = styleConfig.customColors || []; + // 如果自定义颜色数量不足,用默认颜色补齐(这里简单用红色,或者重复最后一个) + const result = [...custom]; + while (result.length < breaksLength) { + result.push(result[result.length - 1] || "rgba(255, 0, 0, 1)"); + } + return result.slice(0, breaksLength); + })(); // 计算每个分段的线条粗细和点大小 const dimensions: number[] = layerType === "linestring" @@ -889,6 +925,22 @@ const StyleEditorPanel: React.FC = ({ currentPipeCalData, ]); + // 初始化或调整自定义颜色数组长度 + useEffect(() => { + const numColors = styleConfig.segments; + setStyleConfig((prev) => { + const prevColors = prev.customColors || []; + if (prevColors.length === numColors) return prev; + + const newColors = [...prevColors]; + const baseColors = RAINBOW_PALETTES[0].colors; + while (newColors.length < numColors) { + newColors.push(baseColors[newColors.length % baseColors.length]); + } + return { ...prev, customColors: newColors.slice(0, numColors) }; + }); + }, [styleConfig.segments]); + const getColorSetting = () => { if (styleConfig.colorType === "single") { return ( @@ -1069,6 +1121,54 @@ const StyleEditorPanel: React.FC = ({ ); } + if (styleConfig.colorType === "custom") { + return ( + + + 自定义颜色 + + + {Array.from({ length: styleConfig.segments }).map((_, idx) => { + const color = + (styleConfig.customColors && styleConfig.customColors[idx]) || + "rgba(0,0,0,1)"; + return ( + + + 分段{idx + 1} + + { + const hex = e.target.value; + const newColor = hexToRgba(hex); + setStyleConfig((prev) => { + const newColors = [...(prev.customColors || [])]; + while (newColors.length < styleConfig.segments) + newColors.push("rgba(0,0,0,1)"); + newColors[idx] = newColor; + return { ...prev, customColors: newColors }; + }); + }} + style={{ + width: "100%", + height: "32px", + cursor: "pointer", + border: "1px solid #ccc", + borderRadius: "4px", + }} + /> + + ); + })} + + + ); + } }; // 根据不同图层的类型和颜色分类方案显示不同的大小设置 const getSizeSetting = () => { @@ -1084,6 +1184,12 @@ const StyleEditorPanel: React.FC = ({ const rainbowColors = RAINBOW_PALETTES[styleConfig.rainbowPaletteIndex].colors; colors = [rainbowColors[0], rainbowColors[rainbowColors.length - 1]]; + } else if (styleConfig.colorType === "custom") { + const customColors = styleConfig.customColors || []; + colors = [ + customColors[0] || "rgba(0,0,0,1)", + customColors[customColors.length - 1] || "rgba(0,0,0,1)", + ]; } if (selectedRenderLayer?.get("type") === "point") { @@ -1362,7 +1468,21 @@ const StyleEditorPanel: React.FC = ({ - setStyleConfig((prev) => ({ ...prev, segments: value as number })) + setStyleConfig((prev) => { + const newSegments = value as number; + const newCustomColors = [...(prev.customColors || [])]; + if (newSegments > newCustomColors.length) { + const baseColors = RAINBOW_PALETTES[0].colors; + for (let i = newCustomColors.length; i < newSegments; i++) { + newCustomColors.push(baseColors[i % baseColors.length]); + } + } + return { + ...prev, + segments: newSegments, + customColors: newCustomColors, + }; + }) } min={2} max={10} @@ -1379,7 +1499,7 @@ const StyleEditorPanel: React.FC = ({ {Array.from({ length: styleConfig.segments }).map((_, idx) => ( = ({ ? [...prev.customBreaks] : []; // 保证长度 - while (prevBreaks.length < styleConfig.segments + 1) + while (prevBreaks.length < styleConfig.segments) prevBreaks.push(0); prevBreaks[idx] = isNaN(v) ? 0 : Math.max(0, v); return { ...prev, customBreaks: prevBreaks }; @@ -1430,16 +1550,32 @@ const StyleEditorPanel: React.FC = ({ {getColorSetting()} diff --git a/src/app/OlMap/MapComponent.tsx b/src/app/OlMap/MapComponent.tsx index 4b2fb4b..defed16 100644 --- a/src/app/OlMap/MapComponent.tsx +++ b/src/app/OlMap/MapComponent.tsx @@ -666,10 +666,11 @@ const MapComponent: React.FC = ({ children }) => { layers: [], }); deckFlowRef.current = deckFlow; - const deckFlowLayer = new DeckLayer(deckFlow); - deckFlowLayer.set("name", "水流动画"); - deckFlowLayer.set("value", "waterflow"); - deckFlowLayer.set("type", "animation"); + const deckFlowLayer = new DeckLayer(deckFlow, { + name: "水流动画", + value: "waterflow", + type: "animation", + }); // 初始化用户可见性状态(默认为 true) deckFlowLayer.initUserVisibility("waterflowLayer", true); // 设置可见性变化回调,同步更新 waterflowUserVisible diff --git a/src/utils/layers.ts b/src/utils/layers.ts index a3a7d3f..23dec82 100644 --- a/src/utils/layers.ts +++ b/src/utils/layers.ts @@ -11,9 +11,18 @@ export class DeckLayer extends Layer { private onVisibilityChange?: (layerId: string, visible: boolean) => void; private userVisibility: Map = new Map(); // 存储用户设置的可见性 - constructor(deckInstance: Deck) { - super({}); + /** + * @param deckInstance deck.gl 实例 + * @param layerProperties 可选:在构造时直接设置到 OpenLayers Layer 的 properties + */ + constructor(deckInstance: Deck, layerProperties?: Record) { + // 将 layerProperties 作为 Layer 的 properties 传入 + super({ properties: layerProperties || {} }); this.deck = deckInstance; + // 再次确保属性应用到实例(兼容场景) + if (layerProperties) { + this.setProperties(layerProperties); + } } // 设置可见性变化回调 @@ -118,4 +127,9 @@ export class DeckLayer extends Layer { this.setDeckLayerVisible(layerId, !currentVisible); } } + + // 可选的封装:在外部用更语义化的方式设置属性(内部可直接调用 setProperties) + setLayerProperties(props: Record): void { + this.setProperties(props); + } }