ES配合高德地图JS-API实现地理位置查询

目录

 实现功能点

技术选型

具体实现

Vue3整合高德地图JS API-2.0

 添加商户:前端

添加商户:后端/ES

 查询用户当前地理坐标

 获取附近(指定距离)的商户

总结/测试Demo代码地址


测试概述:用户使用高德地图组件获取商户详细地址和地理坐标(经纬度)存入ES中,然后获取当前用户的地理坐标位置,用户可定义查询半径,用来搜索以用户位置为中心点,指定半径构成搜索圆形区域搜索出包含在圆形区域内的商户(信息包含排序后的距离)。

 实现功能点

  • 商户入驻选址:商户使用地图组件标记来选择地址,获取商户选择的地址信息和地理位置传入后端,后端将信息存储到ES中。

  • 获取用户当前坐标:浏览器地理定位API:navigator.geolocation,需要用户在弹出授权框中赋予位置权限。

  • 显示商户距离:使用ES中的地理查询(圆形过滤)geo_distance类型进行查询附近(指定半径)商户,并且在sort中对于商户远近进行排序。

技术选型

前端:Vite,Vue3.x ,Pinia,ElementPlus

后端:Spring Boot3.x , ElasticSearch7.x

地图相关服务提供:高德地图JS API 2.0

具体实现

Vue3整合高德地图JS API-2.0

1. 安装依赖包:npm i @amap/amap-jsapi-loader ,用于创建地图等相关对象。

2. 配置key密钥 

关于安全问题:一种解决可以将key密钥放到Vite环境变量中,然后组件内引用。

2.1 注册高德开放平台账号,创建应用获取key和密钥。 

访问:lbs.amap.com

注册账号,进入【我的应用】创建服务平台为为Web端的应用。

如果项目需要跑线上,为了保证我们的Key不暴露在代码中,可以将key和密钥信息放到环境变量中,或者使用代理服务器,在代理服务器处配置上密钥。

高德关于key安全说明:准备-入门-教程-地图 JS API 1.4|高德地图API (amap.com)

3. 配置地图参数,初始化显示地图。

