小白从零开始:用 Python + 高德地图打造 JavaScript 网页地图应用

一、引言:为什么选择 Python + 高德地图 + JavaScript?

在数字化时代,地图应用已渗透到生活的方方面面 —— 从外卖 APP 的骑手定位,到旅游网站的景点导航,再到企业的物流追踪系统。而实现 “后端数据处理 + 前端地图可视化” 的组合,Python(灵活的后端数据处理能力)、高德地图(成熟的地图服务 API)、JavaScript(前端交互核心)无疑是性价比极高的技术栈。

本文将以 “周边餐饮查询工具” 为实战案例,带大家完整实现:Python 后端调用高德地图 Web 服务 API 获取地理数据 → 搭建 API 接口供前端调用 → 用 JavaScript 结合高德地图 JS API 在网页显示地图并加载数据,全程代码可复现,关键步骤用表格拆解,即使是编程新手也能跟随操作。

二、基础准备:工具、账号与技术栈选型

在开始开发前,我们需要完成 “账号申请 + 环境搭建 + 技术选型” 三大准备工作,这是后续开发的基础。

2.1 必备工具与账号

类别

具体内容

用途说明

获取方式

开发工具

VS Code

代码编写(支持 Python/JS/HTML,插件丰富)

官网下载

开发工具

Postman

测试 Python 后端 API 接口

官网下载

开发工具

Anaconda

管理 Python 虚拟环境(避免依赖冲突)

官网下载

账号资源

高德地图开发者账号

获取调用 API 的 Key(核心凭证)

高德开发者平台注册

浏览器

Chrome/Firefox

调试前端网页地图

电脑自带或官网下载

2.2 技术栈选型对比

不同技术栈适用于不同场景,下表为你分析各组件的选型理由,帮助你理解 “为什么选这些工具”:

技术模块

可选方案

本文选型

选型理由

Python 后端框架

Flask / Django / FastAPI

Flask

轻量级、学习成本低,适合小型 API 开发;无需复杂配置,快速上手

HTTP 请求库

requests / aiohttp

requests

同步请求足够满足需求,API 简洁,文档丰富,新手友好

前端地图 API

高德地图 JS API / 百度地图 JS API / 谷歌地图 JS API

高德地图 JS API

国内访问稳定,文档全中文,支持 POI 搜索、路径规划等功能,免费额度充足

前端数据交互

原生 JavaScript /jQuery/ Axios

原生 JavaScript + Axios

原生 JS 兼容性好,Axios 简化异步请求,避免 jQuery 的冗余代码

数据格式

JSON / XML / CSV

JSON

前后端数据交互的主流格式,Python 和 JS 都能轻松解析

2.3 高德地图 API Key 申请步骤(关键!)

调用高德地图的所有功能都需要 “API Key”,请严格按照以下步骤操作:

  1. 注册账号:访问高德开发者平台,用手机号注册并完成实名认证(个人开发者即可,免费额度足够测试)。
  1. 创建应用
    • 登录后进入「控制台」→「应用管理」→「创建应用」,输入应用名称(如 “Python 高德地图 Demo”),选择应用类型(如 “Web 前端”)。
  1. 申请 API Key
    • 在应用下点击「添加 Key」,选择 Key 类型(需区分 “Web 服务 API” 和 “JS API”,本文需同时申请两种):
      • Web 服务 API Key:用于 Python 后端调用(如获取 POI 数据、地理编码),需勾选 “Web 服务” 权限。
      • JS API Key:用于前端网页显示地图,需勾选 “JS API” 权限,并设置 “Referer 白名单”(开发阶段可填localhost或*,生产环境需填真实域名)。

Key 类型

用途

权限要求

白名单设置

Web 服务 API Key

Python 后端获取地理数据

勾选 “Web 服务”

无需设置

JS API Key

JavaScript 前端显示地图

勾选 “JS API”

开发:localhost;生产:真实域名(如www.example.com)

三、Python 后端开发:搭建地理数据 API 服务

Python 后端的核心作用是:调用高德地图 Web 服务 API 获取地理数据(如 POI、经纬度),并封装成 HTTP 接口供前端调用。本节将分 “环境搭建→核心功能实现→API 接口封装” 三部分讲解。

3.1 环境搭建:安装依赖库

打开 Anaconda Prompt,创建并激活虚拟环境,然后安装所需库:

 

# 创建虚拟环境(名为amap-demo,Python版本3.9)

conda create -n amap-demo python=3.9

# 激活虚拟环境

conda activate amap-demo

# 安装依赖库

pip install flask requests flask-cors python-dotenv

各库的用途如下表所示:

库名称

用途

关键功能

Flask

轻量级 Python Web 框架

搭建 HTTP 接口(如/api/poi)、处理前端请求

requests

发送 HTTP 请求

调用高德地图 Web 服务 API(如 POI 搜索接口)

flask-cors

解决跨域问题

允许前端(如localhost:5500)访问后端(如localhost:5000)

python-dotenv

管理环境变量

存储 API Key 等敏感信息(避免硬编码到代码中)

3.2 核心功能 1:调用高德地图 Web 服务 API 获取数据

首先,我们需要封装一个 Python 工具类,用于调用高德地图的 Web 服务 API(如 POI 搜索、地理编码)。创建amap_utils.py文件,代码如下:

import requests

from dotenv import load_dotenv

import os

# 加载环境变量(从.env文件读取API Key)

load_dotenv()

