MapV-Three地图检索服务:三个API搞定90%的搜索需求

地图应用开发中,搜索功能是绕不开的话题。用户想找个地方、定位个坐标、或者输入时来点智能提示——这些看似简单的需求,背后都需要地图检索服务来支撑。

好消息是,MapV-Three框架已经把这些服务封装得明明白白了。今天我们就来聊聊三个核心API:LocalSearchGeocoderAutoComplete。掌握它们,基本就能应对大部分地图搜索场景。

一、三个服务的分工

先搞清楚它们各自能干啥:

LocalSearch - 本地搜索服务

核心能力:根据关键词在指定区域内搜索地点信息,返回匹配的POI列表。

适用场景

  • 搜索"北京大学"定位到具体位置
  • 查找"附近的咖啡馆"
  • 获取某个区域内的所有餐饮店

支持的服务源:百度地图、天地图

Geocoder - 地址解析服务

核心能力:实现地址与坐标之间的双向转换。

适用场景

  • 正向解析:输入"故宫博物院",获取经纬度坐标
  • 逆向解析:输入坐标,获取该位置的详细地址

支持的服务源:百度地图、天地图

AutoComplete - 智能输入提示

核心能力:用户输入关键词时实时返回候选地点列表。

适用场景

  • 搜索框输入"天安",自动提示"天安门广场"、"天安门城楼"
  • 类似百度地图、高德地图的搜索体验

支持的服务源:仅支持百度地图

了解完基础概念,接下来看看怎么用。

二、LocalSearch:关键词搜索地点

先看最常用的功能——根据关键词搜索地点。

基础用法

import {useRef} from 'react';
import * as mapvthree from '@baidu/mapv-three';

const ref = useRef();
const center = [116, 40];

async function callback(engine) {
    
    // 创建本地搜索服务实例
    const localSearch = new mapvthree.services.LocalSearch({
        renderOptions: {
            engine,
            autoViewport: true, // 自动调整视野范围
        },
        pageNum: 0, // 分页参数
    });
    
    // 执行搜索
    localSearch.search('北京大学').then(result => {
        console.log('搜索结果:', result);
    });

    return engine;
}

<App ref={ref} initConfig={{
    map: {
        center: center,
        range: 10000,
        provider: new mapvthree.BaiduVectorTileProvider(),
    },
    rendering: {
        enableAnimationLoop: true,
    }
}} callback={callback} />

关键配置说明

  • autoViewport: true:搜索完成后,地图会自动调整视野到搜索结果位置
  • pageNum: 0:控制分页,0表示返回第一页结果
  • renderOptions 中传入 engine 实例,服务会自动在地图上渲染搜索结果

这段代码运行后,地图会自动飞到"北京大学"的位置并显示标注。相当省心。

切换到天地图(可选)

如果项目需要使用天地图服务,只需在创建实例前设置API源:

async function callback(engine) {
    // 设置API服务源
    mapvthree.services.setApiSource(mapvthree.services.API_SOURCE_TIANDITU);
    
    // 创建本地搜索服务实例
    const localSearch = new mapvthree.services.LocalSearch({
        renderOptions: {
            engine,
            autoViewport: true,
        },
        pageNum: 0,
    });
    
    // 执行搜索
    localSearch.search('北京大学').then(result => {
        console.log('搜索结果:', result);
    });

    const mapView = engine.add(new mapvthree.MapView({
        imageryProvider: new mapvthree.TiandituImageryTileProvider()
    }));

    return engine;
}

