首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用WorkBuddy开发一个智能情感地图小程序

用WorkBuddy开发一个智能情感地图小程序

原创
作者头像
悟空码字
修改2026-04-24 17:03:31
修改2026-04-24 17:03:31
810
举报
文章被收录于专栏:工具工具

一、引言:城市有话说,但谁来听?

每一座城市都在低语——老街的梧桐树下,藏着只有下午三点才透进来的光;巷尾的咖啡馆里,有最适合发呆的角落和旧时光的味道。但这些信息,地图听不见。

打开任何一个地图 App,你能搜到"附近的咖啡厅",却搜不到"一个适合发呆的地方";你能看到 4.8 分的评分,却看不到"这里的空气里飘着慵懒的气息"。

地图变得越来越精确,却离人的感受越来越远。

如果地图能听懂"情绪",城市会变成什么样?这就是 City Whisperer(城市低语)诞生的原因——一个让 AI 听懂你的感觉、让地图为你写诗的小程序。


二、灵感来源:从一次"漫无目的"的周末说起

这个项目的灵感,来自一个再普通不过的周末午后。

那天阳光很好,我想出门走走,但不想去"打卡",也不想"逛街",只想找一个有感觉的地方坐坐。打开地图,搜索框光标闪烁——我该搜什么?

输入"咖啡厅"?出来一堆连锁品牌。输入"景点"?全是人挤人的打卡地。我真正想要的那种"光线好、人不多、能发呆"的地方,根本不是一个品类关键词能描述的。

我最终在朋友圈翻到了朋友的推荐,找到了一家藏在居民楼下的小书店。推门进去的那一刻,阳光刚好从木窗格洒进来,空气里有旧书的味道——这就是我想找的感觉。

那一刻我意识到:用户心里装的是"感觉",但地图搜索框要的是"品类"。 这中间有一道巨大的鸿沟,而 AI 可以成为连接两端的桥梁。

如果我对手机说一句"找个适合发呆的老建筑",AI 能把"发呆"翻译成"咖啡馆、图书馆、书店",把"老建筑"翻译成"古迹、历史建筑",然后地图自动帮我搜索、标记、甚至用诗意的语言告诉我这个地方给人什么感觉——这不就是城市在对你低语吗?

City Whisperer,由此诞生。


三、场景痛点:现有地图产品缺了什么?

在深入技术实现之前,让我们先拆解一下现有地图产品在"情感化探索"场景下的三个核心痛点。

3.1 关键词困局:用户心里是"感觉",搜索框要的是"品类"

"我想找个适合发呆的地方"——这是人类最自然的表达方式。但当前所有地图 App 都要求你输入一个明确的品类词:咖啡厅、书店、公园……用户被迫把自己的"感觉"翻译成"品类",而这个翻译过程本身就会丢失大量信息。"适合发呆"可能是咖啡馆,也可能是公园的长椅,甚至是一间安静的博物馆——只有 AI 能理解这种模糊性。

3.2 信息冰冷:POI 只有名称和评分,缺少情感化的场景描述

当你终于搜到了一堆结果,地图给你的信息是:名称、地址、评分、距离。这些信息是"有用"的,但不是"有感"的。一个 4.5 分的咖啡馆和一个"阳光从木窗格洒进来"的咖啡馆,哪个更让你想推门进去? 用户需要的不仅是事实,更是一种预期感、一种"我想去那里"的情绪驱动。

3.3 交互割裂:搜→看→选→去,每一步都是手动决策

传统地图的交互链路是线性的:搜关键词 → 看列表 → 比较评分 → 选一个 → 导航。每一步都需要用户主动思考和决策,没有"被引领"的体验感。而"探索城市"本质上应该是一种放松的、被启发的体验——你说一句话,地图就能帮你完成后续所有步骤。

痛点的本质:从"你告诉地图找什么"到"你告诉地图你想感受什么"——这就是 City Whisperer 要解决的命题。


四、项目概览:City Whisperer 是什么?

City Whisperer(城市低语) 是一款基于腾讯位置服务的智能地图探索微信小程序。核心交互极其简单:

  1. 你说一句话:在搜索栏输入或语音说出"找个安静的露台""我想喝杯咖啡发发呆"
  2. AI 听懂你:自动解析你的意图,将情绪化描述翻译为地图搜索关键词
  3. 地图标记:调用腾讯地图 API 周边搜索,在地图上标记符合情境的地点
  4. 城市低语:点击地点,AI 生成一段富有情感的"场所印象"文案
  5. 出发探索:一键导航,或分享给朋友一起出发

与传统地图搜索的对比:

维度

传统地图