AMAP_WEB_KEY = os.getenv("AMAP_WEB_KEY") # 从.env文件获取Web服务API Key

class AMapWebAPI:

def __init__(self, api_key=AMAP_WEB_KEY):

self.api_key = api_key

# 高德地图Web服务API基础URL(参考官方文档)

self.base_url = "https://restapi.amap.com/v3"

def search_poi(self, keywords, city, page=1, page_size=20):

"""

搜索POI(兴趣点)数据(如周边餐厅、酒店)

:param keywords: 搜索关键词(如“餐厅”“咖啡馆”)

:param city: 城市名称(如“北京”“上海”)

:param page: 页码(默认第1页)

:param page_size: 每页结果数(默认20条,最大50条)

:return: 格式化后的POI数据(JSON)

"""

# POI搜索接口URL

url = f"{self.base_url}/place/text"

# 请求参数(参考高德Web服务API文档)

params = {

"key": self.api_key,

"keywords": keywords,

"city": city,

"page": page,

"offset": page_size,

"output": "json", # 返回格式:json/xml

"extensions": "all" # 返回详细信息(如地址、电话、经纬度)

}

try:

# 发送GET请求

response = requests.get(url, params=params)

response.raise_for_status() # 若状态码非200,抛出异常

data = response.json()

# 校验返回结果是否成功

if data["status"] == "1":

# 提取关键信息(过滤无用字段,方便前端使用)

result = {

"total": data["count"], # 总结果数

"page": page,

"page_size": page_size,

"pois": [

{

"id": poi["id"], # POI唯一ID

"name": poi["name"], # 名称

"address": poi["address"], # 地址

"location": poi["location"], # 经纬度(格式:lng,lat)

"tel": poi.get("tel", "无"), # 电话(若无则显示“无”)

"distance": poi.get("distance", 0) # 距离中心点的距离(米,若无则0)

}

for poi in data["pois"]

]

}

return {"success": True, "data": result}

else:

return {"success": False, "error": f"获取POI失败:{data['info']}"}

except Exception as e:

return {"success": False, "error": f"请求异常:{str(e)}"}

def geocode(self, address, city=None):

"""

地理编码(地址→经纬度)

:param address: 详细地址(如“北京市海淀区中关村大街1号”)

:param city: 城市(可选,缩小搜索范围,提高准确性)

:return: 经纬度数据(JSON)

"""

url = f"{self.base_url}/geocode/geo"

params = {

"key": self.api_key,

"address": address,

"city": city,

"output": "json"

}

try:

response = requests.get(url, params=params)

response.raise_for_status()

data = response.json()

if data["status"] == "1" and len(data["geocodes"]) > 0:

geocode_info = data["geocodes"][0]

result = {

"address": geocode_info["formatted_address"], # 格式化地址

"location": geocode_info["location"], # 经纬度(lng,lat)

"city": geocode_info["city"], # 城市名称

"adcode": geocode_info["adcode"] # 行政区划代码

}

return {"success": True, "data": result}

else:

return {"success": False, "error": f"地理编码失败:{data['info']}"}

except Exception as e:

return {"success": False, "error": f"请求异常:{str(e)}"}

def reverse_geocode(self, location):

"""

逆地理编码(经纬度→地址)

:param location: 经纬度(格式:lng,lat,如“116.481028,39.921983”)

:return: 地址数据(JSON)

"""

url = f"{self.base_url}/geocode/regeo"

params = {

"key": self.api_key,

"location": location,

"output": "json",

"extensions": "all" # 返回详细信息(如街道、门牌号)

}

try:

response = requests.get(url, params=params)

response.raise_for_status()

data = response.json()

if data["status"] == "1":

regeo_info = data["regeocode"]

result = {

"address": regeo_info["formatted_address"], # 格式化地址

"street": regeo_info["addressComponent"]["street"], # 街道

"number": regeo_info["addressComponent"]["streetNumber"], # 门牌号

"city": regeo_info["addressComponent"]["city"], # 城市

"district": regeo_info["addressComponent"]["district"] # 区县

}

return {"success": True, "data": result}

else:

return {"success": False, "error": f"逆地理编码失败:{data['info']}"}

except Exception as e:

return {"success": False, "error": f"请求异常:{str(e)}"}

关键说明(用表格拆解):

功能方法

输入参数

输出结果

核心逻辑

search_poi

keywords(关键词)、city(城市)、page(页码)、page_size(每页条数)

包含总结果数、当前页 POI 列表(名称、地址、经纬度等)的 JSON

1. 拼接 POI 搜索接口 URL;2. 传入 API Key 和搜索参数;3. 解析响应,过滤无用字段;4. 返回格式化数据

geocode

address(地址)、city(可选城市)

包含格式化地址、经纬度、城市的 JSON

1. 调用地理编码接口;2. 提取第一个匹配结果的经纬度;3. 返回结构化地址信息

reverse_geocode

location(经纬度)

包含详细地址、街道、门牌号的 JSON

1. 调用逆地理编码接口;2. 解析响应中的地址组件;3. 返回人类可读的详细地址

环境变量配置(.env 文件):

为了避免将 API Key 硬编码到代码中(不安全,且便于切换环境),创建.env文件,内容如下:

 

# .env文件(与amap_utils.py同级目录)

AMAP_WEB_KEY=你的高德地图Web服务API Key # 替换为你申请的Key

3.3 核心功能 2:用 Flask 封装 API 接口

接下来,创建app.py文件,用 Flask 搭建 HTTP 接口,供前端调用 Python 后端的地理数据功能。代码如下:

from flask import Flask, request, jsonify

from flask_cors import CORS

from amap_utils import AMapWebAPI

# 初始化Flask应用

app = Flask(__name__)

# 允许跨域(解决前端和后端域名/端口不同导致的请求被拦截问题)

CORS(app, resources=r"/*") # 开发阶段允许所有请求跨域,生产环境需限制域名

# 初始化高德地图Web API工具类

amap_api = AMapWebAPI()

# 1. POI搜索接口(前端调用此接口获取周边POI数据)

@app.route("/api/poi", methods=["GET"])

def get_poi():

# 从前端请求中获取参数(若参数不存在,用默认值)

keywords = request.args.get("keywords", "餐厅") # 默认搜索“餐厅”

city = request.args.get("city", "北京") # 默认城市“北京”

page = int(request.args.get("page", 1)) # 默认第1页

page_size = int(request.args.get("page_size", 20)) # 默认每页20条

# 调用amap_utils中的search_poi方法

result = amap_api.search_poi(keywords, city, page, page_size)

# 返回JSON格式响应

return jsonify(result)

# 2. 地理编码接口(地址→经纬度)

@app.route("/api/geocode", methods=["GET"])

def get_geocode():

address = request.args.get("address") # 前端必须传入地址参数

city = request.args.get("city") # 可选参数

if not address:

return jsonify({"success": False, "error": "地址参数(address)不能为空"})

result = amap_api.geocode(address, city)

return jsonify(result)

# 3. 逆地理编码接口(经纬度→地址)

@app.route("/api/regeo", methods=["GET"])

def get_regeo():

location = request.args.get("location") # 前端必须传入经纬度(格式:lng,lat)

if not location:

return jsonify({"success": False, "error": "经纬度参数(location)不能为空"})

result = amap_api.reverse_geocode(location)

return jsonify(result)

# 启动Flask服务

if __name__ == "__main__":

# debug=True:开发阶段开启调试模式(代码修改后自动重启服务)

app.run(host="0.0.0.0", port=5000, debug=True)

API 接口详情表(前端对接指南):

接口 URL

请求方法

必选参数

可选参数

返回格式

功能描述

/api/poi

GET

无(默认搜索 “北京餐厅”)

keywords(关键词)、city(城市)、page(页码)、page_size(每页条数)

JSON

获取指定城市、关键词的 POI 数据(如北京的餐厅列表)

/api/geocode

GET

address(详细地址)

city(城市)

JSON

将地址转换为经纬度(如 “北京市中关村大街 1 号”→“116.481028,39.921983”)

/api/regeo

GET

location(经纬度,格式:lng,lat)

JSON

将经纬度转换为详细地址(如 “116.481028,39.921983”→“北京市海淀区中关村大街 1 号”)

测试 API 接口(用 Postman):
  1. 启动 Flask 服务:运行app.py,控制台显示 “Running on http://0.0.0.0:5000/”。
  1. 打开 Postman,发送 GET 请求:
    • 示例 1:测试 POI 搜索接口

URL:http://localhost:5000/api/poi?keywords=咖啡馆&city=上海&page=1&page_size=10

若返回{"success":true,"data":{"total":"123","page":1,...}},说明接口正常。

    • 示例 2:测试地理编码接口

URL:http://localhost:5000/api/geocode?address=上海市浦东新区张江高科技园区&city=上海

若返回{"success":true,"data":{"location":"121.618528,31.219342",...}},说明接口正常。

四、JavaScript 前端开发:实现网页地图显示与数据交互

前端的核心作用是:用高德地图 JS API 在网页中显示地图,并调用 Python 后端的 API 接口,将地理数据(如 POI、经纬度)可视化到地图上。本节将分 “地图初始化→数据对接→功能扩展” 三部分讲解。

4.1 前端项目结构

创建frontend文件夹,结构如下(清晰的目录便于维护):

frontend/

├─ index.html # 网页入口(地图显示、页面布局)

├─ css/

│ └─ style.css # 页面样式(美化地图容器、按钮、列表)

└─ js/

├─ map.js # 地图核心逻辑(初始化、标记POI、事件监听)

└─ api.js # 对接Python后端API(发送请求、处理响应)

4.2 步骤 1:编写 HTML 页面(基础布局)

创建index.html,包含地图容器、搜索框、POI 列表显示区域,代码如下:

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Python+高德地图:周边餐饮查询工具</title>

<!-- 1. 引入高德地图JS API(替换为你的JS API Key) -->

<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=你的高德地图JS API Key"></script>

<!-- 2. 引入Axios(简化异步请求) -->

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

<!-- 3. 引入自定义CSS -->

<link rel="stylesheet" href="./css/style.css">

</head>

<body>

<div class="container">

<!-- 顶部搜索栏 -->

<div class="search-bar">

<input type="text" id="city-input" placeholder="请输入城市(如:北京)" value="北京">

<input type="text" id="keyword-input" placeholder="请输入关键词(如:餐厅、咖啡馆)" value="餐厅">

<button id="search-btn">搜索周边</button>

<button id="clear-btn">清空标记</button>

</div>

<!-- 主体内容:左侧地图,右侧POI列表 -->

<div class="main-content">

<!-- 地图容器(必须设置宽高,否则地图无法显示) -->

<div id="map-container"></div>

<!-- POI列表容器 -->

<div class="poi-list">

<h3>搜索结果(<span id="total-count">0</span>条)</h3>

