【腾讯位置服务开发者征文大赛】当 AI 听懂城市的低语——用腾讯位置服务打造智能情感地图小程序
目录一、引言城市有话说但谁来听二、灵感来源从一次漫无目的的周末说起三、场景痛点现有地图产品缺了什么四、项目概览City Whisperer 是什么五、架构设计从一句话到一个地标六、功能实现与核心代码七、毛玻璃 UI 设计让地图会呼吸八、踩坑与经验九、最后City Whisperer城市低语小程序演示一、引言城市有话说但谁来听每一座城市都在低语——老街的梧桐树下藏着只有下午三点才透进来的光巷尾的咖啡馆里有最适合发呆的角落和旧时光的味道。但这些信息地图听不见。打开任何一个地图 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城市低语是一款基于腾讯位置服务的智能地图探索微信小程序。核心交互极其简单你说一句话在搜索栏输入或语音说出找个安静的露台“我想喝杯咖啡发发呆”AI 听懂你自动解析你的意图将情绪化描述翻译为地图搜索关键词地图标记调用腾讯地图 API 周边搜索在地图上标记符合情境的地点城市低语点击地点AI 生成一段富有情感的场所印象文案出发探索一键导航或分享给朋友一起出发与传统地图搜索的对比维度传统地图City Whisperer输入方式品类关键词自然语言 / 语音搜索逻辑精确匹配AI 意图理解信息呈现名称 评分 距离名称 情感文案 导航交互体验线性决策沉浸式引导情感连接无场所印象AI 文案五、架构设计从一句话到一个地标5.1 整体架构图整个系统分为五层从用户输入到最终交互反馈形成完整闭环用户输入层语音输入和文本输入的统一入口AI 意图解析层将自然语言翻译为结构化搜索参数腾讯位置服务层基于 QQMapWX SDK 的定位与搜索数据处理层搜索结果处理、距离计算、AI 文案生成交互反馈层地图渲染、底部抽屉、导航与分享5.2 技术选型与依赖模块技术方案说明地图组件微信小程序原生map无需额外组件性能最优位置服务QQMapWX SDKJavaScript SDK搜索、定位、距离计算坐标系统GCJ-02国测局坐标与微信地图一致wx.getLocation直接返回AI 能力混元大模型 API 接口意图解析、文案生成UI 样式原子化 CSS Glassmorphism毛玻璃风格原子化工具类5.3 数据流设计用户输入 找个适合发呆的老建筑 ↓ 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 坐标并创建地图上下文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.mapContextwx.createMapContext(cityMap,this)wx.hideLoading()},fail:(){wx.hideLoading()wx.showToast({title:定位失败使用默认位置,icon:none})}})}关键点type: gcj02必须显式指定否则默认返回 WGS-84 坐标与地图组件的 GCJ-02 坐标系不匹配会导致定位点偏移。6.2 AI 意图解析把想发呆翻译成咖啡馆这是 City Whisperer 最核心的创新点。后端接入的AI是混元大模型 APIparseIntent(userInput){constinputuserInput.toLowerCase()// 将 input 发送至后端再到混元 API获取结构化意图包括关键词、排序方式、情感标签、文案风格等维度。return{keyword,orderby}}6.3 地图搜索与渲染从关键词到地图上的光点意图解析后调用腾讯位置服务的search接口进行周边搜索handleUserQuery(userInput){this.setData({aiThinking:true})constintentthis.parseIntent(userInput)const{userLat,userLng}this.dataconstlocationStruserLatuserLng?${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.status0res.datares.data.length0){this.processSearchResults(res.data,intent)}else{this.setData({aiThinking:false})wx.showToast({title:没有找到相关地点,icon:none})}}})}搜索成功后将结果处理为地图markers数组并将地图从初始的scale: 12大视野放大到scale: 16街道级别让用户清晰看到搜索点位processSearchResults(poiList,intent){constmarkerspoiList.map((poi,index){// 距离格式化letdistanceText未知if(poi._distance!undefined){distanceTextpoi._distance1000?${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.length0this.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 图标 毛玻璃风格气泡// 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 生成的场所印象文案onMarkerTap(e){constmarkerIde.detail.markerId||e.markerIdconstmarkerthis.data.markers[markerId]if(!marker||!marker._poiData)returnthis.setData({selectedPOI:marker._poiData,showDrawer:true})}AI 文案生成当前使用模板方案8 种风格随机选择generateAIDescription(poi,intent){// 将 POI 信息和用户意图发送至后端再到混元 API生成更精准、更有情感张力的场所描述。returndescription}6.6 分享与导航把好去处传出去导航使用微信内置的wx.openLocation无需额外集成onNavigate(){constpoithis.data.selectedPOI wx.openLocation({latitude:poi.location.lat,longitude:poi.location.lng,name:poi.title,address:poi.address||,scale:16})}分享使用微信小程序原生的button open-typeshare确保触发微信标准分享流程。在onShareAppMessage中携带 POI 坐标和名称让接收者打开后自动定位到该地点buttonclassshare-btnopen-typeshareimageclassnav-icon share-iconsrc/images/share.svg/textclassnav-text share-text分享给朋友/text/buttononShareAppMessage(){constpoithis.data.selectedPOIif(poi){constlatpoi.location?poi.location.lat:poi.latitudeconstlngpoi.location?poi.location.lng:poi.longitudereturn{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中解析分享参数实现从分享卡片直接定位到地点onLoad(options){if(optionsoptions.latoptions.lng){constlatparseFloat(options.lat)constlngparseFloat(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 元素与底层的地图融为一体而不是盖在地图上面。/* 搜索栏 — 浮在地图上方的毛玻璃层 */.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 solidrgba(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 solidrgba(99,102,241,0.1);}设计要点不同层级的 UI 元素使用不同的模糊强度和透明度——搜索栏72% 透明 24px 模糊→ 抽屉面板92% 透明 32px 模糊→ AI 卡片8% 紫色 16px 模糊形成层次分明的视觉纵深。7.2 原子化 CSS 实践在app.wxss中定义了一套原子化工具类确保样式一致性的同时提高开发效率/* 全局工具类 */.flex{display:flex;}.items-center{align-items:center;}.glass{background:rgba(255,255,255,0.7);backdrop-filter:blur(20px);border:1rpx solidrgba(255,255,255,0.3);}.shadow-md{box-shadow:0 8rpx 24rpxrgba(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 bindtaponSharePOI来触发分享然后在onSharePOI中调用wx.showShareMenu。这个方案无法真正触发微信分享面板——showShareMenu只是声明了分享能力并不会弹出分享 UI。正确做法使用button open-typeshare这是微信小程序唯一能主动触发分享消息的方式。点击后微信会自动调用页面的onShareAppMessage方法获取分享内容。同时别忘了重置button的默认样式.share-btn{padding:0;margin:0;line-height:normal;border:1rpx solidrgba(99,102,241,0.15)!important;}.share-btn::after{border:none;}/* 去除默认边框 */九、最后从一句找个适合发呆的老建筑到地图上亮起的标记点从冰冷的 POI 数据到温暖的场所印象——City Whisperer 证明了当地图服务与 AI 相遇城市不再只是一张坐标图而是一本等待翻阅的故事书。腾讯位置服务提供了稳定、精准的底层能力定位、搜索、距离计算让开发者可以把精力集中在如何让地图更有温度这件事上。而微信小程序的原生map组件与 QQMapWX SDK 的无缝衔接让整个开发过程流畅高效。腾讯云语音识别APIhttps://cloud.tencent.com/document/product/1093/52097混元大模型APIhttps://cloud.tencent.com/document/product/1729/111007如果这篇文章对你有启发欢迎点赞、评论、转发你最想用 City Whisperer 搜索什么样的地方欢迎在评论区告诉我——也许下一句城市低语就是为你而写。有任何技术问题或想法也欢迎留言交流我们一起让地图听懂更多人的心声。