vue集成高德地图API工具类封装

发布于:2025-09-11 ⋅ 阅读:(25) ⋅ 点赞:(0)
import axios, { AxiosInstance, AxiosResponse } from 'axios';

// 高德地图 API 响应基础结构
interface AMapResponse {
  status: string;
  info: string;
  infocode: string;
}

// 逆地理编码响应结构
interface RegeoResponse extends AMapResponse {
  regeocode: {
    formatted_address: string;
    addressComponent: {
      province: string;
      city: string;
      district: string;
      township: string;
      citycode: string;
      adcode: string;
    };
    pois?: Array<{ id: string; name: string; address: string }>;
    roads?: Array<{ id: string; name: string }>;
    roadinters?: Array<{ direction: string; distance: string }>;
  };
}

// 地理编码响应结构
interface GeoResponse extends AMapResponse {
  geocodes: Array<{
    location: string;
    formatted_address: string;
    level: string;
    city: string;
    district: string;
    adcode: string;
  }>;
}

// POI 搜索响应结构
interface POISearchResponse extends AMapResponse {
  pois: Array<{
    id: string;
    name: string;
    type: string;
    typecode: string;
    address: string;
    location: string;
    pname: string;
    cityname: string;
    adname: string;
  }>;
}

// 输入提示响应结构
interface InputTipsResponse extends AMapResponse {
  tips: Array<{
    id: string;
    name: string;
    district: string;
    location: string;
  }>;
}

// 距离计算响应结构
interface DistanceResponse extends AMapResponse {
  results: Array<{
    distance: string;
    duration: string;
  }>;
}

// 工具类配置接口
interface AMapConfig {
  apiKey: string;
  baseUrl?: string;
}

// 逆地理编码选项
interface RegeoOptions {
  radius?: number;
  extensions?: 'base' | 'all';
  poiType?: string;
  roadLevel?: number;
  batch?: boolean;
}

// POI 搜索选项
interface POISearchOptions {
  page?: number;
  offset?: number;
  extensions?: 'base' | 'all';
  types?: string;
}

class AMapService {
  private axiosInstance: AxiosInstance;
  private apiKey: string;
  private baseUrl: string;

  /**
   * 构造函数
   * @param config - 配置对象,包含 API Key 和可选的基础 URL
   */
  constructor(config: AMapConfig) {
    this.apiKey = config.apiKey;
    this.baseUrl = config.baseUrl || 'https://restapi.amap.com/v3';
    
    // 初始化 Axios 实例
    this.axiosInstance = axios.create({
      baseURL: this.baseUrl,
      timeout: 10000,
      headers: {
        'Content-Type': 'application/json',
      },
    });
  }

  /**
   * 发起通用请求
   * @param endpoint - API 端点
   * @param params - 请求参数
   * @returns 请求结果
   * @private
   */
  private async _request<T>(endpoint: string, params: Record<string, any> = {}): Promise<T> {
    const baseParams = {
      key: this.apiKey,
      output: 'JSON',
    };

    // 合并参数并移除空值
    const queryParams = { ...baseParams, ...params };
    Object.keys(queryParams).forEach((key) => {
      if (queryParams[key] === undefined || queryParams[key] === null) {
        delete queryParams[key];
      }
    });

    try {
      const response: AxiosResponse<T> = await this.axiosInstance.get(endpoint, { params: queryParams });
      const data = response.data;

      if (data.status !== '1') {
        throw new Error(`高德API错误: ${data.info} (错误码: ${data.infocode})`);
      }

      return data;
    } catch (error) {
      console.error(`高德地图请求失败 (${endpoint}):`, error);
      throw error;
    }
  }

  /**
   * 逆地理编码 - 根据经纬度获取地址信息
   * @param longitude - 经度
   * @param latitude - 纬度
   * @param options - 额外选项
   * @returns 地址信息
   */
  async regeoCode(longitude: number, latitude: number, options: RegeoOptions = {}): Promise<{
    province: string;
    city: string;
    district: string;
    township: string;
    citycode: string;
    adcode: string;
    formattedAddress: string;
    pois: Array<{ id: string; name: string; address: string }>;
    roads: Array<{ id: string; name: string }>;
    roadinters: Array<{ direction: string; distance: string }>;
    rawData: RegeoResponse;
  }> {
    const params = {
      location: `${longitude},${latitude}`,
      radius: options.radius || 1000,
      extensions: options.extensions || 'base',
      poitype: options.poiType,
      roadlevel: options.roadLevel || 0,
      batch: options.batch || false,
    };

    const data = await this._request<RegeoResponse>('geocode/regeo', params);

    if (data.regeocode) {
      const addressComponent = data.regeocode.addressComponent;
      return {
        province: addressComponent.province,
        city: addressComponent.city || addressComponent.province, // 处理直辖市
        district: addressComponent.district,
        township: addressComponent.township,
        citycode: addressComponent.citycode,
        adcode: addressComponent.adcode,
        formattedAddress: data.regeocode.formatted_address,
        pois: data.regeocode.pois || [],
        roads: data.regeocode.roads || [],
        roadinters: data.regeocode.roadinters || [],
        rawData: data,
      };
    }

    throw new Error('未找到地址信息');
  }

  /**
   * 地理编码 - 根据地址描述获取经纬度
   * @param address - 地址描述
   * @param city - 城市限定(可选)
   * @returns 经纬度信息
   */
  async geoCode(address: string, city: string | null = null): Promise<{
    longitude: number;
    latitude: number;
    formattedAddress: string;
    level: string;
    city: string;
    district: string;
    adcode: string;
    rawData: GeoResponse['geocodes'][0];
  }> {
    const params = { address, city };
    const data = await this._request<GeoResponse>('geocode/geo', params);

    if (data.geocodes && data.geocodes.length > 0) {
      const geocode = data.geocodes[0];
      const [longitude, latitude] = geocode.location.split(',').map(Number);

      return {
        longitude,
        latitude,
        formattedAddress: geocode.formatted_address,
        level: geocode.level,
        city: geocode.city,
        district: geocode.district,
        adcode: geocode.adcode,
        rawData: geocode,
      };
    }

    throw new Error('未找到对应的地理位置');
  }

  /**
   * 关键字搜索 POI(兴趣点)
   * @param keywords - 关键字
   * @param city - 城市限定
   * @param options - 额外选项
   * @returns POI 列表
   */
  async searchPOI(keywords: string, city: string | null = null, options: POISearchOptions = {}): Promise<Array<{
    id: string;
    name: string;
    type: string;
    typecode: string;
    address: string;
    location: { longitude: number; latitude: number };
    pname: string;
    cityname: string;
    adname: string;
    rawData: POISearchResponse['pois'][0];
  }>> {
    const params = {
      keywords,
      city: city || '全国',
      page: options.page || 1,
      offset: options.offset || 20,
      extensions: options.extensions || 'base',
      types: options.types,
    };

    const data = await this._request<POISearchResponse>('place/text', params);

    if (data.pois && data.pois.length > 0) {
      return data.pois.map((poi) => ({
        id: poi.id,
        name: poi.name,
        type: poi.type,
        typecode: poi.typecode,
        address: poi.address,
        location: {
          longitude: parseFloat(poi.location.split(',')[0]),
          latitude: parseFloat(poi.location.split(',')[1]),
        },
        pname: poi.pname,
        cityname: poi.cityname,
        adname: poi.adname,
        rawData: poi,
      }));
    }

    return [];
  }

  /**
   * 输入提示(自动完成)
   * @param keywords - 关键词
   * @param city - 城市限定
   * @returns 提示列表
   */
  async inputTips(keywords: string, city: string | null = null): Promise<Array<{
    id: string;
    name: string;
    district: string;
    location: string;
  }>> {
    const params = {
      keywords,
      city: city || '全国',
      type: 'all',
    };

    const data = await this._request<InputTipsResponse>('assistant/inputtips', params);
    return data.tips || [];
  }

  /**
   * 批量逆地理编码(最多 20 个点)
   * @param locations - 经纬度数组 [{longitude, latitude}, ...]
   * @returns 批量结果
   */
  async batchRegeoCode(locations: Array<{ longitude: number; latitude: number }>): Promise<RegeoResponse['regeocode'][]> {
    if (!Array.isArray(locations) || locations.length === 0) {
      throw new Error('位置数组不能为空');
    }

    if (locations.length > 20) {
      throw new Error('批量查询最多支持 20 个点');
    }

    const locationStr = locations.map((loc) => `${loc.longitude},${loc.latitude}`).join('|');
    const data = await this._request<RegeoResponse>('geocode/regeo', {
      location: locationStr,
      batch: true,
    });

    return data.regeocodes || [];
  }

  /**
   * 计算两点间距离
   * @param lng1 - 起点经度
   * @param lat1 - 起点纬度
   * @param lng2 - 终点经度
   * @param lat2 - 终点纬度
   * @returns 距离信息(米)
   */
  async calculateDistance(lng1: number, lat1: number, lng2: number, lat2: number): Promise<{
    distance: number;
    duration: number;
  }> {
    const data = await this._request<DistanceResponse>('distance', {
      origins: `${lng1},${lat1}`,
      destination: `${lng2},${lat2}`,
      type: 1, // 直线距离
    });

    if (data.results && data.results.length > 0) {
      return {
        distance: parseInt(data.results[0].distance),
        duration: parseInt(data.results[0].duration),
      };
    }

    throw new Error('距离计算失败');
  }
}

// 单例模式
let amapInstance: AMapService | null = null;

/**
 * 初始化高德地图服务
 * @param apiKey - API Key
 * @returns 服务实例
 */
export function initAMapService(apiKey: string): AMapService {
  if (!amapInstance) {
    amapInstance = new AMapService({ apiKey });
  }
  return amapInstance;
}

/**
 * 获取高德地图服务实例
 * @returns 服务实例
 */
export function getAMapService(): AMapService {
  if (!amapInstance) {
    throw new Error('请先调用 initAMapService 初始化');
  }
  return amapInstance;
}

export default AMapService;

使用说明

  1. 申请 API Key:在高德地图开放平台(https://lbs.amap.com/)注册并申请 Web 服务 API Key。
  2. 安装依赖
    • 确保项目中已安装 axios 和 TypeScript:npm install axios typescript.
    • 在 tsconfig.json 中启用 esModuleInterop 和 strict 选项以确保类型安全。
  3. 初始化服务

    typescript

    import { initAMapService } from './AMapService';
    
    const amapService = initAMapService('您的API_KEY');
  4. 功能调用示例
    • 逆地理编码

      typescript

      const addressInfo = await amapService.regeoCode(116.480881, 39.989410, { extensions: 'all' });
      console.log(addressInfo.formattedAddress, addressInfo.addressComponent);
    • 地理编码

      typescript

      const geoInfo = await amapService.geoCode('北京市朝阳区望京街', '北京');
      console.log(geoInfo.longitude, geoInfo.latitude);
    • POI 搜索

      typescript

      const pois = await amapService.searchPOI('咖啡', '北京', { page: 1, offset: 10 });
      console.log(pois);
    • 输入提示

      typescript

      const tips = await amapService.inputTips('故宫', '北京');
      console.log(tips);
    • 批量逆地理编码

      typescript

      const locations = [
        { longitude: 116.480881, latitude: 39.989410 },
        { longitude: 116.481026, latitude: 39.989614 },
      ];
      const batchResult = await amapService.batchRegeoCode(locations);
      console.log(batchResult);
    • 距离计算

      typescript

      const distanceInfo = await amapService.calculateDistance(116.480881, 39.989410, 116.481026, 39.989614);
      console.log(distanceInfo.distance, distanceInfo.duration);

网站公告

今日签到

点亮在社区的每一天
去签到