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

2024-08-29 21:36

本文主要是介绍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)

这篇关于ES配合高德地图JS-API实现地理位置查询的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1118942

相关文章

JS常用组件收集

收集了一些平时遇到的前端比较优秀的组件,方便以后开发的时候查找!!! 函数工具: Lodash 页面固定: stickUp、jQuery.Pin 轮播: unslider、swiper 开关: switch 复选框: icheck 气泡: grumble 隐藏元素: Headroom

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount