第六篇:POI搜索功能
本篇教程将学习如何使用高德地图的POI(兴趣点)搜索功能,包括关键字搜索、周边搜索、ID搜索等。
学习目标
- 掌握POI关键字搜索
- 实现周边搜索功能
- 处理搜索结果并在地图上展示
- 理解输入提示(InputTips)功能
1. POI搜索类型
| 搜索类型 | 适用场景 | 示例 |
|---|---|---|
| 关键字搜索 | 按名称搜索某类POI | 搜索"星巴克" |
| 周边搜索 | 搜索指定位置附近的POI | 当前位置3公里内的餐厅 |
| 多边形搜索 | 搜索指定区域内的POI | 某个行政区内的银行 |
| ID搜索 | 按POI ID查询详情 | 获取某个POI的详细信息 |
2. 核心类说明
| 类名 | 说明 |
|---|---|
PoiSearch |
POI搜索核心类 |
PoiQuery |
搜索条件配置 |
PoiResult |
搜索结果 |
PoiItem |
单个POI信息 |
OnPoiSearchListener |
搜索回调接口 |
3. 完整代码示例
创建文件 entry/src/main/ets/pages/Demo05_PoiSearch.ets:
typescript
import {
AMap,
MapView,
MapViewComponent,
MapViewManager,
MapViewCreateCallback,
CameraUpdateFactory,
LatLng,
Marker,
MarkerOptions,
BitmapDescriptorFactory,
LatLngBounds
} from '@amap/amap_lbs_map3d';
import {
PoiSearch,
PoiQuery,
PoiResult,
PoiItem,
OnPoiSearchListener,
AMapException,
LatLonPoint,
PoiSearchBound
} from '@amap/amap_lbs_search';
import { inputMethod } from '@kit.IMEKit';
const MAP_VIEW_NAME = 'PoiSearchDemo';
/**
* POI搜索类型
*/
type SearchType = 'keyword' | 'around';
@Entry
@Component
struct Demo05_PoiSearch {
private mapView: MapView | undefined = undefined;
private aMap: AMap | undefined = undefined;
private poiSearch: PoiSearch | undefined = undefined;
private poiMarkers: Marker[] = [];
@State isMapReady: boolean = false;
@State keyword: string = '餐厅';
@State city: string = '北京';
@State searchType: SearchType = 'keyword';
@State isSearching: boolean = false;
@State searchResult: string = '';
@State poiList: PoiItem[] = [];
@State currentPage: number = 0;
@State totalPages: number = 0;
// 周边搜索中心点
private searchCenter: LatLonPoint = new LatLonPoint(39.909187, 116.397451);
private searchRadius: number = 3000; // 3公里
/**
* POI搜索回调
*/
private poiSearchListener: OnPoiSearchListener = {
onPoiSearched: (result: PoiResult | undefined, errorCode: number) => {
this.isSearching = false;
if (errorCode === AMapException.CODE_AMAP_SUCCESS) {
if (result) {
const pois = result.getPois();
if (pois && pois.length > 0) {
this.poiList = pois as PoiItem[];
this.currentPage = result.getQuery()?.getPageNum() || 0;
this.totalPages = result.getPageCount();
this.searchResult = `找到 ${pois.length} 个结果 (第${this.currentPage + 1}/${this.totalPages}页)`;
// 清除旧标记并添加新标记
this.clearPoiMarkers();
this.addPoiMarkersToMap();
// 调整地图视野
this.fitMapBounds();
} else {
this.searchResult = '未找到相关结果';
this.poiList = [];
this.clearPoiMarkers();
}
}
} else {
this.searchResult = `搜索失败: 错误码 ${errorCode}`;
console.error('[PoiSearch] Search failed:', errorCode);
}
},
onPoiItemSearched: (poiItem: PoiItem | undefined, errorCode: number) => {
// ID搜索回调
if (errorCode === AMapException.CODE_AMAP_SUCCESS && poiItem) {
console.info('[PoiSearch] POI detail:', poiItem.getTitle());
}
}
};
private mapViewCreateCallback: MapViewCreateCallback =
(mapview: MapView | undefined, mapViewName: string | undefined) => {
if (!mapview || mapViewName !== MAP_VIEW_NAME) return;
this.mapView = mapview;
this.mapView.onCreate();
this.mapView.getMapAsync((map: AMap) => {
this.aMap = map;
this.isMapReady = true;
// 移动到北京
const beijing = new LatLng(39.909187, 116.397451);
map.moveCamera(CameraUpdateFactory.newLatLngZoom(beijing, 13));
// 启用控件
map.getUiSettings()?.setZoomControlsEnabled(true);
// 地图点击设置搜索中心
map.setOnMapClickListener((point: LatLng) => {
if (this.searchType === 'around') {
this.searchCenter = new LatLonPoint(point.latitude, point.longitude);
this.searchResult = `已设置搜索中心: ${point.latitude.toFixed(4)}, ${point.longitude.toFixed(4)}`;
}
});
});
};
/**
* 执行关键字搜索
*/
private doKeywordSearch(): void {
if (!this.poiSearch || !this.keyword.trim()) {
this.searchResult = '请输入搜索关键字';
return;
}
// 收起键盘
inputMethod.getController().stopInputSession();
this.isSearching = true;
this.searchResult = '搜索中...';
// 创建搜索条件
const query = new PoiQuery(this.keyword, '', this.city);
query.setPageSize(20); // 每页数量
query.setPageNum(0); // 页码(从0开始)
// 设置查询并执行搜索
this.poiSearch.setQuery(query);
this.poiSearch.searchPOIAsyn();
console.info('[PoiSearch] Keyword search:', this.keyword, 'in', this.city);
}
/**
* 执行周边搜索
*/
private doAroundSearch(): void {
if (!this.poiSearch || !this.keyword.trim()) {
this.searchResult = '请输入搜索关键字';
return;
}
inputMethod.getController().stopInputSession();
this.isSearching = true;
this.searchResult = '搜索中...';
// 创建搜索条件
const query = new PoiQuery(this.keyword, '', '');
query.setPageSize(20);
query.setPageNum(0);
// 设置周边搜索范围
const bound = new PoiSearchBound(this.searchCenter, this.searchRadius);
query.setBound(bound);
this.poiSearch.setQuery(query);
this.poiSearch.searchPOIAsyn();
console.info('[PoiSearch] Around search:', this.keyword, 'radius:', this.searchRadius);
}
/**
* 加载下一页
*/
private loadNextPage(): void {
if (!this.poiSearch || this.currentPage >= this.totalPages - 1) {
return;
}
this.isSearching = true;
this.currentPage++;
const query = this.poiSearch.getQuery();
if (query) {
query.setPageNum(this.currentPage);
this.poiSearch.setQuery(query);
this.poiSearch.searchPOIAsyn();
}
}
/**
* 在地图上添加POI标记
*/
private addPoiMarkersToMap(): void {
if (!this.aMap) return;
for (let i = 0; i < this.poiList.length; i++) {
const poi = this.poiList[i];
const latLonPoint = poi.getLatLonPoint();
if (latLonPoint) {
const options = new MarkerOptions();
options.setPosition(new LatLng(latLonPoint.getLatitude(), latLonPoint.getLongitude()));
options.setTitle(poi.getTitle() || '');
options.setSnippet(poi.getSnippet() || '');
// 使用不同颜色标记
const hue = i < 5 ? BitmapDescriptorFactory.HUE_RED : BitmapDescriptorFactory.HUE_BLUE;
options.setIcon(BitmapDescriptorFactory.defaultMarker(hue));
options.setZIndex(10);
const marker = this.aMap.addMarker(options);
if (marker) {
this.poiMarkers.push(marker);
}
}
}
}
/**
* 清除POI标记
*/
private clearPoiMarkers(): void {
for (const marker of this.poiMarkers) {
marker.remove();
}
this.poiMarkers = [];
}
/**
* 调整地图视野以显示所有标记
*/
private fitMapBounds(): void {
if (!this.aMap || this.poiList.length === 0) return;
// 计算边界
let minLat = 90, maxLat = -90, minLng = 180, maxLng = -180;
for (const poi of this.poiList) {
const point = poi.getLatLonPoint();
if (point) {
const lat = point.getLatitude();
const lng = point.getLongitude();
minLat = Math.min(minLat, lat);
maxLat = Math.max(maxLat, lat);
minLng = Math.min(minLng, lng);
maxLng = Math.max(maxLng, lng);
}
}
if (minLat < maxLat && minLng < maxLng) {
const southwest = new LatLng(minLat, minLng);
const northeast = new LatLng(maxLat, maxLng);
const bounds = new LatLngBounds(southwest, northeast);
this.aMap.animateCamera(
CameraUpdateFactory.newLatLngBounds(bounds, 50),
500
);
}
}
/**
* 点击POI项,移动到该位置
*/
private onPoiItemClick(poi: PoiItem): void {
const point = poi.getLatLonPoint();
if (point && this.aMap) {
const latLng = new LatLng(point.getLatitude(), point.getLongitude());
this.aMap.animateCamera(
CameraUpdateFactory.newLatLngZoom(latLng, 16),
500
);
// 显示对应标记的InfoWindow
for (const marker of this.poiMarkers) {
const pos = marker.getPosition();
if (Math.abs(pos.latitude - point.getLatitude()) < 0.0001 &&
Math.abs(pos.longitude - point.getLongitude()) < 0.0001) {
marker.showInfoWindow();
break;
}
}
}
}
aboutToAppear(): void {
MapViewManager.getInstance()
.registerMapViewCreatedCallback(this.mapViewCreateCallback);
// 初始化POI搜索
const context = getContext(this);
this.poiSearch = new PoiSearch(context, undefined);
this.poiSearch.setOnPoiSearchListener(this.poiSearchListener);
}
aboutToDisappear(): void {
this.clearPoiMarkers();
MapViewManager.getInstance()
.unregisterMapViewCreatedCallback(this.mapViewCreateCallback);
if (this.mapView) {
this.mapView.onDestroy();
this.mapView = undefined;
this.aMap = undefined;
}
}
build() {
Column() {
// 标题栏
Row() {
Text('POI搜索')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
}
.width('100%')
.height(50)
.padding({ left: 16 })
.backgroundColor('#9C27B0')
// 搜索栏
Column() {
// 搜索类型选择
Row() {
Row() {
Radio({ value: 'keyword', group: 'searchType' })
.checked(this.searchType === 'keyword')
.onChange((isChecked: boolean) => {
if (isChecked) this.searchType = 'keyword';
})
Text('关键字搜索')
.fontSize(14)
.margin({ left: 4 })
}
.margin({ right: 20 })
Row() {
Radio({ value: 'around', group: 'searchType' })
.checked(this.searchType === 'around')
.onChange((isChecked: boolean) => {
if (isChecked) this.searchType = 'around';
})
Text('周边搜索')
.fontSize(14)
.margin({ left: 4 })
}
}
.width('100%')
.margin({ bottom: 8 })
// 搜索输入
Row() {
TextInput({ text: this.keyword, placeholder: '输入关键字' })
.layoutWeight(1)
.height(40)
.onChange((value: string) => { this.keyword = value; })
if (this.searchType === 'keyword') {
TextInput({ text: this.city, placeholder: '城市' })
.width(80)
.height(40)
.margin({ left: 8 })
.onChange((value: string) => { this.city = value; })
}
Button('搜索')
.height(40)
.margin({ left: 8 })
.enabled(!this.isSearching)
.onClick(() => {
if (this.searchType === 'keyword') {
this.doKeywordSearch();
} else {
this.doAroundSearch();
}
})
}
.width('100%')
// 搜索结果提示
Text(this.searchResult)
.fontSize(12)
.fontColor('#666')
.margin({ top: 8 })
.width('100%')
}
.padding(12)
.backgroundColor('#f5f5f5')
// 地图和结果列表
Row() {
// 地图
Stack() {
MapViewComponent({ mapViewName: MAP_VIEW_NAME })
.width('100%')
.height('100%')
if (this.searchType === 'around') {
Text('点击地图设置搜索中心')
.fontSize(11)
.fontColor('#fff')
.backgroundColor('rgba(0,0,0,0.6)')
.padding(6)
.borderRadius(4)
.position({ x: 10, y: 10 })
}
}
.layoutWeight(1)
.height('100%')
// 结果列表
if (this.poiList.length > 0) {
Column() {
Text('搜索结果')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.padding(8)
.width('100%')
.backgroundColor('#e0e0e0')
List() {
ForEach(this.poiList, (poi: PoiItem, index: number) => {
ListItem() {
Column() {
Text(`${index + 1}. ${poi.getTitle() || '未知'}`)
.fontSize(13)
.fontColor('#333')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(poi.getSnippet() || '')
.fontSize(11)
.fontColor('#999')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.margin({ top: 2 })
}
.width('100%')
.alignItems(HorizontalAlign.Start)
.padding(8)
}
.onClick(() => this.onPoiItemClick(poi))
})
}
.layoutWeight(1)
.divider({ strokeWidth: 1, color: '#eee' })
// 加载更多按钮
if (this.currentPage < this.totalPages - 1) {
Button('加载更多')
.width('100%')
.height(36)
.fontSize(12)
.enabled(!this.isSearching)
.onClick(() => this.loadNextPage())
}
}
.width(150)
.height('100%')
.backgroundColor(Color.White)
}
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
}
}
4. PoiItem 常用方法
typescript
interface PoiItem {
// 获取POI名称
getTitle(): string;
// 获取POI地址
getSnippet(): string;
// 获取POI ID
getPoiId(): string;
// 获取坐标
getLatLonPoint(): LatLonPoint;
// 获取电话
getTel(): string;
// 获取POI类型
getTypeDes(): string;
// 获取距离(周边搜索时有效)
getDistance(): number;
// 获取行政区编码
getAdCode(): string;
// 获取省份
getProvinceName(): string;
// 获取城市
getCityName(): string;
// 获取区县
getAdName(): string;
}
5. 搜索条件配置
5.1 PoiQuery 常用设置
typescript
const query = new PoiQuery(keyword, type, city);
// 设置每页结果数量(1-50)
query.setPageSize(20);
// 设置页码(从0开始)
query.setPageNum(0);
// 设置搜索类型(可选,如"餐饮服务|购物服务")
query.setTypes('餐饮服务');
// 设置是否返回扩展信息(all或base)
query.setExtensions('all');
// 设置周边搜索范围
query.setBound(new PoiSearchBound(centerPoint, radius));
// 设置返回语言
query.setQueryLanguage(ServiceSettings.CHINESE);
5.2 常用POI类型
| 类型编码 | 说明 |
|---|---|
| 餐饮服务 | 餐厅、快餐、咖啡厅等 |
| 购物服务 | 商场、超市、便利店等 |
| 生活服务 | 银行、邮局、洗衣店等 |
| 医疗保健服务 | 医院、诊所、药店等 |
| 住宿服务 | 酒店、宾馆、民宿等 |
| 交通设施服务 | 停车场、加油站、公交站等 |
6. 输入提示(InputTips)
用于实现搜索框的自动补全功能:
typescript
import { Inputtips, InputtipsQuery, OnInputtipsListener, Tip } from '@amap/amap_lbs_search';
// 创建InputTips
const inputTips = new Inputtips(context);
// 设置回调
inputTips.setInputtipsListener({
onGetInputtips: (tips: Tip[], errorCode: number) => {
if (errorCode === AMapException.CODE_AMAP_SUCCESS) {
tips.forEach(tip => {
console.log(tip.getName(), tip.getAddress());
});
}
}
});
// 执行查询
const query = new InputtipsQuery('星巴克', '北京');
inputTips.requestInputtips(query);
7. 实用技巧
7.1 处理搜索建议城市
当在某城市搜索不到结果时,API会返回推荐城市:
typescript
onPoiSearched: (result: PoiResult | undefined, errorCode: number) => {
if (result) {
const suggestions = result.getSearchSuggestionCitys();
if (suggestions && suggestions.length > 0) {
suggestions.forEach(city => {
console.log('推荐城市:', city.getCityName());
});
}
}
}
7.2 搜索特定类型
typescript
// 只搜索餐饮类POI
const query = new PoiQuery('', '餐饮服务', '北京');
本篇小结
本篇教程我们学习了:
- ✅ POI关键字搜索实现
- ✅ 周边搜索功能
- ✅ 搜索结果的解析和展示
- ✅ 分页加载更多结果
- ✅ 输入提示功能
下一篇我们将学习地理编码与逆地理编码。
班级
源码地址