途个开心:旅行规划与记录系统

这个项目属于哪个课程 2025综合设计——多源异构数据采集与融合应用综合实践
组名、项目简介 组名:汪汪功立大队
项目需求:随着个性化旅游需求的爆发式增长,传统碎片化的信息获取与行程管理方式已无法满足用户对效率与深度的双重追求。同时,旅行结束后的数据资产(如足迹、消费、情感记忆)往往散落在不同平台,缺乏系统的沉淀与分析。
项目目标:构建一个集智能规划、实时地图联动、全生命周期旅行记录与多维数据洞察于一体的 Web 应用平台。
项目开展技术路线
数据采集层:高德地图API实时地理数据获取 + 旅游信息采集 + AI清洗去重
数据存储层:SQLite/PostgreSQL关系型数据库 + Redis缓存机制 + 结构化行程数据建模
后端逻辑层:Python FastAPI异步框架 + 阿里云Qwen大模型Agent + Pydantic数据验证
算法层:高德地图多模式路线规划API + 旅行人格DNA量化算法 + 经济学模型分析
前端交互层:React 18数据流UI + 高德地图交互式组件 + 生态流光可视化仪表盘
团队成员学号 102302132(组长),102302133,102302134,102302135,102302137,102302145,102302107
这个项目目标 本项目旨在打造一款全生命周期的智能旅行助手,实现以下目标:
1. 智能规划:利用 LLM 实现“一句话生成可执行路书”。
2. 地图联动:实现对话与地图的实时双向交互,所见即所得。
3. 资产沉淀:提供完善的旅行记录功能,覆盖从计划到完成的全过程。
4. 深度分析:通过可视化数据看板,为用户提供上帝视角的旅行行为分析报告。

个人完成:

1.数据的采集

起初,我在GitHub上找到了基于Selenium模拟网页登录爬取小红书笔记、微博,但是能力有限一直没有修改成功。后来,通过搜索发现可以利用八爪鱼采集器来爬取数据:

133bcd658e9c06c97252ee11a1866823

将爬取到的内容通过AI的清洗构建应用平台的知识库。

2.地图功能的调用

(1)POI搜索功能调用

async def search_poi(keywords: str, city: str = "", api_key: str = None):
    """
    Search for a place (Point of Interest) by keyword and optional city.
    """
    # Use passed key if available, else default
    key_to_use = api_key if api_key else AMAP_KEY
    
    url = "https://restapi.amap.com/v3/place/text"
    params = {
        "key": key_to_use,
        "keywords": keywords,
        "city": city,
        "offset": 5,  # Return top 5 results
        "page": 1,
        "extensions": "all"
    }

search_poi 函数用于根据关键词搜索兴趣点,它向高德地图的 v3/place/text 接口发送异步 HTTP 请求。该函数支持传入关键词和可选的城市参数,可以返回最多 5 个搜索结果。API 调用成功后,我们会从返回数据中提取每个 POI 的名称、详细地址、经纬度坐标、类型分类和联系电话等关键信息,并将这些数据格式化为结构化的字典列表返回给前端使用。

(2)周边搜索功能实现

async def search_nearby(location: str, keywords: str = "", radius: int = 1000, api_key: str = None):
    """
    Search for places around a specific coordinate (lng,lat).
    Useful for finding 'parking', 'hotel' near a spot.
    """
    # Use passed key if available, else default
    key_to_use = api_key if api_key else AMAP_KEY
    
    url = "https://restapi.amap.com/v3/place/around"
    params = {
        "key": key_to_use,
        "location": location,
        "keywords": keywords,
        "radius": radius,
        "offset": 5,
        "page": 1,
        "extensions": "all"
    }

search_nearby 专门用于搜索指定坐标点周边的设施。它调用高德地图的 v3/place/around 接口,支持设置搜索半径(默认 1000 米)和特定的关键词,比如 "parking" 寻找停车场或 "hotel" 搜索酒店。这个功能在用户需要寻找某个景点或位置附近的配套设施时特别有用。除了基本的 POI 信息外,该函数还会获取每个地点与中心点的距离信息。

(3)地图实例化与配置

