高德地图与Three.js结合实现3D大屏可视化

高德地图与Three.js结合实现3D大屏可视化

文末源码地址及视频演示

前言

在智慧城市安全管理场景中,如何将真实的地理信息与3D模型完美结合,实现沉浸式的可视化监控体验?本文将以巡逻犬管理系统的大屏预览功能为例,详细介绍如何通过高德地图API与Three.js深度结合,实现3D机械狗模型在地图上的实时巡逻展示。

该系统实现了以下核心功能:

  • 在高德地图上加载并渲染3D机械狗模型
  • 实现模型沿预设路线的自动巡逻动画
  • 镜头自动跟随模型移动,提供沉浸式监控体验
  • 实时显示巡逻进度、告警信息等业务数据

技术栈

  • 高德地图 JS API 2.0:提供地图底图和空间定位能力
  • Three.js r157:3D模型渲染和动画控制
  • Loca 2.0:高德地图数据可视化API,用于镜头跟随
  • React + TypeScript:前端框架和类型支持
  • TWEEN.js:补间动画库,用于平滑的模型移动

一、高德地图初始化

1.1 地图配置

首先需要配置高德地图的加载参数,包括API Key、版本号等:

typescript 复制代码
// src/utils/amapConfig.ts
export const mapConfig = {
  key: 'your-amap-key',
  version: '2.0',
  Loca: {
    version: '2.0.0',  // Loca版本需与地图版本一致
  },
};

// 初始化安全配置(必须在AMapLoader.load之前调用)
export const initAmapSecurity = () => {
  if (typeof window !== 'undefined') {
    (window as any)._AMapSecurityConfig = {
      securityJsCode: 'your-security-code',
    };
  }
};

1.2 创建地图实例

使用AMapLoader.load加载地图API,然后创建地图实例:

typescript 复制代码
// 设置安全密钥
initAmapSecurity();

// 加载高德地图
const AMap = await AMapLoader.load(mapConfig);

// 创建地图实例,开启3D视图模式
const mapInstance = new AMap.Map(mapContainerRef.current, {
  zoom: 13,
  center: defaultCenter,
  viewMode: '3D',  // 关键:必须开启3D模式
  resizeEnable: true,
});

关键点

  • viewMode: '3D' 必须设置,否则无法使用3D相关功能
  • 需要提前设置安全密钥,否则会报错

1.3 初始化Loca容器

Loca是高德地图的数据可视化容器,用于实现镜头跟随等功能:

typescript 复制代码
const loca = new (window as any).Loca.Container({
  map: mapInstance,
  zIndex: 9
});

二、创建GLCustomLayer自定义图层

GLCustomLayer是高德地图提供的WebGL自定义图层,允许我们在地图上渲染Three.js内容。

2.1 图层结构

typescript 复制代码
const customLayer = new AMap.GLCustomLayer({
  zIndex: 200,  // 图层层级,确保模型在最上层
  init: async (gl: any) => {
    // 在这里初始化Three.js场景、相机、渲染器等
  },
  render: () => {
    // 在这里执行每帧的渲染逻辑
  },
});

mapInstance.add(customLayer);

2.2 初始化Three.js场景

init方法中创建Three.js的核心组件:

typescript 复制代码
init: async (gl: any) => {
  // 1. 创建透视相机
  const camera = new THREE.PerspectiveCamera(
    60,  // 视野角度
    window.innerWidth / window.innerHeight,  // 宽高比
    100,  // 近裁剪面
    1 << 30  // 远裁剪面(使用位运算表示大数值)
  );
  
  // 2. 创建WebGL渲染器
  const renderer = new THREE.WebGLRenderer({
    context: gl,  // 使用地图提供的WebGL上下文
    antialias: false,  // 禁用抗锯齿,减少WebGL扩展需求
    powerPreference: 'default',
  });
  renderer.autoClear = false;  // 必须设置为false,否则地图底图无法显示
  renderer.shadowMap.enabled = false;  // 禁用阴影,避免WebGL扩展问题
  
  // 3. 创建场景
  const scene = new THREE.Scene();
  
  // 4. 添加光源
  const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
  scene.add(ambientLight);
  
  const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
  directionalLight.position.set(1000, -100, 900);
  scene.add(directionalLight);
}

关键点

  • renderer.autoClear = false 必须设置,否则会清除地图底图
  • 使用地图提供的gl上下文创建渲染器,实现资源共享

三、坐标系统转换

高德地图使用经纬度坐标(WGS84),而Three.js使用3D世界坐标,两者之间的转换是关键。

3.1 获取自定义坐标系统

地图实例提供了customCoords工具,用于坐标转换:

typescript 复制代码
// 获取自定义坐标系统
const customCoords = mapInstance.customCoords;

// 设置坐标系统中心点(重要:必须在设置模型位置前设置)
const center = mapInstance.getCenter();
customCoords.setCenter([center.lng, center.lat]);

3.2 经纬度转3D坐标

使用lngLatsToCoords方法将经纬度转换为Three.js坐标:

typescript 复制代码
// 将经纬度 [lng, lat] 转换为Three.js坐标 [x, z, y?]
const position = customCoords.lngLatsToCoords([
  [120.188767, 30.193832]
])[0];

// 注意:返回的数组格式为 [x, z, y?]
// position[0] 对应 Three.js 的 z 轴(纬度)
// position[1] 对应 Three.js 的 x 轴(经度)
// position[2] 对应 Three.js 的 y 轴(高度,可选)

robotGroup.position.setX(position[1]);  // x坐标(经度)
robotGroup.position.setZ(position[0]);  // z坐标(纬度)
robotGroup.position.setY(position.length > 2 ? position[2] : 0);  // y坐标(高度)

坐标轴对应关系

  • 高德地图:X轴(经度),Y轴(纬度),Z轴(高度)
  • Three.js:X轴(右),Y轴(上),Z轴(前)
  • 转换后:position[1] → Three.js X轴,position[0] → Three.js Z轴

3.3 同步相机参数

render方法中,需要同步高德地图的相机参数到Three.js相机:

typescript 复制代码
render: () => {
  const { near, far, fov, up, lookAt, position } = customCoords.getCameraParams();
  
  // 同步相机参数
  camera.near = near;
  camera.far = far;
  camera.fov = fov;
  camera.position.set(position[0], position[1], position[2]);
  camera.up.set(up[0], up[1], up[2]);
  camera.lookAt(lookAt[0], lookAt[1], lookAt[2]);
  camera.updateProjectionMatrix();
  
  // 渲染场景
  renderer.render(scene, camera);
  
  // 必须执行:重新设置three的gl上下文状态
  renderer.resetState();
}

四、加载3D模型

4.1 使用GLTFLoader加载模型

typescript 复制代码
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

const loader = new GLTFLoader();
const modelPath = '/assets/modules/robot_dog/scene.gltf';

const gltf = await new Promise<any>((resolve, reject) => {
  loader.load(
    modelPath,
    (gltf: any) => resolve(gltf),
    (progress: any) => {
      if (progress.total > 0) {
        const percent = (progress.loaded / progress.total) * 100;
        console.log('模型加载进度:', percent.toFixed(2) + '%');
      }
    },
    reject
  );
});

const robotModel = gltf.scene;

4.2 模型预处理

加载模型后需要进行预处理,包括材质优化、位置调整等:

typescript 复制代码
// 遍历模型所有子对象
robotModel.traverse((child: THREE.Object3D) => {
  if (child instanceof THREE.Mesh) {
    // 禁用阴影相关功能
    child.castShadow = false;
    child.receiveShadow = false;
    
    // 简化材质,避免使用需要WebGL扩展的高级特性
    if (child.material) {
      const materials = Array.isArray(child.material) 
        ? child.material 
        : [child.material];
      
      materials.forEach((mat: any) => {
        // 禁用transmission等高级特性
        if (mat.transmission !== undefined) {
          mat.transmission = 0;
        }
      });
    }
  }
});

// 计算模型边界框并居中
const box = new THREE.Box3().setFromObject(robotModel);
const center = box.getCenter(new THREE.Vector3());

// 将模型居中(X和Z轴)
robotModel.position.x = -center.x;
robotModel.position.z = -center.z;
// 将模型底部放在y=0
robotModel.position.y = -box.min.y;

// 设置模型缩放
const scale = 15;
robotModel.scale.set(scale, scale, scale);