<div id="poi-items"></div> <!-- POI列表将通过JS动态生成 -->

<!-- 分页按钮 -->

<div class="pagination">

<button id="prev-page" disabled>上一页</button>

<span id="page-info">第1页 / 共0页</span>

<button id="next-page" disabled>下一页</button>

</div>

</div>

</div>

</div>

<!-- 引入自定义JS(先引入api.js,再引入map.js,保证依赖顺序) -->

<script src="./js/api.js"></script>

<script src="./js/map.js"></script>

</body>

</html>

关键说明:
  • 高德地图 JS API 引入:必须在<head>中引入,且key需替换为你申请的 “JS API Key”(若 Key 错误,地图会显示空白并报错)。
  • 地图容器:#map-container必须设置宽高(将在style.css中配置),否则地图无法渲染。
  • 依赖顺序:先引入api.js(对接后端 API),再引入map.js(使用 API 数据),避免变量未定义错误。

4.3 步骤 2:编写 CSS 样式(美化页面)

创建css/style.css,优化页面布局和视觉效果,代码如下:

/* 重置默认样式(避免浏览器差异) */

* {

margin: 0;

padding: 0;

box-sizing: border-box;

font-family: "Microsoft YaHei", sans-serif;

}

body {

background-color: #f5f5f5;

padding: 20px;

}

/* 容器:限制页面宽度,居中显示 */

.container {

max-width: 1400px;

margin: 0 auto;

}

/* 搜索栏样式 */

.search-bar {

display: flex;

gap: 10px;

margin-bottom: 20px;

align-items: center;

}

.search-bar input {

padding: 10px 15px;

border: 1px solid #ddd;

border-radius: 4px;

font-size: 16px;

}

#city-input {

width: 200px;

}

#keyword-input {

flex: 1; /* 占满剩余宽度 */

max-width: 500px;

}

.search-bar button {

padding: 10px 20px;

background-color: #3f88c5;

color: white;

border: none;

border-radius: 4px;

font-size: 16px;

cursor: pointer;

transition: background-color 0.3s;

}

.search-bar button:hover {

background-color: #2a6faa;

}

#clear-btn {

background-color: #e63946;

}

#clear-btn:hover {

background-color: #c1121f;

}

/* 主体内容:地图+POI列表 */

.main-content {

display: flex;

gap: 20px;

height: 80vh; /* 占屏幕高度的80% */

}

/* 地图容器样式 */

#map-container {

flex: 2; /* 地图占2/3宽度 */

border-radius: 8px;

box-shadow: 0 2px 10px rgba(0,0,0,0.1);

z-index: 1; /* 确保地图在底层,不遮挡其他元素 */

}

/* POI列表容器样式 */

.poi-list {

flex: 1; /* 列表占1/3宽度 */

background-color: white;

border-radius: 8px;

box-shadow: 0 2px 10px rgba(0,0,0,0.1);

padding: 20px;

overflow-y: auto; /* 内容超出时滚动 */

}

.poi-list h3 {

margin-bottom: 15px;

color: #333;

border-bottom: 1px solid #eee;

padding-bottom: 10px;

}

/* POI列表项样式 */

.poi-item {

padding: 15px;

border-bottom: 1px solid #eee;

cursor: pointer;

transition: background-color 0.3s;

}

.poi-item:hover {

background-color: #f9f9f9;

}

.poi-item h4 {

color: #3f88c5;

margin-bottom: 5px;

}

.poi-item p {

color: #666;

font-size: 14px;

margin: 3px 0;

}

/* 分页按钮样式 */

.pagination {

display: flex;

justify-content: center;

align-items: center;

gap: 15px;

margin-top: 20px;

}

.pagination button {

padding: 8px 15px;

border: 1px solid #ddd;

border-radius: 4px;

background-color: white;

cursor: pointer;

font-size: 14px;

}

.pagination button:disabled {

background-color: #f5f5f5;

color: #999;

cursor: not-allowed;

}

#page-info {

color: #666;

font-size: 14px;

}

4.4 步骤 3:编写 API 对接逻辑(api.js)

创建js/api.js,封装调用 Python 后端 API 的函数,供map.js使用。代码如下:

 

// api.js:对接Python后端API的工具函数

const API_BASE_URL = "http://localhost:5000/api"; // 后端API基础URL

// 1. 获取POI数据(调用后端/api/poi接口)

export async function getPOIData(params) {

try {

// 发送GET请求(Axios会自动拼接参数到URL)

const response = await axios.get(`${API_BASE_URL}/poi`, { params });

return response.data; // 返回后端响应数据

} catch (error) {

console.error("获取POI数据失败:", error);

return { success: false, error: "网络异常,获取数据失败" };

}

}

// 2. 地理编码(地址→经纬度,调用后端/api/geocode接口)

export async function getGeocode(address, city = "") {

try {

const response = await axios.get(`${API_BASE_URL}/geocode`, {

params: { address, city }

});

return response.data;

} catch (error) {

console.error("地理编码失败:", error);

return { success: false, error: "网络异常,地理编码失败" };

}

}

// 3. 逆地理编码(经纬度→地址,调用后端/api/regeo接口)

export async function getRegeo(location) {

try {

const response = await axios.get(`${API_BASE_URL}/regeo`, {

params: { location }

});

return response.data;

} catch (error) {

console.error("逆地理编码失败:", error);

return { success: false, error: "网络异常,逆地理编码失败" };

}

}

关键说明:
  • async/await:用于处理异步请求,避免回调地狱,代码更易读。
  • 错误处理:每个函数都有try/catch,捕获网络异常并返回错误信息,便于前端提示用户。

4.5 步骤 4:编写地图核心逻辑(map.js)

创建js/map.js,实现地图初始化、POI 标记显示、搜索交互等核心功能。代码如下:

 

// 导入api.js中的工具函数

import { getPOIData, getGeocode, getRegeo } from "./api.js";

// 全局变量(存储地图实例、POI标记等)

let map = null; // 高德地图实例

let poiMarkers = []; // 存储POI标记的数组(用于清空标记)

let currentPage = 1; // 当前页码

let totalPage = 0; // 总页数

let totalCount = 0; // 总POI数量

let currentParams = { // 当前搜索参数(关键词、城市等)

keywords: "餐厅",

city: "北京",

page_size: 10

};

// 页面加载完成后初始化地图

document.addEventListener("DOMContentLoaded", async () => {

// 1. 初始化高德地图

initMap();

// 2. 绑定搜索、清空、分页按钮的点击事件

bindEvents();

// 3. 初始加载第一页POI数据

await loadPOIData(currentParams, currentPage);

});

/**

* 1. 初始化高德地图

*/

function initMap() {

// 创建地图实例(中心点默认设为北京:116.404, 39.915)

map = new AMap.Map("map-container", {

zoom: 13, // 地图缩放级别(1-20,越大越详细)

center: [116.404, 39.915], // 地图中心点经纬度

layers: [new AMap.TileLayer.Satellite()], // 卫星图层(可替换为默认图层:AMap.TileLayer.RoadNet)

resizeEnable: true // 允许地图随容器大小变化而调整

});

// 添加地图控件(缩放控件、比例尺、定位控件)

map.addControl(new AMap.Scale()); // 比例尺控件(显示地图比例尺)

map.addControl(new AMap.Zoom()); // 缩放控件(放大/缩小地图)

map.addControl(new AMap.Geolocation({ // 定位控件(获取用户当前位置)

enableHighAccuracy: true, // 开启高精度定位

timeout: 10000, // 定位超时时间(毫秒)

buttonPosition: "RB" // 控件位置(右下角)

}));

// 监听定位成功事件(获取用户位置后,将地图中心点移到用户位置)

map.on("geolocationComplete", (data) => {

const { position } = data;

map.setCenter([position.lng, position.lat]);

console.log("用户当前位置:", position);

});

}

/**

* 2. 绑定页面交互事件(搜索、清空、分页)

*/

function bindEvents() {

// 搜索按钮点击事件

document.getElementById("search-btn").addEventListener("click", async () => {

const city = document.getElementById("city-input").value.trim();

const keywords = document.getElementById("keyword-input").value.trim();

// 校验输入(城市和关键词不能为空)

if (!city || !keywords) {

alert("请输入城市和搜索关键词!");

return;

}

// 更新当前搜索参数

currentParams = {

keywords: keywords,

city: city,

page_size: currentParams.page_size // 保持每页条数不变

};

currentPage = 1; // 重置为第一页

// 加载新的POI数据

await loadPOIData(currentParams, currentPage);

});

// 清空标记按钮点击事件

document.getElementById("clear-btn").addEventListener("click", () => {

clearPOIMarkers(); // 清空地图上的POI标记

document.getElementById("poi-items").innerHTML = ""; // 清空POI列表

document.getElementById("total-count").textContent = "0"; // 重置总条数

document.getElementById("page-info").textContent = "第1页 / 共0页"; // 重置分页信息

currentPage = 1;

totalPage = 0;

totalCount = 0;

});

// 上一页按钮点击事件

document.getElementById("prev-page").addEventListener("click", async () => {

if (currentPage > 1) {

currentPage--;

await loadPOIData(currentParams, currentPage);

}

});

// 下一页按钮点击事件

document.getElementById("next-page").addEventListener("click", async () => {

if (currentPage < totalPage) {

currentPage++;

await loadPOIData(currentParams, currentPage);

}

});

// POI列表项点击事件(通过事件委托,监听动态生成的列表项)

document.getElementById("poi-items").addEventListener("click", (e) => {

const poiItem = e.target.closest(".poi-item");

if (poiItem) {

// 获取POI的经纬度(从data-location属性中获取)

const location = poiItem.getAttribute("data-location");

if (location) {

const [lng, lat] = location.split(",").map(Number);

// 将地图中心点移到该POI,并放大到15级

map.setCenter([lng, lat]);

map.setZoom(15);

}

}

});

}

/**

* 3. 加载POI数据(调用后端API,更新地图标记和列表)

* @param {Object} params - 搜索参数(keywords, city, page_size)

* @param {number} page - 当前页码

*/

async function loadPOIData(params, page) {

// 显示加载提示

document.getElementById("poi-items").innerHTML = '<div style="text-align:center;padding:20px;">加载中...</div>';

// 调用后端API获取POI数据

const result = await getPOIData({ ...params, page });

if (result.success) {

const { total, page: currentPageRes, pois } = result.data;

totalCount = total;

currentPage = currentPageRes;

totalPage = Math.ceil(totalCount / params.page_size); // 计算总页数(向上取整)

// 更新页面UI(总条数、分页信息)

document.getElementById("total-count").textContent = totalCount;

document.getElementById("page-info").textContent = `第${currentPage}页 / 共${totalPage}页`;

// 更新分页按钮状态(是否禁用)

document.getElementById("prev-page").disabled = currentPage === 1;

document.getElementById("next-page").disabled = currentPage === totalPage;

// 清空之前的POI标记

clearPOIMarkers();

// 1. 在地图上添加POI标记

addPOIMarkers(pois);

// 2. 生成POI列表

renderPOIList(pois);

} else {

// 显示错误提示

document.getElementById("poi-items").innerHTML = `<div style="text-align:center;padding:20px;color:red;">${result.error}</div>`;

}

}