<template><!-- 高德地图容器 --><div ref="mapRef" id="map_container"></div>
</template><script setup>
import { load } from '@amap/amap-jsapi-loader';
import { onMounted, ref } from 'vue';// 对应地图渲染的 ref 元素 <div ref="mapRef" />
const mapRef = ref()// 配置key和密钥(取自环境变量)
const mapApiKey = reactive({securityJsCode: import.meta.env.VITE_APP_AMAP_SECURITY_JS_CODE,key: import.meta.env.VITE_APP_AMAP_API_KEY
})// 地图相关对象
const map = ref()
let mapO = {};// 设置地图默认显示的中心点(经度,纬度)
let centerLnglat = ref([109.428071, 24.326442]);const initMapView = async () => {try {// 配置安全密钥window._AMapSecurityConfig = {securityJsCode: mapApiKey.securityJsCode}// 给map赋予操作构造器map.value = await load({key: mapApiKey.key,version: '2.0',  // 指定要加载的 JSAPI 的版本,默认为 1.4.15plugins: ['AMap.Scale'] //需要使用的的插件列表,多个插件用逗号分隔})// 获取地图对象mapO = new map.value.Map(mapRef.value, {viewMode: '3D', // 是否为3D地图模式zoom: 11, // 初始化地图级别center: centerLnglat.value, // 中心地理坐标resizeEnable: true})// 添加点击事件,创建标记,后续用到mapO.on('click', OnPoint)} catch (error) {console.log(error)}
}onMounted(() => {// 执行地图初始化,显示地图initMapView()
})</script>

编写完如上初始化地图代码之后,在地图容器中就能看到地图了。

 添加商户:前端

过程:用户点击右上角“加入商户”,显示表单填写商户信息,以及显示高德地图组件,根据用户填写的省市位置重载地图组件中心点,当用户标记地图中指定位置时,获取标记位置的坐标,然后调用【逆地理编码(坐标->地址)】接口将地理坐标转换为详细地址显示。用户可适当修改详细地址后,提交-新增完毕。 

根据用户输入字段【省】和【市】重载地图中心点(正向地理编码):

// 地理编码(地址->坐标),如下map.value在上方初始化地图代码中有定义和赋值
function selectLnglat() {map.value.plugin('AMap.Geocoder', function () {var geocoder = new map.value.Geocoder({city: info.address  // info.address变量就是省市内容})geocoder.getLocation(info.address, function (status, result) {if (status === 'complete' && result.info === 'OK') {// result中对应详细地理坐标信息,将地图赋予新地理中心点重载地图centerLnglat.value[0] = result.geocodes[0].location.lng;centerLnglat.value[1] = result.geocodes[0].location.lat;initMapView();console.log(result);} else {console.log(status);}})})
}

标记获取坐标,坐标获取地址(逆地理编码)代码:

<script setup>
// 如上初始化地图代码中声明了:mapO.on('click', OnPoint),给地图添加了点击事件。// 地图点击处理器,创建标记点
function OnPoint(e) {// 获取点击位置的经纬度const { lng, lat } = e.lnglat;// 处理标记点显示偏移问题// 如下map.value在上方初始化地图代码中有定义并赋值const pixel = mapO.lngLatToContainer(e.lnglat); // 获取地图的像素坐标const offset = new map.value.Pixel(-2, -12);// 图标的偏移量(根据图标的实际大小来设置)// 计算新的像素坐标const newPixel = new map.value.Pixel(pixel.getX() + offset.getX(), pixel.getY() + offset.getY());// 将新的像素坐标转换为经纬度const newLngLat = mapO.containerToLngLat(newPixel);// 创建标记点const marker = new map.value.Marker({position: newLngLat, // 使用调整后的经纬度位置,标记点图标显示位置title: '标记点', // 鼠标悬停时显示的文字icon: 'http://webapi.amap.com/theme/v1.3/markers/n/mark_b.png', // 自定义标记图标 URL(可选)offset: offset // 偏移量});// 将标记点添加到地图上marker.setMap(mapO);// 使用经纬度查询出详细地址(逆地理查询)getDetailAddress(newLngLat.lng, newLngLat.lat);// 将地理坐标存储,先纬度后经度,因为es存储String类型的地理坐标与WKT相反// 后续将此坐标存在es中,作为商户的地理坐标位置进行距离搜索info.setLatlng(newLngLat.lat+","+newLngLat.lng);// 输出经纬度坐标console.log("点击经纬度:", lng, lat);console.log("调整后的经纬度:", newLngLat.lat, newLngLat.lng);
}   </script>

其中有标记点显示时偏移的调整处理,如果在实际开发中发现偏移可适当调整数值。 

逆地理编码(根据用户的标记点,推出详细地址) :

// 根据经纬度坐标获取详细地址(逆地理编码(坐标->地址))
function getDetailAddress(lng, lat) {map.value.plugin('AMap.Geocoder', function () {var geocoder = new map.value.Geocoder({city: info.address  // 这个值填写:城市名称,或城市代码都可})// 构造坐标参数数组let agrsPotin = [lng, lat];geocoder.getAddress(agrsPotin, function (status, result) {if (status === 'complete' && result.info === 'OK') {// result.regeocode.formattedAddress就是推出的地址// 将详细地址存入pinia中,在表单组件中取出展示info.setDetailAddress(result.regeocode.formattedAddress)} else {console.log("获取地址失败,", status);}})})
}

添加商户:后端/ES

创建ES的库索引,引入依赖并创建新增接口,接收前端传入的商户信息参数,将数据存储ES中。

1.创建ES的库索引结构。

PUT /hotels
{"mappings": {"properties": {"id":{"type": "keyword"},"picture": { "type": "keyword" },"name": { "type": "text","analyzer": "ik_smart"},"score": { "type": "float"},"distance": { "type": "float"},"desc": { "type": "text" },"newPrice": { "type": "float" },"oldPrice": { "type": "float" },"saleMonth": { "type": "integer" },"detailAddress":{"type": "text"},"location": { "type": "geo_point" }}}
}

2.SpringBoot引入ES依赖

推荐引入8以下的,8以上版本Java客户端变化太大,API中文文档不全面。

<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>7.12.1</version>
</dependency>

3.编写对应实体类,和新增接口

// 将ES客户端加入Ioc容器
@Configuration
public class ElasticsearchConfig {@Value("${esLInfo}") // 连接es信息定义在.yml配置文件中,如http://ip:9200private String esLInfo;@Beanpublic RestHighLevelClient createEsClient(){return new RestHighLevelClient(RestClient.builder(HttpHost.create(esLInfo)));}
}
// 实体类,对应es库索引
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Hotel {private String id;private String picture;private String name;private Float score;private String detailAddress;private String desc;private Float newPrice;private Float oldPrice;private Integer saleMonth;private String location; // geo_point类型,格式为 "lat,lon"private String detailAddress;}
// 新增接口
@PostMapping
public String addHandle(@RequestBody Hotel hotel) throws IOException {// 构建新增请求IndexRequest request = new IndexRequest("hotels").id(hotel.getId());// 准备数据request.source(gson.toJson(hotel), XContentType.JSON);IndexResponse index = client.index(request, RequestOptions.DEFAULT);log.info(index+"===");return "新增成功";
}

 查询用户当前地理坐标

使用浏览器内置函数:navigator.geolocation,实现当前用户地理坐标的查询。

弹出授权框,用户授权后可获取。

<script setup>// 当前用户的地理坐标const position = ref(null);function getUserLocation(){if (navigator.geolocation) {navigator.geolocation.getCurrentPosition((pos) => {position.value = {lat: pos.coords.latitude,lng: pos.coords.longitude,};// 根据用户地理坐标获取周围指定距离的酒店getNearHotel(position.value);// console.log("当前用户地理位置:",position.value);},(err) => {error.value = err.message;});} else {error.value = '浏览器不支持';}}
</script>

 获取附近(指定距离)的商户

 1. 编写后端接口,根据用户当前位置和想要查询的半径获取包含在圆形区域的内商户信息。

// searchVo参数封装定义三个参数:经度,纬度,半径,前端传入值
@PostMapping("/searchByLocation")
public List<Map<String, Object>> searchHotelsByLocation(@RequestBody SearchVo searchVo) throws IOException {// 构建查询请求SearchRequest searchRequest = new SearchRequest("hotels");// 构建查询DSLSearchSourceBuilder sourceBuilder = new SearchSourceBuilder();BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();boolQuery.filter(QueryBuilders.geoDistanceQuery("location").point(searchVo.getLat(), searchVo.getLon()).distance(searchVo.getRadiusKm(), DistanceUnit.KILOMETERS));sourceBuilder.query(boolQuery);sourceBuilder.sort(SortBuilders.geoDistanceSort("location", searchVo.getLat(), searchVo.getLon()).order(SortOrder.ASC) // 实现升序排序,距离越近越靠前.unit(DistanceUnit.KILOMETERS));searchRequest.source(sourceBuilder);// 执行查询SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);SearchHits hits = searchResponse.getHits();List<Map<String, Object>> hotels = new ArrayList<>();for (SearchHit hit : hits) {Map<String, Object> hotel = hit.getSourceAsMap();// 可以获取距离信息(如果需要显示距离)double distance = (double) hit.getSortValues()[0];hotel.put("distance_km", distance);hotels.add(hotel);}return hotels;
}

2. 前端传入用户坐标和搜索半径获取商家数据展示。

// 使用圆形过滤,过滤出当前用户指定距离的附近酒店
async function getNearHotel(userLocation){// 定义搜索半径,实际可绑定表单组件让用户选择。let radiusKm = 3.0;// 构造查询参数Volet searchArgs = {lat:userLocation.lat,lon:userLocation.lng,radiusKm:radiusKm}const res = await axios.post("http://localhost:9009/es/searchByLocation",searchArgs);console.log("查询到"+ radiusKm + "公里内,存在"+res.data.length+"家酒店/旅社");// 处理距离小数点,保留逗号后两位for(let item of res.data){item.distance_km = item.distance_km.toFixed(2);}// 赋值,显示搜索到的酒店cardsD.value = res.data
}

总结/测试Demo代码地址

至此,使用高德地图API配合ES完成地理位置查询功能点,测试完毕。

测试Demo完整代码地址gitee.com/maohe101/map-es-demo

更多高德地图的JS API可查看文档:概述-地图 JS API 2.0|高德地图API (amap.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/52088.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot2:学SpringBoot前的知识准备-用IDEA创建传统的webapp工程,并整合SpringMVC

1、IDEA创建工程 基于Maven模板创建的SpringMVC工程 工程创建好后&#xff0c;只有webapp目录 这里&#xff0c;我们需要手动创建java目录和resources配置文件目录 创建好后&#xff0c;配置下目录属性 最终结构 至此&#xff0c;工程就创建好了 2、配置Tomcat 参考&am…

论文笔记:2023顶会SIGIR - Strategy-aware Bundle Recommender System

论文笔记&#xff1a;2023顶会SIGIR - Strategy-aware Bundle Recommender System

【Python报错已解决】`Provisional headers are shown Learn more`

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述&#xff1a;1.1 报错示例&#xff1a;1.2 报错分析&#xff1a;1.3 解决思路&#xff…

145. 利用 Redis Bitmap实践: 用户签到统计

文章目录 一、Redis Bitmap简介二、Bitmap 的主要应用三、Go使用Redis实现签到统计用户签到查询用户签到状态统计今年累计签到天数统计当月的签到情况 总结 在现代应用程序中&#xff0c;用户签到是一个常见的功能。我们通常使用 MySQL 数据库来存储用户的签到记录。然而&#…

智能家居系统(基于STM32F103C8T6标准库+FreeRTOS+Qt串口开发实现)

视频演示&#xff1a;基于STM32F103C8T6标准库FreeRTOSQt串口开发实现的智能家居项目_哔哩哔哩_bilibili 基于STM32F103C8T6标准库FreeRTOSQt串口开发实现的智能家居项目: https://pan.baidu.com/s/1f41gAfOOnlcQoKoMx3o84A?pwd6j2g 提取码: 6j2g 注&#xff1a;本项目为学习完…

Windows I/O系统

硬件存储体系 寄存器 处理器内部定义的存储体&#xff0c;它们除了存储功能&#xff0c;往往还兼有其他的能力&#xff0c;比如参与运算&#xff0c;地址解析&#xff0c;指示处理器的状态&#xff0c;等等。寄存器是由处理器内部专门的触发器电路实现的&#xff0c;处理器往…

2024年高教杯国赛(B题)数学建模竞赛解题思路|完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

基于物联网的低成本便携式传感器节点用于火灾和空气污染的检测与报警

目录 摘要 引言 材料和方法 传感器节点 IoT 微控制器 颗粒物传感器 环境和气体传感器 MQTT代理 Node-Red监控平台 系统结构 数据存储 工作描述 实验结果 讨论 结论 致谢 参考文献 这篇论文的标题是《Low-cost IoT-based Portable Sensor Node for Fire and Air…

数据要素大市场打开新局面——数字经济背景下中国特色的数据产权三权分置

前言 数字经济飞速发展的大背景下&#xff0c;无论是数据要素大市场还是数据资产入表的发展和落地&#xff0c;都需要以数据产权基本制度为保障。对数据产权给与恰当的权利定位&#xff0c;有助于让数据成为创造和捕获价值的新经济资源。数据的价值在于使用&#xff0c;而传统…

链表leetcode-1

目录 1.常用技巧 1.1引入虚拟头结点 1.2对于变量&#xff0c;不要太吝啬&#xff0c;可以多定义&#xff0c;方便处理 1.3快慢双指针 2.例题 2.1两数相加 2.2两两交换链表中的节点 2.3重排链表 2.4合并K个升序链表 2.5K个一组翻转链表 1.常用技巧 1.1引入虚拟头结点 可…

场景是人工智能第四要素,是垂直领域人工智能的第一要素。

"场景是人工智能的第四要素&#xff0c;与数据、算力、算法同等重要。"拿着技术找场景&#xff0c;还是拿着场景找技术&#xff1f;这个锤子和钉子的问题&#xff0c;一直困扰着各家AI大厂。从近5年的实践来看&#xff0c;拿着场景找技术是更为稳健的&#xff0c;否则…

哪款宠物空气净化器能更好的清理浮毛?希喂、352、IAM测评分享

家里这三只可爱的小猫咪&#xff0c;已然成为了我们生活中不可或缺的家庭成员&#xff0c;陪伴我们度过了说长不长说短不短的五年时光。时常庆幸自己当年选择养它们&#xff0c;在我失落的时候总能给我安慰&#xff0c;治愈我多时。 但这个温馨的背后也有一点小烦恼&#xff0…

ES6语法详解

以下是ES6常用的一些语法和特性&#xff1a; 声明变量的关键字变化&#xff1a;使用let和const、var来声明变量。 箭头函数&#xff1a;使用箭头&#xff08;>&#xff09;定义函数&#xff0c;简化函数的写法。 模板字符串&#xff1a;使用反引号&#xff08;&#xff0…

【java入门】关键字、标识符与变量初识

&#x1f680; 个人简介&#xff1a;某大型国企资深软件开发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…

Docker Elasticsearch安装ik分词插件教程

本章教程在通过Docker 安装Elasticsearch,并安装ik分词插件。本文的重点是安装ik分词插件。 一、安装Elasticsearch 安装教程以前写过,参考:https://blog.csdn.net/qq_19309473/article/details/140725121 安装之后,通过http://ip:9200,可以访问,就表示安装成功。 二、安装…

Linux终端简单配置(Vim、oh-my-zsh和Terminator)

文章目录 0. 概述1. 完整Vim配置2. Vim配置方案解释2.1 状态行与配色方案2.2 文件管理与缓存设置2.3 搜索与导航优化2.4 缩进与格式化设置2.5 粘贴模式快捷切换2.6 文件编码与格式2.7 性能优化 3. 安装 Oh My Zsh 及配置3.1 安装 Oh My Zsh3.2 Oh My Zsh 配置 3. Terminator终端…

vscode +STM32 VS CODE EXTENSION

stm32 vs code extersion 1.0.0版本可以直接导入cubeide的工程&#xff0c;之后版本不可以&#xff0c;所以为了省事&#xff0c;使用stm32 vs code extersion 1.0.0插件。 安装完stm32 vs code extersion插件&#xff0c;会默认把相关插件一起安装。但是需要手动安装Ninja&am…

交叉编译概念

交叉编译概念 目录 交叉编译概念1. 什么是交叉编译2. 交叉编译的作用3. 交叉编译器4. 交叉编译工具链5. 交叉编译的一般步骤6. 交叉编译实例 1. 什么是交叉编译 交叉编译是指在一个平台上编译代码&#xff0c;使其能够在另一个不同的平台上运行的过程。这种编译方式通常用于开…

Android12——Launcher3文件夹布局修改调整

文章声明&#xff1a;本文是笔者参考良心大佬作品后结合实际需求进行相应的定制&#xff0c;本篇主要是笔者记录一次解析bug笔记&#xff0c;文中可能会引用大佬文章中的部分图片在此声明&#xff0c;并非盈利目的&#xff0c;如涉嫌侵权请私信&#xff0c;谢谢&#xff01; 大…

什么是函数调用约定?

目录 前言 一、函数调用约定的主要内容 二、常见的函数调用约定 1. __cdecl&#xff08;C Declaration&#xff09; 2. __stdcall&#xff08;Standard Call&#xff09; 3. __fastcall&#xff08;Fast Call&#xff09; 4. __thiscall&#xff08;This Call&#xff0…