4.3 创建模型组并设置初始旋转

由于高德地图和Three.js的坐标系差异,需要调整模型的初始旋转:

typescript 复制代码
// 创建外层Group用于位置和旋转控制
const robotGroup = new THREE.Group();
robotGroup.add(robotModel);

// 设置初始旋转(90, 90, 0)度转换为弧度
const initialRotationX = (Math.PI / 180) * 90;
const initialRotationY = (Math.PI / 180) * 90;
const initialRotationZ = (Math.PI / 180) * 0;
robotGroup.rotation.set(initialRotationX, initialRotationY, initialRotationZ);

scene.add(robotGroup);

五、实现镜头跟随

5.1 使用Loca实现镜头跟随

高德地图的Loca API提供了viewControl.addTrackAnimate方法,可以实现镜头自动跟随路径移动:

typescript 复制代码
// 计算路径总距离
let totalDistance = 0;
for (let i = 0; i < paths.length - 1; i++) {
  totalDistance += AMap.GeometryUtil.distance(paths[i], paths[i + 1]);
}

// 假设速度是 1.5 m/s
const speed = 1.5;
const duration = (totalDistance / speed) * 1000;  // 转换为毫秒

loca.viewControl.addTrackAnimate({
  path: paths,  // 镜头轨迹,二维数组
  duration: duration,  // 时长(毫秒)
  timing: [[0, 0.3], [1, 0.7]],  // 速率控制器
  rotationSpeed: 180,  // 每秒旋转多少度
}, function () {
  console.log('单程巡逻完成');
  // 可以在这里处理往返逻辑
});

loca.animate.start();  // 启动动画

5.2 模型位置同步

render方法中,根据地图中心点实时更新模型位置:

typescript 复制代码
render: () => {
  // ... 同步相机参数代码 ...
  
  if (robotGroup && mapInstance && !patrolFinishedRef.current) {
    // 获取当前地图中心(镜头跟随会改变地图中心)
    const center = mapInstance.getCenter();
    if (center) {
      // 更新坐标系统中心点为地图中心点
      customCoords.setCenter([center.lng, center.lat]);
      
      // 将地图中心转换为Three.js坐标
      const position = customCoords.lngLatsToCoords([
        [center.lng, center.lat]
      ])[0];
      
      // 更新模型位置
      robotGroup.position.setX(position[1]);
      robotGroup.position.setZ(position[0]);
      robotGroup.position.setY(position.length > 2 ? position[2] : 0);
      
      // 更新模型旋转(根据地图旋转)
      const rotation = mapInstance.getRotation();
      if (rotation !== undefined) {
        const initialRotationY = (Math.PI / 180) * 90;
        robotGroup.rotation.y = initialRotationY + (rotation * Math.PI / 180);
      }
    }
  }
  
  // 渲染场景
  renderer.render(scene, camera);
  renderer.resetState();
}

关键点

  • 使用地图中心点作为模型位置,实现精确跟随
  • 在每次render中更新坐标系统中心点,确保坐标转换准确
  • 同步地图旋转角度到模型Y轴旋转

六、巡逻动画实现

6.1 启动巡逻

当模型加载完成并设置好初始位置后,可以启动巡逻动画:

typescript 复制代码
const startPatrol = (paths: number[][], mapInstance: any, AMap: any) => {
  // 停止之前的巡逻
  TWEEN.removeAll();
  patrolFinishedRef.current = false;
  
  // 保存路径
  patrolPathsRef.current = paths;
  patrolIndexRef.current = 0;
  
  // 播放前进动画
  playAnimation('1LYP');  // 播放行走动画
  
  // 设置坐标系统中心点为路径起点
  const firstPoint = paths[0];
  customCoordsRef.current.setCenter([firstPoint[0], firstPoint[1]]);
  
  // 使用Loca实现镜头跟随
  const loca = locaRef.current;
  if (loca) {
    // ... addTrackAnimate 代码 ...
  }
  
  // 启动模型移动动画
  changeObject();
};

6.2 模型移动动画

使用TWEEN.js实现模型在路径点之间的平滑移动:

typescript 复制代码
const changeObject = () => {
  if (patrolFinishedRef.current || patrolIndexRef.current >= patrolPathsRef.current.length - 1) {
    return;
  }

  const sp = patrolPathsRef.current[patrolIndexRef.current];
  const ep = patrolPathsRef.current[patrolIndexRef.current + 1];
  const s = new THREE.Vector2(sp[0], sp[1]);
  const e = new THREE.Vector2(ep[0], ep[1]);

  const speed = 0.03;
  const dis = AMap.GeometryUtil.distance(sp, ep);
  
  if (dis <= 0) {
    patrolIndexRef.current++;
    changeObject();
    return;
  }

  // 使用TWEEN实现平滑移动
  new TWEEN.Tween(s)
    .to(e.clone(), dis / speed / speedFactor)
    .start()
    .onUpdate((v) => {
      // 更新模型经纬度引用
      modelLngLatRef.current = [v.x, v.y];
      
      // 节流更新状态(每100ms更新一次)
      const now = Date.now();
      if (now - lastUpdateTimeRef.current > 100) {
        setCurrentLngLat([v.x, v.y]);
        checkSamplePoint([v.x, v.y], AMap);  // 检测取样点
        // 计算已巡逻长度
        updatePatrolledLength(v);
        lastUpdateTimeRef.current = now;
      }
    })
    .onComplete(() => {
      accumulatedLengthRef.current += dis;
      
      if (patrolIndexRef.current < patrolPathsRef.current.length - 2) {
        patrolIndexRef.current++;
        changeObject();  // 继续下一段
      } else {
        // 单程完成
        if (patrolMode !== '往返') {
          patrolFinishedRef.current = true;
          playAnimation('1Idle');  // 播放静止动画
        }
      }
    });
};

6.3 动画系统

模型支持多种动画(行走、静止、跳舞等),使用AnimationMixer管理:

typescript 复制代码
// 设置动画系统
if (gltf.animations && gltf.animations.length > 0) {
  const mixer = new THREE.AnimationMixer(robotModel);
  
  // 创建所有动画动作
  const actions = new Map<string, THREE.AnimationAction>();
  gltf.animations.forEach((clip: THREE.AnimationClip) => {
    const action = mixer.clipAction(clip);
    action.setLoop(THREE.LoopRepeat);  // 循环播放
    actions.set(clip.name, action);
  });
  
  // 播放默认静止动画
  const defaultAction = actions.get('1Idle');
  if (defaultAction) {
    defaultAction.setEffectiveTimeScale(0.6);  // 设置播放速度
    defaultAction.fadeIn(0.3);
    defaultAction.play();
  }
}

// 在render循环中更新动画
const render = () => {
  requestAnimationFrame(() => {
    render();
  });
  
  // 更新动画混合器
  if (mixer) {
    const currentTime = performance.now();
    const delta = (currentTime - lastAnimationTime) / 1000;
    mixer.update(delta);
    lastAnimationTime = currentTime;
  }
  
  // 更新TWEEN动画
  TWEEN.update();
  
  // 渲染地图
  mapInstance.render();
};

图片略大,耐心等候

七、AI安全隐患自动检测与告警

系统集成了Coze AI大模型,实现了巡逻过程中的自动安全隐患检测和告警功能。当机械狗沿路线巡逻时,系统会在预设的取样点自动触发AI分析,识别潜在的安全隐患。

7.1 取样点计算

系统支持基于路线间隔的自动取样点计算,根据巡逻犬配置的取样间隔(如每50米、100米等),在路线上均匀分布取样点:

typescript 复制代码
// 计算取样点(基于路线间隔)
const calculateSamplePoints = (
  paths: number[][], 
  sampleInterval: number, 
  AMap: any
): Array<{ lng: number; lat: number; distance: number }> => {
  const samplePoints: Array<{ lng: number; lat: number; distance: number }> = [];
  let accumulatedDistance = 0;
  
  // 从第一个点开始(0米处)
  samplePoints.push({
    lng: paths[0][0],
    lat: paths[0][1],
    distance: 0,
  });
  
  // 遍历路径,计算每个取样点
  for (let i = 0; i < paths.length - 1; i++) {
    const currentPoint = paths[i];
    const nextPoint = paths[i + 1];
    const segmentDistance = AMap.GeometryUtil.distance(currentPoint, nextPoint);
    
    // 检查当前段是否包含取样点
    while (accumulatedDistance + segmentDistance >= (samplePoints.length * sampleInterval)) {
      const targetDistance = samplePoints.length * sampleInterval;
      const distanceInSegment = targetDistance - accumulatedDistance;
      
      // 计算取样点在当前段中的位置(线性插值)
      const ratio = distanceInSegment / segmentDistance;
      const sampleLng = currentPoint[0] + (nextPoint[0] - currentPoint[0]) * ratio;
      const sampleLat = currentPoint[1] + (nextPoint[1] - currentPoint[1]) * ratio;
      
      samplePoints.push({
        lng: sampleLng,
        lat: sampleLat,
        distance: targetDistance,
      });
    }
    
    accumulatedDistance += segmentDistance;
  }
  
  return samplePoints;
};

关键点

  • 使用高德地图的GeometryUtil.distance计算路径段距离
  • 通过线性插值计算取样点的精确位置
  • 取样点从路线起点开始,按固定间隔均匀分布

7.2 自动触发检测

在巡逻过程中,系统实时检测模型位置是否到达取样点附近(±10米范围内):

typescript 复制代码
// 检测是否到达取样点
const checkSamplePoint = (currentLngLat: [number, number], AMap: any) => {
  const patrolDog = currentPatrolDogRef.current;
  const route = currentRouteRefForSample.current;
  const area = currentAreaRefForSample.current;
  
  if (!patrolDog || !route || !patrolDog.cameraDeviceId) {
    return; // 没有绑定摄像头,不进行取样
  }

  // 检查取样方式(必须是"路线间隔"模式)
  if (patrolDog.sampleMode !== '路线间隔' || !patrolDog.sampleInterval) {
    return;
  }

  // 检查是否在取样点附近(±10米范围内)
  for (let i = 0; i < samplePointsRef.current.length; i++) {
    if (processedSamplePointsRef.current.has(i)) {
      continue; // 已处理过,跳过
    }

    const samplePoint = samplePointsRef.current[i];
    const distance = AMap.GeometryUtil.distance(
      [currentLngLat[0], currentLngLat[1]],
      [samplePoint.lng, samplePoint.lat]
    );

    // 在 ±10 米范围内,触发取样
    if (distance <= 10) {
      console.log(`✅ 到达取样点 ${i + 1}/${samplePointsRef.current.length}`);
      processedSamplePointsRef.current.add(i);
      
      // 异步调用 Coze API(不阻塞巡逻)
      analyzeSecurity(
        patrolDog,
        route,
        area,
        currentLngLat,
        AMap
      ).catch(error => {
        console.error('安全隐患分析失败:', error);
      });
      
      break; // 一次只处理一个取样点
    }
  }
};

关键点

  • 使用距离判断,避免重复触发
  • 异步调用AI分析,不阻塞巡逻动画
  • 使用Set记录已处理的取样点,确保每个点只处理一次

7.3 调用Coze API进行安全隐患分析

系统使用Coze平台的大模型工作流进行图像安全隐患分析:

typescript 复制代码
// 调用 Coze API 进行安全隐患分析
const analyzeSecurity = async (
  patrolDog: PatrolDog,
  route: Route,
  area: Area | null,
  currentLngLat: [number, number],
  AMap: any
): Promise<void> => {
  try {
    // 1. 获取默认令牌
    await initDB();
    const tokens = await db.token.getAll();
    const validTokens = tokens.filter(token => Date.now() <= token.expireDate);
    if (validTokens.length === 0) {
      console.warn('没有可用的令牌,跳过安全隐患分析');
      return;
    }

    const defaultToken = validTokens.find(t => t.isDefault) || validTokens[0];
    
    // 2. 准备分析数据
    // 随机选择一张测试图片(实际应用中应使用摄像头实时抓拍)
    const randomImageUrl = imageUrlr[Math.floor(Math.random() * imageUrlr.length)];
    
    // 构建输入文本,描述当前巡逻场景
    const inputText = `${patrolDog.name}当前在${area?.name || '未知'}区域${route.name}巡逻时抓拍了一张照片。分析是否存在安全隐患`;
    
    // 3. 创建 Coze API 客户端
    const apiClient = new CozeAPI({
      token: defaultToken.token,
      baseURL: 'https://api.coze.cn',
      allowPersonalAccessTokenInBrowser: true,
    });

    // 4. 调用工作流
    const workflow_id = '7585585625312034858';
    const res = await apiClient.workflows.runs.create({
      workflow_id: workflow_id,
      parameters: {
        input: inputText,
        mediaUrl: randomImageUrl,
      },
    });

    // 5. 解析返回结果
    let analysisResult: { securityType: number; score: number; desc: string } | null = null;
    
    if (res.data) {
      const dataObj = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
      
      if (dataObj.output && typeof dataObj.output === 'string') {
        // 提取 markdown 代码块中的 JSON
        const jsonMatch = dataObj.output.match(/```json\s*([\s\S]*?)\s*```/) || 
                         dataObj.output.match(/```\s*([\s\S]*?)\s*```/);
        
        if (jsonMatch && jsonMatch[1]) {
          analysisResult = JSON.parse(jsonMatch[1].trim());
        } else {
          // 尝试直接解析 output 为 JSON
          analysisResult = JSON.parse(dataObj.output);
        }
      } else {
        analysisResult = dataObj;
      }
    }

    // 6. 判断是否是报警(securityType !== 0 且 score !== 0)
    if (analysisResult && analysisResult.securityType !== 0 && analysisResult.score !== 0) {
      // 保存到分析报警表
      const analysisAlert: Omit<AnalysisAlert, 'id' | 'createTime' | 'updateTime'> = {
        alertTime: Date.now(),
        patrolDogId: patrolDog.id!,
        patrolDogName: patrolDog.name,
        cameraDeviceId: patrolDog.cameraDeviceId,
        cameraDeviceName: patrolDog.cameraDeviceName,
        routeId: route.id!,
        routeName: route.name,
        areaId: area?.id,
        areaName: area?.name,
        securityType: analysisResult.securityType as 0 | 1 | 2 | 3 | 4 | 5,
        score: analysisResult.score,
        desc: analysisResult.desc,
        mediaUrl: randomImageUrl,
        input: inputText,
        status: '未处理',
      };

      await db.analysisAlert.add(analysisAlert);
      console.log('✅ 安全隐患告警已保存');
      
      // 更新告警列表(实时显示在大屏右侧)
      updateAlertList(patrolDog.id!, route.id!, area?.id);
    } else {
      console.log('未发现安全隐患,不保存报警');
    }
  } catch (error) {
    console.error('调用 Coze API 失败:', error);
  }
};

API返回结果格式

json 复制代码
{
  "securityType": 1,  // 0=无隐患, 1=明火燃烟, 2=打架斗殴, 3=违章停车, 4=杂物堆放, 5=私搭乱建
  "score": 85,        // 严重程度评分 (0-100)
  "desc": "检测到明火,存在严重安全隐患"  // 详细描述
}

关键点

  • 使用@coze/api官方SDK调用工作流API
  • 支持多种安全隐患类型识别(明火燃烟、打架斗殴、违章停车等)
  • 自动保存告警记录,支持后续查询和处理
  • 告警信息实时显示在大屏右侧告警列表中

7.4 Coze测试页面

系统提供了专门的Coze测试页面,方便开发者测试和调试AI分析功能。在Coze测试页面中,可以:

  1. 选择令牌:从已配置的Coze API令牌中选择(支持多个令牌管理)
  2. 输入分析文本:描述需要分析的场景
  3. 上传图片URL:提供需要分析的图片地址
  4. 自动填充功能:点击"自动填充"按钮,快速填充默认的测试数据
  5. 查看完整响应:显示Coze API的完整返回结果,包括解析后的JSON和原始响应
typescript 复制代码
// Coze测试页面核心功能
const handleTest = async () => {
  const values = await form.validateFields();
  
  // 创建 Coze API 客户端
  const apiClient = new CozeAPI({
    token: values.token,
    baseURL: 'https://api.coze.cn',
    allowPersonalAccessTokenInBrowser: true,
  });

  // 调用工作流
  const workflow_id = '7585585625312034858';
  const res = await apiClient.workflows.runs.create({
    workflow_id: workflow_id,
    parameters: {
      input: values.input,
      mediaUrl: values.mediaUrl,
    },
  });

  // 解析并显示结果
  // ... 解析逻辑 ...
};

测试页面特性

  • 自动填充数据:提供默认的测试图片和文本,方便快速测试
  • 图片预览:实时预览输入的图片URL
  • 完整响应展示:显示API的完整响应,便于调试
  • 错误处理:友好的错误提示,帮助定位问题

请截图 Coze测试页面 自动填充功能 测试结果展示

使用场景

  • 测试新的安全隐患识别算法
  • 验证Coze API令牌是否有效
  • 调试API返回结果格式
  • 验证图片URL是否可被Coze解析

八、性能优化建议

7.1 渲染优化

  • 禁用不必要的WebGL扩展(如阴影、抗锯齿)
  • 使用requestAnimationFrame统一管理渲染循环
  • 合理设置模型LOD(细节层次)

7.2 内存管理

  • 及时清理不需要的TWEEN动画:TWEEN.removeAll()
  • 组件卸载时销毁Three.js资源
  • 模型加载后缓存,避免重复加载

7.3 坐标转换优化

  • 坐标系统中心点跟随地图中心,减少转换误差
  • 使用节流控制状态更新频率
  • 避免在render中进行复杂计算

九、常见问题解决

8.1 模型不显示

问题:模型加载成功但在地图上不可见

解决方案

  • 检查renderer.autoClear是否设置为false
  • 确认坐标转换是否正确(注意数组索引对应关系)
  • 检查模型缩放是否合适(可能太小或太大)

8.2 模型位置偏移

问题:模型位置与预期不符

解决方案

  • 确保在设置模型位置前调用customCoords.setCenter()
  • 检查坐标轴对应关系(position[1]对应X轴,position[0]对应Z轴)
  • 使用AxesHelper辅助调试坐标轴方向

8.3 镜头跟随不流畅

问题:镜头跟随有延迟或卡顿

解决方案

  • 调整rotationSpeed参数,控制旋转速度
  • 优化timing速率控制器,实现更平滑的加速减速
  • 检查render循环是否正常执行

十、总结

通过高德地图与Three.js的深度结合,我们成功实现了3D模型在地图上的实时展示和动画效果,并集成了AI大模型实现智能安全隐患检测。核心要点包括:

  1. GLCustomLayer是关键桥梁:通过自定义图层实现Three.js与高德地图的融合
  2. 坐标转换是核心 :正确理解和使用customCoords进行坐标转换
  3. 镜头跟随提升体验:使用Loca API实现平滑的镜头跟随效果
  4. AI智能检测增强功能:集成Coze大模型实现自动安全隐患识别和告警
  5. 性能优化不可忽视:合理配置渲染参数,避免不必要的WebGL扩展

技术亮点

  • 虚实结合:真实地理信息与3D模型的完美融合
  • 智能检测:基于AI大模型的自动安全隐患识别
  • 实时告警:巡逻过程中的实时检测和告警推送
  • 可视化展示:沉浸式大屏监控体验

这种技术方案不仅适用于巡逻犬管理系统,还可以扩展到智慧城市、物流追踪、车辆监控、园区安防等多个场景,为空间数据可视化提供了强大的技术支撑。通过AI能力的集成,系统从传统的可视化展示升级为智能化的安全监控平台,实现了"看得见、管得住、能预警"的完整闭环。

参考资源

www.bilibili.com/video/BV18c...

相关推荐
秋雨雁南飞2 小时前
WaferMap.HTML
前端·css·html
前端不太难2 小时前
RN 列表里的局部状态和全局状态边界
开发语言·前端·harmonyos
程琬清君2 小时前
前端动态标尺
开发语言·前端·javascript
0思必得02 小时前
[Web自动化] Web安全基础
运维·前端·javascript·python·自动化·html·web自动化
天天向上vir2 小时前
防抖与节流
前端·typescript·vue
宇珩前端踩坑日记2 小时前
怎么让 Vue DevTools 用 Trae 打开源码
前端·trae
小徐不会敲代码~2 小时前
Vue3 学习 6
开发语言·前端·vue.js·学习
CreasyChan2 小时前
C#中单个下划线的语法与用途详解
前端·c#
C_心欲无痕2 小时前
react - useState更新机制(直接更新和函数式更新)
前端·javascript·react.js