注意事项

  • 必须在创建服务实例之前调用 setApiSource()
  • 切换服务源后,地图底图也要对应切换(使用 TiandituImageryTileProvider
  • 百度地图和天地图的数据结构略有差异,建议做好兼容处理

三、Geocoder:地址与坐标互转

很多时候,我们需要在地址和坐标之间进行转换。Geocoder就是干这个的。

正向解析:地址转坐标

将地址字符串转换为经纬度坐标:

import {useRef} from 'react';
import * as mapvthree from '@baidu/mapv-three';

const ref = useRef();
const center = [116.403414, 39.924091];

async function callback(engine) {    
    // 创建地理编码服务实例
    const geocoder = new mapvthree.services.Geocoder();
    
    // 地理编码:地址转坐标
    geocoder.getPoint('故宫博物院', (point, detailInfo) => {
        
        if (point) {
            const labelItem = engine.rendering.label.addLabel({
                text: detailInfo.address,
                position: [point.x, point.y],
                mapSrc: 'assets/images/marker/libarary.png',
                iconSize: [60, 60],
                textFillStyle: '#000000',
                textStrokeStyle: '#ffffff',
                textStrokeWidth: 2,
                textAnchor: 'left',
            });
        }
    }).then(res => {
        console.log('Promise 结果:', res);
    });

    const mapView = engine.add(new mapvthree.MapView({
        terrainProvider: null,
        vectorProvider: new mapvthree.BaiduVectorTileProvider({
            displayOptions: {
                poi: false,
            }
        })
    }));
    
    return engine;
}

这个API提供了两种使用方式:

  1. 回调函数:通过第二个参数传入回调,解析完成后立即执行
  2. Promise:返回Promise对象,可以用 .then() 链式调用

两种方式都可以,看个人习惯。回调函数适合简单场景,Promise适合需要错误处理或链式调用的复杂场景。

逆向解析:坐标转地址

将坐标转换为详细地址信息:

import {useRef} from 'react';
import * as mapvthree from '@baidu/mapv-three';

const ref = useRef();
const center = [116.28069, 40.050241];

async function callback(engine) {
    // 创建地理编码服务实例
    const geocoder = new mapvthree.services.Geocoder();

    const targetPoint = center;
    
    // 逆地理编码:坐标转地址
    geocoder.getLocation(targetPoint, (geocoderResult) => {
        if (geocoderResult && geocoderResult.address) {
            engine.rendering.label.addLabel({
                text: geocoderResult.address,
                position: targetPoint,
                textFillStyle: '#ff0000',
                textStrokeStyle: '#ffffff',
                textStrokeWidth: 2,
            })
        }
    });

    
    const mapView = engine.add(new mapvthree.MapView({
        terrainProvider: null,
        vectorProvider: new mapvthree.BaiduVectorTileProvider({
            displayOptions: {
                poi: false,
            }
        })
    }))
    
    return engine;
}

逆向解析常用于地图点击事件的响应。当用户点击地图某个位置时,可以通过逆向解析显示该位置的详细地址。

实用提示

  • 返回的 geocoderResult 包含 address(完整地址)和 addressComponents(省市区等结构化信息)
  • 某些偏远地区或水域可能无法解析出详细地址,需要做好空值判断

天地图版本

Geocoder同样支持天地图服务:

async function callback(engine) {
    // 设置API服务源
    mapvthree.services.setApiSource(mapvthree.services.API_SOURCE_TIANDITU);
    
    // 创建地理编码服务实例
    const geocoder = new mapvthree.services.Geocoder();
    
    // 地理编码:地址转坐标
    geocoder.getPoint('故宫博物院', (point, detailInfo) => {
        
        if (point) {
            const labelItem = engine.rendering.label.addLabel({
                text: detailInfo.address,
                position: [point.x, point.y],
                mapSrc: 'assets/images/marker/libarary.png',
                iconSize: [60, 60],
                textFillStyle: '#000000',
                textStrokeStyle: '#ffffff',
                textStrokeWidth: 2,
                textAnchor: 'left',
            });
        }
    }).then(res => {
        console.log('Promise 结果:', res);
    });

    const mapView = engine.add(new mapvthree.MapView({
        imageryProvider: new mapvthree.TiandituImageryTileProvider()
    }));
    
    return engine;
}

四、AutoComplete:打造智能搜索体验

如果想实现类似百度地图那种"边输入边提示"的体验,就需要用到AutoComplete服务。

完整示例

AutoComplete的使用稍微复杂一些,因为它涉及到UI交互。下面是一个完整的实现:

import React, {useRef, useEffect, useState} from 'react';
import * as mapvthree from '@baidu/mapv-three';

const ref = useRef();
const center = [116.403414, 39.924091];

async function callback(engine) {
  const {gui, input, params, setResults, clearResults} = setupGui(ref);

  const autoComplete = new mapvthree.services.AutoComplete({
    apiSource: mapvthree.services.API_SOURCE_BAIDU,
    input
  });

  const label = addLabel(engine);

  autoComplete.addEventListener('searchComplete', e => {
    const pois = e.results?.pois || e.results || [];
    if (!pois.length) { 
        clearResults();
        return;
    }

    setResults(pois, (poi) => {
        if (!poi.location) {
            return;
        }
        flyTo(engine, poi.location);
        updateLabel(label, poi);
    });
  });

  input.addEventListener('input', () => {
    if (input.value.trim() === '') {
        clearResults();
    }
  });

  return engine;
}

function addLabel(engine) {
  const label = engine.add(new mapvthree.Label({
    type: 'icontext',
    mapSrc: '/mapvthree/assets/images/marker_red.png',
    textFillStyle: 'white',
    iconWidth: 20,
    iconHeight: 20,
    textSize: 14,
    textStrokeStyle: 'black',
    textStrokeWidth: 1,
    textAnchor: 'bottom'
  }));

  const dataSource = mapvthree.GeoJSONDataSource.fromGeoJSON([]);
  dataSource.defineAttribute('text', 'name');
  label.dataSource = dataSource;

  return label;
}

function updateLabel(label, poi) {
    if (!poi.location) {
        return;
    }
    label.dataSource.setData([{
        type: 'Feature',
        geometry: {
            type: 'Point',
            coordinates: [poi.location.x, poi.location.y],
        },
        properties: {
            name: poi.street.trim() || '未命名地点'
        }
    }]);
}

function flyTo(engine, location) {
    engine.map.flyTo(location.toArray(), {
        range: 1000,
        pitch: 0,
        heading: 0
    });
}

核心流程

  1. 创建AutoComplete实例,绑定到输入框
  2. 监听 searchComplete 事件,获取搜索结果
  3. 用户选择某个候选项后,在地图上标注并飞到该位置
  4. 监听输入框的 input 事件,当输入框清空时清除候选列表

几个要点

  • AutoComplete会自动监听输入框的变化,无需手动调用search方法
  • searchComplete 事件会在每次输入变化后触发
  • 建议对输入事件做防抖处理,避免频繁请求

五、实用技巧与注意事项

1. 防抖优化

AutoComplete在用户快速输入时会频繁触发请求,建议添加防抖:

function debounce(func, delay) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => func.apply(this, args), delay);
    };
}