/**

* 4. 在地图上添加POI标记

* @param {Array} pois - POI数据数组

*/

function addPOIMarkers(pois) {

pois.forEach(poi => {

const { location, name, address } = poi;

const [lng, lat] = location.split(",").map(Number);

// 创建自定义图标(替换默认标记图标)

const markerIcon = new AMap.Icon({

size: new AMap.Size(32, 32), // 图标大小

image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_b.png", // 图标地址(高德默认蓝色标记)

imageSize: new AMap.Size(32, 32) // 图标图片大小

});

// 创建标记实例

const marker = new AMap.Marker({

position: [lng, lat], // 标记位置(经纬度)

icon: markerIcon, // 自定义图标

title: name, // 鼠标 hover 时显示的标题

anchor: "bottom-center" // 图标锚点(标记点对应图标底部中心)

});

// 添加信息窗口(点击标记时显示POI详情)

const infoWindow = new AMap.InfoWindow({

content: `

<div style="padding:10px;">

<h4 style="color:#3f88c5;margin-bottom:5px;">${name}</h4>

<p style="margin:3px 0;font-size:14px;">地址:${address}</p>

<p style="margin:3px 0;font-size:14px;">电话:${poi.tel}</p>

</div>

`, // 信息窗口内容

offset: new AMap.Pixel(0, -30) // 信息窗口偏移(避免遮挡标记)

});

// 绑定标记的点击事件(显示信息窗口)

marker.on("click", () => {

infoWindow.open(map, marker.getPosition());

});

// 将标记添加到地图

marker.setMap(map);

// 存储标记到数组(用于后续清空)

poiMarkers.push({ marker, infoWindow });

});

}

/**

* 5. 生成POI列表(动态渲染到页面)

* @param {Array} pois - POI数据数组

*/

function renderPOIList(pois) {

if (pois.length === 0) {

document.getElementById("poi-items").innerHTML = '<div style="text-align:center;padding:20px;">未找到相关POI数据</div>';

return;

}

// 拼接POI列表HTML

let poiHtml = "";

pois.forEach(poi => {

poiHtml += `

<div class="poi-item" data-location="${poi.location}">

<h4>${poi.name}</h4>

<p>地址:${poi.address}</p>

<p>电话:${poi.tel}</p>

${poi.distance > 0 ? `<p>距离:${poi.distance}米</p>` : ""}

</div>

`;

});

// 将HTML插入到POI列表容器

document.getElementById("poi-items").innerHTML = poiHtml;

}

/**

* 6. 清空地图上的POI标记和信息窗口

*/

function clearPOIMarkers() {

poiMarkers.forEach(({ marker, infoWindow }) => {

marker.setMap(null); // 从地图上移除标记

infoWindow.close(); // 关闭信息窗口

});

poiMarkers = []; // 清空标记数组

}

地图核心功能拆解表:

功能模块

关键函数

实现逻辑

前端交互效果

地图初始化

initMap()

1. 创建地图实例,设置中心点和缩放级别;2. 添加缩放、比例尺、定位控件;3. 监听定位事件

页面加载后显示北京地图,右下角有定位按钮,点击可获取用户位置并居中

POI 搜索

loadPOIData()

1. 调用后端/api/poi接口获取数据;2. 清空旧标记;3. 调用addPOIMarkers()和renderPOIList()更新地图和列表

点击 “搜索周边” 后,地图显示 POI 标记,右侧显示列表,支持分页

POI 标记

addPOIMarkers()

1. 为每个 POI 创建自定义图标标记;2. 绑定点击事件(显示信息窗口);3. 将标记添加到地图

地图上显示蓝色标记,鼠标 hover 显示名称,点击显示详情(地址、电话)

POI 列表交互

renderPOIList() + 事件委托

1. 动态生成 POI 列表 HTML;2. 列表项绑定点击事件(地图居中到该 POI)

点击右侧列表项,地图自动定位到对应 POI 并放大

清空标记

clearPOIMarkers()

1. 遍历标记数组,移除地图上的标记;2. 关闭信息窗口;3. 清空数组

点击 “清空标记” 后,地图标记和列表全部清空

五、功能扩展:从基础到进阶(可选)

基础版本实现后,你可以根据需求扩展功能。以下是几个常用的进阶功能,同样用表格和代码说明实现思路。

5.1 扩展 1:添加 “地址搜索定位” 功能

需求:用户输入详细地址(如 “上海市浦东新区张江高科技园区”),点击按钮后地图定位到该地址,并显示 POI。

实现步骤:
  1. 在index.html的搜索栏添加地址输入框和定位按钮:
 

<div class="search-bar">

<!-- 原有代码 -->

<input type="text" id="address-input" placeholder="请输入详细地址(如:北京市中关村大街1号)">

<button id="locate-address-btn">地址定位</button>

</div>

  1. 在map.js的bindEvents()函数中添加按钮点击事件:
 

// 地址定位按钮点击事件

