feat(map): add coordinate zoom action

This commit is contained in:
2026-06-09 17:55:17 +08:00
parent 22afdbf2e8
commit 7d966a5e91
4 changed files with 128 additions and 0 deletions
+56
View File
@@ -118,6 +118,12 @@ const TOOL_META: Record<string, ToolMeta> = {
actionLabel: "定位到地图", actionLabel: "定位到地图",
color: "#3ba272", color: "#3ba272",
}, },
zoom_to_map: {
label: "缩放到坐标",
icon: <LocationOnRounded sx={{ fontSize: 18 }} />,
actionLabel: "缩放到地图",
color: "#0ea5e9",
},
view_history: { view_history: {
label: "查看计算结果", label: "查看计算结果",
icon: <TimelineRounded sx={{ fontSize: 18 }} />, icon: <TimelineRounded sx={{ fontSize: 18 }} />,
@@ -176,6 +182,46 @@ function normalizeLocateIds(params: Record<string, unknown>): string[] {
return []; return [];
} }
function readFiniteNumber(value: unknown): number | null {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
}
function buildZoomTo3857Action(
params: Record<string, unknown>,
): Extract<ChatToolAction, { type: "zoom_to_map" }> | null {
const rawCoordinate = params.coordinate ?? params.coordinates ?? params.center;
const tuple = Array.isArray(rawCoordinate)
? rawCoordinate
: [params.x ?? params.lon ?? params.longitude, params.y ?? params.lat ?? params.latitude];
const x = readFiniteNumber(tuple[0]);
const y = readFiniteNumber(tuple[1]);
if (x === null || y === null) {
return null;
}
const zoom = readFiniteNumber(params.zoom);
const durationMs = readFiniteNumber(params.duration_ms ?? params.durationMs);
const rawSourceCrs = params.source_crs ?? params.sourceCrs ?? params.crs;
const normalizedSourceCrs =
typeof rawSourceCrs === "string" ? rawSourceCrs.trim().toUpperCase() : "";
const sourceCrs =
normalizedSourceCrs === "EPSG:4326" ? "EPSG:4326" : "EPSG:3857";
return {
type: "zoom_to_map",
coordinate: [x, y],
sourceCrs,
zoom: zoom ?? undefined,
durationMs: durationMs ?? undefined,
};
}
function getToolDescription(toolCall: ToolCall): string { function getToolDescription(toolCall: ToolCall): string {
const { params } = toolCall; const { params } = toolCall;
const resolveScadaFeatureInfos = (): [string, string][] => { const resolveScadaFeatureInfos = (): [string, string][] => {
@@ -281,6 +327,14 @@ function getToolDescription(toolCall: ToolCall): string {
case "render_junctions": { case "render_junctions": {
return (params.render_ref as string | undefined) ?? "渲染引用"; return (params.render_ref as string | undefined) ?? "渲染引用";
} }
case "zoom_to_map": {
const action = buildZoomTo3857Action(params);
if (!action) {
return "地图坐标";
}
const zoom = action.zoom === undefined ? "" : ` · zoom ${action.zoom}`;
return `${action.coordinate[0]}, ${action.coordinate[1]} · ${action.sourceCrs}${zoom}`;
}
case APPLY_LAYER_STYLE_TOOL: { case APPLY_LAYER_STYLE_TOOL: {
const payload = parseApplyLayerStylePayload(params); const payload = parseApplyLayerStylePayload(params);
return payload ? describeApplyLayerStyle(payload) : "图层样式"; return payload ? describeApplyLayerStyle(payload) : "图层样式";
@@ -341,6 +395,8 @@ function buildAction(toolCall: ToolCall): ChatToolAction | null {
(params.end as string | undefined), (params.end as string | undefined),
}); });
switch (toolCall.tool) { switch (toolCall.tool) {
case "zoom_to_map":
return buildZoomTo3857Action(params);
case "locate_features": { case "locate_features": {
const featureTypeRaw = params.feature_type; const featureTypeRaw = params.feature_type;
const featureType = const featureType =
@@ -148,6 +148,46 @@ const compactNames = (names: string[]) => {
: names.join(", "); : names.join(", ");
}; };
const readFiniteNumber = (value: unknown): number | null => {
if (typeof value === "number" && Number.isFinite(value)) {
return value;
}
if (typeof value === "string" && value.trim()) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : null;
}
return null;
};
const parseZoomTo3857Action = (
params: Record<string, unknown>,
): Extract<ChatToolAction, { type: "zoom_to_map" }> | null => {
const rawCoordinate = params.coordinate ?? params.coordinates ?? params.center;
const tuple = Array.isArray(rawCoordinate)
? rawCoordinate
: [params.x ?? params.lon ?? params.longitude, params.y ?? params.lat ?? params.latitude];
const x = readFiniteNumber(tuple[0]);
const y = readFiniteNumber(tuple[1]);
if (x === null || y === null) {
return null;
}
const zoom = readFiniteNumber(params.zoom);
const durationMs = readFiniteNumber(params.duration_ms ?? params.durationMs);
const rawSourceCrs = params.source_crs ?? params.sourceCrs ?? params.crs;
const normalizedSourceCrs =
typeof rawSourceCrs === "string" ? rawSourceCrs.trim().toUpperCase() : "";
const sourceCrs =
normalizedSourceCrs === "EPSG:4326" ? "EPSG:4326" : "EPSG:3857";
return {
type: "zoom_to_map",
coordinate: [x, y],
sourceCrs,
zoom: zoom ?? undefined,
durationMs: durationMs ?? undefined,
};
};
const buildLocateArtifact = ( const buildLocateArtifact = (
tool: string, tool: string,
params: Record<string, unknown>, params: Record<string, unknown>,
@@ -190,6 +230,18 @@ const buildToolAction = (
}; };
} }
if (tool === "zoom_to_map") {
const action = parseZoomTo3857Action(params);
return {
action,
kind: "map",
title: "缩放到地图坐标",
description: action
? `${action.coordinate[0]}, ${action.coordinate[1]} (${action.sourceCrs})`
: "地图坐标",
};
}
if (tool === "locate_features" || LOCATE_TOOL_CONFIG[tool]) { if (tool === "locate_features" || LOCATE_TOOL_CONFIG[tool]) {
const locate = buildLocateArtifact(tool, params); const locate = buildLocateArtifact(tool, params);
return { return {
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useRef, type Dispatch, type SetStateAction } fr
import Feature from "ol/Feature"; import Feature from "ol/Feature";
import { GeoJSON } from "ol/format"; import { GeoJSON } from "ol/format";
import Point from "ol/geom/Point"; import Point from "ol/geom/Point";
import { transform } from "ol/proj";
import { bbox, featureCollection } from "@turf/turf"; import { bbox, featureCollection } from "@turf/turf";
import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler"; import { useChatToolActionHandler } from "@/hooks/useChatToolActionHandler";
@@ -110,6 +111,18 @@ export const useToolbarChatActions = ({
locateFeatures(action.ids, action.layer, action.geometryKind); locateFeatures(action.ids, action.layer, action.geometryKind);
break; break;
} }
case "zoom_to_map": {
const center =
action.sourceCrs === "EPSG:4326"
? transform(action.coordinate, "EPSG:4326", "EPSG:3857")
: action.coordinate;
map?.getView().animate({
center,
zoom: action.zoom ?? map.getView().getZoom() ?? 18,
duration: action.durationMs ?? 1000,
});
break;
}
case "view_history": { case "view_history": {
setChatPanelFeatureInfos(action.featureInfos); setChatPanelFeatureInfos(action.featureInfos);
setChatPanelType(action.dataType); setChatPanelType(action.dataType);
+7
View File
@@ -15,6 +15,13 @@ export type ChatToolAction =
layer: string; layer: string;
geometryKind: "point" | "line"; geometryKind: "point" | "line";
} }
| {
type: "zoom_to_map";
coordinate: [number, number];
sourceCrs?: "EPSG:3857" | "EPSG:4326";
zoom?: number;
durationMs?: number;
}
| { | {
type: "view_history"; type: "view_history";
featureInfos: [string, string][]; featureInfos: [string, string][];