City Whisperer

输入方式

品类关键词

自然语言 / 语音

搜索逻辑

精确匹配

AI 意图理解

信息呈现

名称 + 评分 + 距离

名称 + 情感文案 + 导航

交互体验

线性决策

沉浸式引导

情感连接

"场所印象"AI 文案


五、架构设计:从一句话到一个地标

5.1 整体架构图

整个系统分为五层,从用户输入到最终交互反馈形成完整闭环:

  • 用户输入层:语音输入和文本输入的统一入口
  • AI 意图解析层:将自然语言翻译为结构化搜索参数
  • 腾讯位置服务层:基于 QQMapWX SDK 的定位与搜索
  • 数据处理层:搜索结果处理、距离计算、AI 文案生成
  • 交互反馈层:地图渲染、底部抽屉、导航与分享

5.2 技术选型与依赖

模块

技术方案

说明

地图组件

微信小程序原生 <map>

无需额外组件,性能最优

位置服务

QQMapWX SDK(JavaScript SDK)

搜索、定位、距离计算

坐标系统

GCJ-02(国测局坐标)

与微信地图一致,wx.getLocation 直接返回

AI 能力

Mock 实现 + 预留混元 API 接口

意图解析、文案生成

UI 样式

原子化 CSS + Glassmorphism

毛玻璃风格,原子化工具类

5.3 数据流设计

代码语言:javascript
复制
用户输入 "找个适合发呆的老建筑"
        ↓
parseIntent() → { keyword: "古迹", orderby: "_distance" }
        ↓
qqmapsdk.search({ keyword: "古迹", location: "39.9,116.3" })
        ↓
processSearchResults() → 生成 markers[] + AI 文案
        ↓
<map> 渲染 markers + scale: 16 放大
        ↓
bindmarkertap → 底部抽屉 → 场所印象 → 导航/分享

六、功能实现与核心代码

6.1 初始化定位:让地图知道"我在哪"

一切探索的起点,是用户当前的位置。我们使用 wx.getLocation 获取 GCJ-02 坐标,并创建地图上下文:

代码语言:javascript
复制
initLocation() {
  wx.showLoading({ title: '正在获取位置...' })

  wx.getLocation({
    type: 'gcj02', // 使用国测局坐标,与地图组件一致
    success: (res) => {
      this.setData({
        latitude: res.latitude,
        longitude: res.longitude,
        userLat: res.latitude,
        userLng: res.longitude
      })
      // 保存到全局,供搜索时使用
      getApp().globalData.userLocation = {
        latitude: res.latitude,
        longitude: res.longitude
      }
      this.mapContext = wx.createMapContext('cityMap', this)
      wx.hideLoading()
    },
    fail: () => {
      wx.hideLoading()
      wx.showToast({ title: '定位失败,使用默认位置', icon: 'none' })
    }
  })
}

关键点type: 'gcj02' 必须显式指定,否则默认返回 WGS-84 坐标,与地图组件的 GCJ-02 坐标系不匹配,会导致定位点偏移。

6.2 AI 意图解析:把"想发呆"翻译成"咖啡馆"

这是 City Whisperer 最核心的创新点。当前实现采用关键词映射方案,未来将接入混元大模型 API:

代码语言:javascript
复制
parseIntent(userInput) {
  const input = userInput.toLowerCase()

  // 关键词映射表:将自然语言映射为 POI 搜索关键词
  const keywordMap = {
    '咖啡': '咖啡厅', '喝咖啡': '咖啡厅',
    '发呆': '咖啡馆', '安静': '图书馆',
    '老建筑': '古迹',   '古建筑': '古迹',
    '露台': '露台餐厅', '拍照': '景点',
    '夜景': '观景台',   '猫': '猫咖',
    // ... 50+ 关键词映射
  }

  // 排序方式推断
  const orderbyMap = {
    '近': '_distance', '附近': '_distance',
    '好评': '_score',   '推荐': '_score'
  }

  // 匹配关键词和排序
  let keyword = '景点'  // 默认
  let orderby = '_distance'

  for (const [key, value] of Object.entries(keywordMap)) {
    if (input.includes(key)) { keyword = value; break }
  }
  for (const [key, value] of Object.entries(orderbyMap)) {
    if (input.includes(key)) { orderby = value; break }
  }

  return { keyword, orderby }
}

⚠️ 此处未来接入混元大模型 API——将 userInput 发送至混元 API,获取结构化意图,包括关键词、排序方式、情感标签、文案风格等维度。

6.3 地图搜索与渲染:从关键词到地图上的光点

意图解析后,调用腾讯位置服务的 search 接口进行周边搜索:

代码语言:javascript
复制
handleUserQuery(userInput) {
  this.setData({ aiThinking: true })

  const intent = this.parseIntent(userInput)
  const { userLat, userLng } = this.data
  const locationStr = userLat && userLng
    ? `${userLat},${userLng}`
    : `${this.data.latitude},${this.data.longitude}`

  qqmapsdk.search({
    keyword: intent.keyword,
    location: locationStr,
    orderby: intent.orderby,
    page_size: 20,
    page_index: 1,
    success: (res) => {
      if (res.status === 0 && res.data && res.data.length > 0) {
        this.processSearchResults(res.data, intent)
      } else {
        this.setData({ aiThinking: false })
        wx.showToast({ title: '没有找到相关地点', icon: 'none' })
      }
    }
  })
}

搜索成功后,将结果处理为地图 markers 数组,并将地图从初始的 scale: 12(大视野)放大到 scale: 16(街道级别),让用户清晰看到搜索点位:

代码语言:javascript
复制
processSearchResults(poiList, intent) {
  const markers = poiList.map((poi, index) => {
    // 距离格式化
    let distanceText = '未知'
    if (poi._distance !== undefined) {
      distanceText = poi._distance < 1000
        ? `${Math.round(poi._distance)}m`
        : `${(poi._distance / 1000).toFixed(1)}km`
    }

    return {
      id: index,
      latitude: poi.location.lat,
      longitude: poi.location.lng,
      title: poi.title,
      iconPath: '/images/marker.svg',
      width: 40, height: 40,
      anchor: { x: 0.5, y: 1 },
      callout: {
        content: poi.title,
        color: '#1E1B4B', fontSize: 12,
        borderRadius: 12, bgColor: 'rgba(255,255,255,0.92)',
        display: 'BYCLICK', textAlign: 'center'
      },
      _poiData: {
        ...poi,
        _distanceText: distanceText,
        _aiDescription: this.generateAIDescription(poi, intent)
      }
    }
  })

  this.setData({
    markers,
    aiThinking: false,
    scale: 16  // 搜索成功后放大地图展示点位
  })

  // 缩放视野包含所有标记点
  if (markers.length > 0 && this.mapContext) {
    this.mapContext.includePoints({
      points: markers.map(m => ({
        latitude: m.latitude, longitude: m.longitude
      })),
      padding: [120, 80, 200, 80]
    })
  }
}

6.4 自定义 Marker 与气泡:告别默认大头针

传统地图默认的"红色大头针"千篇一律。City Whisperer 使用自定义 SVG 图标 + 毛玻璃风格气泡:

代码语言:javascript
复制
// Marker 配置
{
  iconPath: '/images/marker.svg',  // 自定义圆形图标
  width: 40, height: 40,
  anchor: { x: 0.5, y: 1 },      // 锚点在底部中心
  callout: {
    content: poi.title,
    color: '#1E1B4B',
    borderRadius: 12,
    bgColor: 'rgba(255,255,255,0.92)',  // 半透明背景
    borderWidth: 1,
    borderColor: 'rgba(99,102,241,0.15)',
    display: 'BYCLICK'
  }
}

自定义 Marker 图标采用圆形设计 + Indigo 主色调,与整体毛玻璃风格统一。气泡使用半透明白色背景 + 细微边框,点击时才显示,避免视觉干扰。

6.5 底部抽屉交互:场所印象,AI 的第一句低语

点击 Marker 后,底部弹出一个半模态抽屉面板,展示地点详情和 AI 生成的"场所印象"文案:

代码语言:javascript
复制
onMarkerTap(e) {
  const markerId = e.detail.markerId || e.markerId
  const marker = this.data.markers[markerId]
  if (!marker || !marker._poiData) return

  this.setData({
    selectedPOI: marker._poiData,
    showDrawer: true
  })
}

AI 文案生成当前使用模板方案,8 种风格随机选择:

代码语言:javascript
复制
generateAIDescription(poi, intent) {
  // ⚠️ 此处未来接入混元大模型 API
  const templates = [
    `${poi.title},一个藏在城市褶皱里的温柔角落。在这里,时间慢下来,只有风和光影在低语。`,
    `${poi.title}像一首未完的诗——走进去,你会发现,城市的低语原来一直在这里。`,
    // ... 更多模板
  ]
  return templates[Math.floor(Math.random() * templates.length)]
}

⚠️ 此处未来接入混元大模型 API——将 POI 信息和用户意图发送至混元 API,生成更精准、更有情感张力的场所描述。

6.6 分享与导航:把好去处传出去

导航使用微信内置的 wx.openLocation,无需额外集成:

代码语言:javascript
复制
onNavigate() {
  const poi = this.data.selectedPOI
  wx.openLocation({
    latitude: poi.location.lat,
    longitude: poi.location.lng,
    name: poi.title,
    address: poi.address || '',
    scale: 16
  })
}

分享使用微信小程序原生的 button open-type="share",确保触发微信标准分享流程。在 onShareAppMessage 中携带 POI 坐标和名称,让接收者打开后自动定位到该地点:

代码语言:javascript
复制
<button class="share-btn" open-type="share">
  <image class="nav-icon share-icon" src="/images/share.svg" />
  <text class="nav-text share-text">分享给朋友</text>
</button>
代码语言:javascript
复制
onShareAppMessage() {
  const poi = this.data.selectedPOI
  if (poi) {
    const lat = poi.location ? poi.location.lat : poi.latitude
    const lng = poi.location ? poi.location.lng : poi.longitude
    return {
      title: `我在 City Whisperer 发现了一个好去处:${poi.title}`,
      path: `/pages/index/index?lat=${lat}&lng=${lng}&name=${encodeURIComponent(poi.title)}`
    }
  }
  return {
    title: 'City Whisperer - 城市低语,发现身边的美好',
    path: '/pages/index/index'
  }
}

onLoad 中解析分享参数,实现从分享卡片直接定位到地点:

代码语言:javascript
复制
onLoad(options) {
  if (options && options.lat && options.lng) {
    const lat = parseFloat(options.lat)
    const lng = parseFloat(options.lng)
    if (!isNaN(lat) && !isNaN(lng)) {
      this.setData({ latitude: lat, longitude: lng, scale: 16 })
    }
  }
  this.initLocation()
}

七、毛玻璃 UI 设计:让地图会"呼吸"

7.1 Glassmorphism 在小程序中的实现

City Whisperer 的 UI 采用毛玻璃(Glassmorphism)风格,核心实现依赖 CSS 的 backdrop-filter: blur() 属性。这种风格的精髓在于:让 UI 元素与底层的地图融为一体,而不是盖在地图上面。

代码语言:javascript
复制
/* 搜索栏 — 浮在地图上方的毛玻璃层 */
.search-bar {
  position: absolute;
  top: 0;
  z-index: 100;
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom: 1rpx solid rgba(99, 102, 241, 0.1);
}

/* 底部抽屉 — 更强模糊的毛玻璃面板 */
.bottom-sheet {
  background: rgba(255, 255, 255, 0.92);
  backdrop-filter: blur(32px);
  -webkit-backdrop-filter: blur(32px);
  border-radius: 48rpx 48rpx 0 0;
}

/* AI 印象卡片 — 紫色调毛玻璃 */
.ai-impression {
  background: rgba(99, 102, 241, 0.08);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1rpx solid rgba(99, 102, 241, 0.1);
}

设计要点:不同层级的 UI 元素使用不同的模糊强度和透明度——搜索栏(72% 透明 + 24px 模糊)→ 抽屉面板(92% 透明 + 32px 模糊)→ AI 卡片(8% 紫色 + 16px 模糊),形成层次分明的视觉纵深。

7.2 原子化 CSS 实践

app.wxss 中定义了一套原子化工具类,确保样式一致性的同时提高开发效率:

代码语言:javascript
复制
/* 全局工具类 */
.flex { display: flex; }
.items-center { align-items: center; }
.glass {
  background: rgba(255, 255, 255, 0.7);
  backdrop-filter: blur(20px);
  border: 1rpx solid rgba(255, 255, 255, 0.3);
}
.shadow-md { box-shadow: 0 8rpx 24rpx rgba(99, 102, 241, 0.12); }

这种方式虽然不如 Tailwind CSS 完整,但在小程序环境下足够灵活,且避免了引入第三方库的体积开销。


八、踩坑与经验

8.1 坐标系那些坑

中国地图开发绕不开的坑就是坐标系。微信小程序的 <map> 组件使用 GCJ-02(国测局坐标,俗称"火星坐标"),而 GPS 设备返回的是 WGS-84 坐标。如果你忘记在 wx.getLocation 中指定 type: 'gcj02',定位点会在地图上偏移几百米——这种 bug 在开发阶段不容易发现,到了真实环境就会暴露。

经验:始终显式指定 type: 'gcj02',不要依赖默认值。

8.2 includePoints 的 padding 陷阱

MapContext.includePoints()padding 参数格式为 [top, right, bottom, left],单位是 px。初期我设置的 padding: [80, 80, 80, 80] 四边等距,但实际场景中顶部有搜索栏遮挡、底部有"搜索结果计数"浮层,导致部分 Marker 被遮挡。

经验:根据实际 UI 布局调整 padding。当前使用 [120, 80, 200, 80],顶部多留空间给搜索栏,底部多留空间给浮层和交互区域。

另外,搜索成功后将 scale 从 12 调到 16 再调用 includePoints,这样 includePoints 在计算视野范围时会在缩放后的比例尺下进行,确保所有点位都清晰可见。

8.3 分享能力的正确打开方式

最初我使用 <view bindtap="onSharePOI"> 来触发分享,然后在 onSharePOI 中调用 wx.showShareMenu。这个方案无法真正触发微信分享面板——showShareMenu 只是声明了分享能力,并不会弹出分享 UI。

正确做法:使用 <button open-type="share">,这是微信小程序唯一能主动触发分享消息的方式。点击后微信会自动调用页面的 onShareAppMessage 方法,获取分享内容。

同时,别忘了重置 <button> 的默认样式:

代码语言:javascript
复制
.share-btn {
  padding: 0;
  margin: 0;
  line-height: normal;
  border: 1rpx solid rgba(99, 102, 241, 0.15) !important;
}
.share-btn::after { border: none; } /* 去除默认边框 */

九、未来展望:混元大模型接入后的想象空间

City Whisperer 当前的 AI 能力是 Mock 实现,但架构上已预留了三个明确的接入点。一旦接入腾讯混元大模型 API,将实现三重升级:

第一重:语音识别升级。当前使用 wx.startRecord 录音后模拟识别结果,接入混元后可实现真正的语音转文字,用户只需对着手机说出需求,无需手动输入。

第二重:意图理解升级。当前的关键词映射方案覆盖了 50+ 场景,但终究是有限的。接入混元后,用户可以说任何自然语言——"我想找个适合写稿的地方,最好有插座,不要太吵"——AI 能理解多维度约束,生成精准的搜索策略。

第三重:情感文案升级。当前的 8 种文案模板虽然风格统一,但缺乏针对性。接入混元后,AI 可以根据 POI 的真实信息(名称、类型、位置、评价等)生成独一无二的场所描述——"这家藏在老街拐角的咖啡馆,窗外的梧桐树已经种了三十年,下午三点的阳光刚好落在靠窗的第二个座位"。

最终愿景:City Whisperer 不仅是一个地图工具,更是一个"城市感知器"——它理解你的情绪,感知城市的性格,在人与城之间架起一座由 AI 编织的桥梁。


十、结语与互动

从一句"找个适合发呆的老建筑"到地图上亮起的标记点,从冰冷的 POI 数据到温暖的"场所印象"——City Whisperer 证明了,当地图服务与 AI 相遇,城市不再只是一张坐标图,而是一本等待翻阅的故事书。

腾讯位置服务提供了稳定、精准的底层能力(定位、搜索、距离计算),让开发者可以把精力集中在"如何让地图更有温度"这件事上。而微信小程序的原生 <map> 组件与 QQMapWX SDK 的无缝衔接,让整个开发过程流畅高效。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、引言:城市有话说,但谁来听?
  • 二、灵感来源:从一次"漫无目的"的周末说起
  • 三、场景痛点:现有地图产品缺了什么?
    • 3.1 关键词困局:用户心里是"感觉",搜索框要的是"品类"
    • 3.2 信息冰冷:POI 只有名称和评分,缺少情感化的场景描述
    • 3.3 交互割裂:搜→看→选→去,每一步都是手动决策
  • 四、项目概览:City Whisperer 是什么?
  • 五、架构设计:从一句话到一个地标
    • 5.1 整体架构图
    • 5.2 技术选型与依赖
    • 5.3 数据流设计
  • 六、功能实现与核心代码
    • 6.1 初始化定位:让地图知道"我在哪"
    • 6.2 AI 意图解析:把"想发呆"翻译成"咖啡馆"
    • 6.3 地图搜索与渲染:从关键词到地图上的光点
    • 6.4 自定义 Marker 与气泡:告别默认大头针
    • 6.5 底部抽屉交互:场所印象,AI 的第一句低语
    • 6.6 分享与导航:把好去处传出去
  • 七、毛玻璃 UI 设计:让地图会"呼吸"
    • 7.1 Glassmorphism 在小程序中的实现
    • 7.2 原子化 CSS 实践
  • 八、踩坑与经验
    • 8.1 坐标系那些坑
    • 8.2 includePoints 的 padding 陷阱
    • 8.3 分享能力的正确打开方式
  • 九、未来展望:混元大模型接入后的想象空间
  • 十、结语与互动
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档