document.getElementById("locate-address-btn").addEventListener("click", async () => {

const address = document.getElementById("address-input").value.trim();

const city = document.getElementById("city-input").value.trim();

if (!address) {

alert("请输入详细地址!");

return;

}

// 调用地理编码接口(地址→经纬度)

const result = await getGeocode(address, city);

if (result.success) {

const { location, formatted_address } = result.data;

const [lng, lat] = location.split(",").map(Number);

// 1. 地图定位到该地址

map.setCenter([lng, lat]);

map.setZoom(15);

// 2. 在该地址添加定位标记

clearPOIMarkers(); // 清空原有标记

const locateMarker = new AMap.Marker({

position: [lng, lat],

icon: new AMap.Icon({

size: new AMap.Size(32, 32),

image: "https://webapi.amap.com/theme/v1.3/markers/n/mark_r.png", // 红色定位标记

imageSize: new AMap.Size(32, 32)

}),

title: "定位地址"

});

locateMarker.setMap(map);

poiMarkers.push({ marker: locateMarker, infoWindow: new AMap.InfoWindow() });

// 3. 显示地址信息窗口

const infoWindow = new AMap.InfoWindow({

content: `<div style="padding:10px;">定位地址:${formatted_address}</div>`,

offset: new AMap.Pixel(0, -30)

});

infoWindow.open(map, [lng, lat]);

// 4. 可选:搜索该地址周边的POI(如周边500米内的餐厅)

currentParams = {

keywords: currentParams.keywords,

city: city,

page_size: currentParams.page_size,

location: location, // 新增:以定位地址为中心点

radius: 500 // 新增:搜索半径500米

};

currentPage = 1;

await loadPOIData(currentParams, currentPage);

} else {

alert(`地址定位失败:${result.error}`);

}

});

  1. 更新 Python 后端amap_utils.py的search_poi()方法,支持按中心点和半径搜索:
 

def search_poi(self, keywords, city, page=1, page_size=20, location=None, radius=None):

url = f"{self.base_url}/place/text"

params = {

"key": self.api_key,

"keywords": keywords,

"city": city,

"page": page,

"offset": page_size,

"output": "json",

"extensions": "all"

}

# 新增:若传入location和radius,添加到请求参数(按中心点搜索)

if location and radius:

params["location"] = location

params["radius"] = radius # 搜索半径(米)

# 后续代码不变...

5.2 扩展 2:添加 “热力图” 显示 POI 分布

需求:用热力图直观展示 POI 的分布密度(如北京餐厅的分布情况)。

实现步骤:
  1. 在index.html的高德地图 JS API 引入中添加热力图插件:
 

<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=你的JS API Key&plugins=HeatMap"></script>

  1. 在map.js中添加热力图相关全局变量和函数:
 

let heatMap = null; // 热力图实例

let isHeatMapShow = false; // 热力图是否显示的开关

// 在initMap()函数中初始化热力图(默认不显示)

function initMap() {

// 原有代码...

// 初始化热力图(隐藏状态)

heatMap = new AMap.HeatMap(map, {

radius: 25, // 热力图点半径(越大,热力范围越广)

opacity: [0, 0.8], // 透明度范围

gradient: { // 热力图颜色梯度(从低到高:蓝→绿→黄→红)

0.2: '#00fffc',

0.4: '#4dff4d',

0.6: '#ffff00',

0.8: '#ff9900',

1.0: '#ff0000'

}

});

heatMap.hide(); // 默认隐藏热力图

}

// 添加热力图切换按钮事件(在bindEvents()中)

function bindEvents() {

// 原有代码...

// 热力图切换按钮(需在index.html中添加按钮)

document.getElementById("toggle-heatmap-btn").addEventListener("click", () => {

isHeatMapShow = !isHeatMapShow;

const btn = document.getElementById("toggle-heatmap-btn");

if (isHeatMapShow) {

// 显示热力图:隐藏POI标记,显示热力图

clearPOIMarkers();

heatMap.show();

btn.textContent = "隐藏热力图";

// 加载POI数据并生成热力图数据

if (totalCount > 0) {

loadHeatMapData(currentParams);

} else {

alert("请先搜索POI数据,再显示热力图!");

isHeatMapShow = false;

btn.textContent = "显示热力图";

}

} else {

// 隐藏热力图:显示POI标记

heatMap.hide();

btn.textContent = "显示热力图";

loadPOIData(currentParams, currentPage); // 重新加载POI标记

}

});

}

// 加载热力图数据

async function loadHeatMapData(params) {

// 获取所有POI数据(不分页,最多1000条,高德API限制)

const result = await getPOIData({ ...params, page: 1, page_size: 1000 });

if (result.success) {

const pois = result.data.pois;

// 转换为热力图所需格式:[[lng, lat, weight], ...](weight为权重,这里用1)

const heatData = pois.map(poi => {

const [lng, lat] = poi.location.split(",").map(Number);

return [lng, lat, 1]; // 权重为1,所有POI同等重要

});

// 设置热力图数据

heatMap.setData(heatData);

}

}

  1. 在index.html的搜索栏添加热力图切换按钮:
 

<div class="search-bar">

<!-- 原有代码 -->

<button id="toggle-heatmap-btn">显示热力图</button>

</div>

六、常见问题排查与优化(避坑指南)

在开发过程中,你可能会遇到各种问题(如地图空白、API 调用失败)。下表整理了高频问题及解决方案:

常见问题

可能原因

解决方案

地图显示空白,控制台报错 “Invalid Key”

1. JS API Key 错误;2. Key 未勾选 “JS API” 权限;3. Referer 白名单未设置

1. 检查 Key 是否为 “JS API Key”,而非 “Web 服务 API Key”;2. 进入高德开发者平台,确认 Key 已勾选 “JS API” 权限;3. 开发阶段将 Referer 白名单设为localhost或*

前端调用后端 API 报错 “CORS Error”

后端未配置跨域,浏览器拦截前端请求

1. 确保 Flask 已安装flask-cors;2. 在app.py中添加CORS(app, resources=r"/*");3. 生产环境需限制允许跨域的域名(如CORS(app, resources={"r/*": {"origins": "https://www.example.com"}}))

后端调用高德 Web 服务 API 返回 “INVALID_USER_KEY”

1. Web 服务 API Key 错误;2. Key 未勾选 “Web 服务” 权限;3. Key 已过期

1. 检查.env文件中的AMAP_WEB_KEY是否正确;2. 确认 Key 已勾选 “Web 服务” 权限;3. 进入高德开发者平台,检查 Key 是否有效(未被禁用)

地图标记不显示,但 POI 列表正常

1. 经纬度格式错误(如 “lat,lng” 顺序颠倒);2. 标记图标地址错误;3. 地图容器未设置宽高

1. 确认 POI 的location格式为 “lng,lat”(如 “116.404,39.915”);2. 检查addPOIMarkers()中的图标地址是否可访问;3. 确认#map-container在 CSS 中设置了width和height

前端请求后端 API 时,控制台显示 “404 Not Found”

1. 后端服务未启动;2. API URL 错误;3. 请求方法错误(如用 POST 请求 GET 接口)

1. 确保app.py已启动,且端口为 5000;2. 检查api.js中的API_BASE_URL是否为http://localhost:5000/api;3. 确认请求方法与后端接口一致(如/api/poi为 GET 请求)

热力图不显示,控制台无报错

1. 热力图插件未引入;2. 热力图数据格式错误;3. 热力图被 POI 标记覆盖

1. 确认 JS API 引入时添加了plugins=HeatMap;2. 检查heatData格式是否为[[lng, lat, weight], ...];3. 显示热力图前需调用clearPOIMarkers()清空标记

七、项目部署:从本地到线上(可选)

本地开发完成后,若想让他人访问你的地图应用,需要将项目部署到服务器。以下是简易部署方案(适合个人测试):

7.1 部署 Python 后端(用 PythonAnywhere)

  1. 注册PythonAnywhere账号(免费版支持 1 个 Web 应用)。
  1. 上传后端代码(app.pyamap_utils.py、.env)到 PythonAnywhere 的mysite目录。
  1. 安装依赖:在 PythonAnywhere 的 “Consoles” 中运行pip install flask requests flask-cors python-dotenv。
  1. 配置 Web 应用:
    • 选择 “Flask” 框架,Python 版本 3.9+。
    • 设置 “Source code” 路径为你的代码目录(如/home/你的用户名/mysite)。
    • 设置 “WSGI configuration file” 为/home/你的用户名/mysite/flask_app.wsgi。
  1. 启动 Web 应用:点击 “Reload”,后端 API 地址为https://你的用户名.pythonanywhere.com/api

7.2 部署前端页面(用 GitHub Pages)

  1. 创建 GitHub 仓库(如amap-demo-frontend),上传前端代码(index.html、css/、js/)。
  1. 进入仓库 “Settings”→“Pages”,设置 “Source” 为 “main branch”,“Folder” 为 “/”。
  1. 保存后,前端页面地址为https://你的用户名.github.io/amap-demo-frontend/
  1. 修改前端api.js中的API_BASE_URL为 PythonAnywhere 的后端地址(如https://你的用户名.pythonanywhere.com/api)。

八、总结与展望

8.1 项目总结

本文通过 “周边餐饮查询工具” 实战案例,完整讲解了 “Python 后端 + 高德地图 + JavaScript 前端” 的开发流程,核心收获如下:

  1. 技术栈整合:Python(Flask)负责后端数据处理和 API 封装,高德地图提供地理数据和地图渲染能力,JavaScript 负责前端交互,三者协同实现 “数据→接口→可视化” 的完整链路。
  1. 关键知识点
    • 高德地图 API 的两种 Key(Web 服务 API Key 用于后端,JS API Key 用于前端)的申请与配置。
    • Python 后端如何调用第三方 API(高德 Web 服务 API)并封装成 HTTP 接口。
    • JavaScript 前端如何用高德 JS API 初始化地图、添加标记、实现交互,并对接后端 API。
  1. 实战能力:掌握了 POI 搜索、地理编码、地图标记、热力图等常用功能的实现,以及跨域问题、API 错误排查等实战技巧。

8.2 未来扩展方向

  1. 功能扩展
    • 添加 “路径规划” 功能(步行、驾车、公交路线)。
    • 结合数据库(如 MySQL)存储 POI 数据,支持历史搜索记录。
    • 实现用户认证(如微信登录),保存用户的收藏 POI。
  1. 性能优化
    • 后端添加缓存(如 Redis),减少重复调用高德 API 的次数(节省额度)。
    • 前端实现 POI 数据懒加载,优化大数据量下的渲染速度。
  1. 平台扩展
    • 基于此项目开发移动端应用(用 React Native 或 Flutter)。
    • 集成其他地图服务(如百度地图、谷歌地图),支持多地图切换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值