// 在实际项目中,可以对搜索逻辑进行防抖处理
const debouncedSearch = debounce(() => {
    // 搜索逻辑
}, 300);

2. 错误处理

地图服务可能因为网络、API限制等原因失败,务必做好错误处理:

localSearch.search('关键词')
    .then(result => {
        if (!result || !result.pois || result.pois.length === 0) {
            console.log('未找到匹配结果');
            return;
        }
        // 处理结果
    })
    .catch(error => {
        console.error('搜索失败:', error);
        // 显示错误提示
    });

3. 批量标注性能优化

当搜索结果较多时,使用GeoJSON批量添加标注比逐个添加性能更好:

const features = pois.map(poi => ({
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [poi.location.lng, poi.location.lat],
    },
    properties: {
        name: poi.name,
    }
}));

const dataSource = mapvthree.GeoJSONDataSource.fromGeoJSON(features);
label.dataSource = dataSource;

4. 服务源选择

  • 百度地图:数据覆盖全面,更新及时,三个服务都支持
  • 天地图:政府官方服务,部分项目有政策要求,LocalSearch和Geocoder支持
  • AutoComplete仅支持百度地图,如果项目使用天地图,需要自行实现类似功能

5. 坐标系统注意事项

百度地图使用BD-09坐标系,天地图使用GCJ-02坐标系。如果需要在两者之间切换,要注意坐标转换问题。

六、总结

MapV-Three的三个地图检索服务各有分工:

  • LocalSearch:关键词搜索,快速定位地点
  • Geocoder:地址坐标互转,双向解析
  • AutoComplete:智能输入提示,提升用户体验

这三个服务配合使用,基本能覆盖地图应用中90%的搜索需求。而且API设计得相当简洁,上手成本低,性价比很高。

最后提醒几点:

  1. 记得设置 autoViewport: true,让搜索结果自动展示
  2. 做好空值判断和错误处理
  3. AutoComplete要做防抖优化
  4. 根据项目需求选择合适的服务源(百度地图 or 天地图)

掌握这些,地图搜索功能基本就稳了。


参考资料

  • MapV-Three 官方文档
  • 百度地图开放平台