// 初始化地图
  useEffect(() => {
    if (!mapLoaded || !isOpen || !mapContainerRef.current) return;

    try {
      console.log('初始化地图...');

      // 创建地图实例
      const mapInstance = new window.AMap.Map(mapContainerRef.current, {
        zoom: 11,
        center: [116.397428, 39.90923], // 北京中心
        viewMode: '2D',
        resizeEnable: true,
        zoomEnable: true,
        dragEnable: true,
        doubleClickZoom: true,
        keyboardEnable: false,
      });

      // 添加控件
      mapInstance.addControl(new window.AMap.Scale());
      mapInstance.addControl(new window.AMap.ToolBar());

地图初始化时,设置了合理的默认参数:缩放级别为 11,默认中心点设为北京天安门广场(经纬度 [116.397428, 39.90923])。地图启用了缩放、拖拽、双击缩放等交互功能,并添加了比例尺和工具栏控件,为用户提供了完整的地图操作体验。

(4)用户位置获取与标记

// 尝试获取当前位置
      if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(
          (position) => {
            const { latitude, longitude } = position.coords;
            setCurrentLocation([longitude, latitude]);

            // 添加当前位置标记
            const marker = new window.AMap.Marker({
              position: [longitude, latitude],
              title: '我的位置',
              icon: 'https://webapi.amap.com/theme/v1.3/markers/n/mark_bs.png'
            });
            marker.setMap(mapInstance);

            // 将地图中心设置为当前位置
            mapInstance.setCenter([longitude, latitude]);
            mapInstance.setZoom(14);
          },
          (error) => {
            console.log('获取位置失败:', error);
          }
        );
      }
    } catch (err) {
      console.error('地图初始化失败:', err);
      setError('地图初始化失败: ' + err.message);
    }
  }, [mapLoaded, isOpen]);

首先检测浏览器是否支持地理位置API,若支持则尝试获取用户当前位置坐标。成功获取后,将经纬度数据转换为高德地图兼容格式,同时在地图上添加专属标记点并使用官方蓝色图标标识“我的位置”。随后,地图视图会自动调整:中心点精确定位至用户所在位置,并设置14级缩放级别以展示适宜的周边环境。

(5)搜索结果的动态标记

// 搜索地点
  const handleSearch = async () => {
    if (!searchQuery.trim()) {
      setError('请输入搜索关键词');
      return;
    }

    setLoading(true);
    setError('');
    setSearchResults([]);

    try {
      console.log('开始搜索:', searchQuery);

      // 获取访问令牌
      const token = localStorage.getItem('access_token');
      const headers = token ? { 'Authorization': `Bearer ${token}` } : {};

      console.log('请求头:', headers);

      const response = await axios.get(`${BASE_URL}/api/map/search`, {
        params: {
          keyword: searchQuery,
          city: '全国'
        },
        headers: headers,
        timeout: 10000 // 10秒超时
      });

      console.log('搜索响应:', response.data);

      if (response.data.success) {
        const results = response.data.data || [];
        setSearchResults(results);
        console.log(`搜索成功,找到 ${results.length} 个结果`);

        // 在地图上标记搜索结果
        if (map && results.length > 0) {
          if (typeof map.clearMap === 'function') {
            map.clearMap();
          } else if (typeof map.clear === 'function') {
            map.clear(); // 如果 clearMap 不存在,尝试使用 clear()
          } else {
            console.warn('地图清理方法不可用');
          }

          const markers = [];
          results.forEach((place, index) => {
            if (place.location && place.location.lng && place.location.lat) {
              const marker = new window.AMap.Marker({
                position: [place.location.lng, place.location.lat],
                title: place.name,
                content: `<div class="bg-white p-1 rounded shadow text-xs">${index + 1}. ${place.name}</div>`,
                offset: new window.AMap.Pixel(0, -25)
              });

              marker.on('click', () => {
                const infoWindow = new window.AMap.InfoWindow({
                  content: `
                    <div class="p-2 max-w-xs">
                      <h3 class="font-bold text-sm">${place.name}</h3>
                      <p class="text-gray-600 text-xs mt-1">${place.address || '无地址信息'}</p>
                      <p class="text-gray-500 text-xs mt-1">${place.type || '未知类型'}</p>
                      ${place.tel && place.tel !== 'N/A' ? `<p class="text-green-500 text-xs mt-1"> ${place.tel}</p>` : ''}
                    </div>
                  `,
                  offset: new window.AMap.Pixel(0, -35)
                });
                infoWindow.open(map, marker.getPosition());
              });

              marker.setMap(map);
              markers.push(marker);
            }
          });

搜索过程中设置了10秒超时限制和授权验证,确保安全性和响应效率。获取搜索结果后,代码会先清理地图上的现有标记,然后为每个有效地点创建带编号的可视化标记,并绑定点击事件以展示详细信息弹窗,展示该地点的详细信息,包括名称、地址、类型和联系电话等。同时,搜索结果会被存储到状态中供界面列表显示,整个过程包含完整的加载状态管理和错误处理机制。

(6)智能视野调整

// 调整地图视野
          if (markers.length > 0) {
            const bounds = new window.AMap.Bounds();
            markers.forEach(marker => {
              bounds.extend(marker.getPosition());
            });
            map.setBounds(bounds);

            // 如果只有一个结果,放大一点
            if (markers.length === 1) {
              map.setZoom(15);
            }
          }
        } else if (results.length === 0) {
          setError('未找到相关地点,请尝试其他关键词');
        }
      } else {
        console.error('搜索失败:', response.data.msg);
        setError('搜索失败: ' + (response.data.msg || '未知错误'));
      }
    } catch (error) {
      console.error('搜索请求失败:', error);

搜索结果较多时,实现了自动调整地图视野的功能。通过计算所有标记点的位置边界,使用 setBounds 方法将地图调整到恰好能显示所有标记点的合适范围。如果只有一个搜索结果,则会将地图缩放到 15 级,提供更详细的视图。

(7)路线规划及可视化

// 规划路线
  const handleRoute = async () => {
    if (!origin.trim() || !destination.trim()) {
      setError('请输入起点和终点');
      return;
    }

    setLoading(true);
    setError('');
    setRouteResult(null);

    try {
      console.log('开始规划路线:', { origin, destination, mode: routeMode });

      // 获取访问令牌
      const token = localStorage.getItem('access_token');
      const headers = token ? { 'Authorization': `Bearer ${token}` } : {};

      const response = await axios.get(`${BASE_URL}/api/map/direction`, {
        params: {
          origin,
          destination,
          mode: routeMode
        },
        headers: headers,
        timeout: 15000 // 15秒超时
      });

      console.log('路线规划响应:', response.data);

      if (response.data.success) {
        setRouteResult(response.data);
        console.log('路线规划成功');

        // 在地图上显示路线
        if (map) {
          if (typeof map.clearMap === 'function') {
            map.clearMap();
          } else if (typeof map.clear === 'function') {
            map.clear(); // 如果 clearMap 不存在,尝试使用 clear()
          } else {
            console.warn('地图清理方法不可用');
          }

          // 标记起点
          if (response.data.origin_loc) {
            const originMarker = new window.AMap.Marker({
              position: response.data.origin_loc,
              title: '起点',
              icon: 'https://webapi.amap.com/theme/v1.3/markers/n/start.png'
            });
            originMarker.setMap(map);
          }

          // 标记终点
          if (response.data.dest_loc) {
            const destMarker = new window.AMap.Marker({
              position: response.data.dest_loc,
              title: '终点',
              icon: 'https://webapi.amap.com/theme/v1.3/markers/n/end.png'
            });
            destMarker.setMap(map);
          }

          // 绘制路线
          if (response.data.path && response.data.path.length > 0) {
            // 根据交通方式选择颜色
            let strokeColor = '#FF6B6B'; // 默认红色
            switch (routeMode) {
              case 'driving':
                strokeColor = '#FFE66D'; // 黄色
                break;
              case 'bus':
                strokeColor = '#45B7D1'; // 蓝色
                break;
              case 'train':
                strokeColor = '#4ECDC4'; // 青色
                break;
              case 'walking':
                strokeColor = '#FF6B6B'; // 红色
                break;
              case 'bicycling':
                strokeColor = '#95E77E'; // 绿色
                break;
              case 'plane':
                strokeColor = '#C780E8'; // 紫色
                break;
            }

代码实现了完整的路线规划与地图可视化流程。首先验证用户输入的起点和终点,设置加载状态并发起后端API请求获取路线数据。成功获取数据后,代码执行地图可视化操作:清空现有地图标记,使用专用图标分别标记起点和终点位置。然后根据用户选择的交通方式(驾车、公交、步行、火车、自行车或飞机)为路线路径线分配相应的视觉颜色编码,如驾车用黄色、公交用蓝色等,通过色彩区分增强不同出行模式的可辨识度,为用户提供直观的路线展示体验。

(8)最终界面及应用的展示:

image

image

3.个人心得

此次“途个开心”综合实践,虽然在数据采集方面碰了壁,但还是让我了解到了新的工具。同时在开发过程中,通过高德地图的POI搜索系统,不仅实现了基础的地点查询功能,更重要的是构建了一个动态的地理信息处理管道:首先利用关键字搜索API将用户的模糊需求(如“西湖附近的茶馆”)转化为精确的地理实体列表;然后调用周边搜索API建立位置关联网络,智能发现目标地点周围的配套设施;最后结合地图渲染技术,将这些抽象数据转化为直观的可视化标记——每个搜索结果都不仅仅是文字信息,而是带有精确坐标、分类标识和交互能力的地理实体。

posted @ 2025-12-30 21:38  cjx2133  阅读(12)  评论(0)    收藏  举报