feat(map): add coordinate zoom action
This commit is contained in:
@@ -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);
|
||||||
|
|||||||
@@ -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][];
|
||||||
|
|||||||
Reference in New Issue
Block a user