@clawhub-tencent-adm-effa9ea7bd
Skill to call Cloud API for Tencent Cloud (腾讯云). Used for cloud automation or resource management. 当用户需要查询、创建、管理腾讯云资源,或执行云 API 自动化操作时触发。
---
name: tcapi
display_name: 腾讯云 API 助手
description: Skill to call Cloud API for Tencent Cloud (腾讯云). Used for cloud automation or resource management. 当用户需要查询、创建、管理腾讯云资源,或执行云 API 自动化操作时触发。
version: 1.0.0
tags: [tccli, cloud-api, tencent-cloud, automation]
keywords: [腾讯云, tccli, cloud api, 云资源, 云管理, 自动化运维]
prompt_template: 对 {service} 产品执行 {action} 操作
examples:
- 查询广州地域的 CVM 实例
- 创建一台按量计费的云服务器
- 查看 COS 存储桶列表
---
# 腾讯云 API 助手
统一使用 **tccli** 命令行工具调用腾讯云 API,实现云资源的查询、创建、修改、删除等操作。
## 适用场景
- 云资源查询与管理(CVM / COS / CBS / VPC / TKE 等 200+ 产品)
- 自动化运维(批量操作、定时任务、脚本编排)
- 云 API 接口探索与文档检索
## 不适用场景
- 不支持 Terraform / Pulumi 等 IaC 编排工具
- 不做多云管理(仅限腾讯云)
- 不做费用充值、账号注册等非 API 操作
## 前置条件
- 已安装 tccli,未安装参考 [references/install.md](references/install.md)
- 已完成凭证配置(详见下方「Step 2 凭证配置」)
## 核心原则
> **优先检索最佳实践 → 再查接口文档 → 最后调用 API**。不要跳过文档检索直接调用,避免用错接口或遗漏参数。
---
# 执行流程
## Step 1:检索 API 文档
调用前先通过 curl + grep 检索业务、接口、最佳实践、数据结构。参考 [references/refs.md](references/refs.md) 获取完整检索方式。
### 1.1 发现业务
检索 tccli 服务名(如 cvm、cbs)。
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/services.md | grep 云服务器
```
参考输出:
```
[cvm](service/cvm/index.md) | 云服务器 | 2017-03-12 | ...
```
### 1.2 发现最佳实践
优先检索是否有匹配当前场景的最佳实践。
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/practices.md | grep 重装
```
### 1.3 检索接口
若最佳实践未覆盖,在业务接口列表中检索(接口名即 tccli 的 `<Action>`)。
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/actions.md | grep "扩容\|磁盘"
```
### 1.4 阅读接口文档
获取参数说明和支持的地域信息:
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/action/ResizeInstanceDisks.md
```
### 1.5 阅读数据结构
文档中涉及的数据结构可进一步查看:
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/model/SystemDisk.md
```
## Step 2:凭证配置
如果已经提供了凭证,tccli 可以正常调用。
如缺少凭证,执行 tccli 会提示 "secretId is invalid"。应执行 `tccli auth login` 进行浏览器授权登录,等待回调后继续(命令会起本地端口、阻塞进程,直到浏览器 OAuth 完成并回调)。
凭证授权原理,以及多用户凭证的使用方法,参考 [references/auth.md](references/auth.md)。
**安全红线**:严禁向用户索要 SecretId/SecretKey,也拒绝任何有可能打印凭证的操作(尤其是 `tccli configure list`)。
## Step 3:调用 API
**基本形式**:
```sh
tccli <service> <Action> [--param value ...] [--region <地域>]
```
**输入参数**:
| 参数 | 类型 | 必填 | 说明 |
|:-----|:-----|:-----|:-----|
| `service` | string | 是 | 产品标识,如 `cvm`、`cbs`、`vpc`。通过 Step 1.1 检索获取 |
| `Action` | string | 是 | 接口名,如 `DescribeInstances`、`RunInstances`。通过 Step 1.3 检索获取 |
| `--region` | string | 视接口 | 地域,如 `ap-guangzhou`。多数产品必传;全局接口(cam、account、dnspod、domain、ssl、ba、tag)可省略 |
| `--param value` | 各类型 | 视接口 | 接口参数,简单类型直接传值,复杂类型传 JSON 字符串 |
**常用示例**:
```sh
# 查询 CVM 地域
tccli cvm DescribeRegions
# 查询实例(需指定地域)
tccli cvm DescribeInstances --region ap-guangzhou
```
**参数规则**:
- 非简单类型参数必须为标准 JSON,例如:`--Placement '{"Zone":"ap-guangzhou-2"}'`。
- 创建类接口示例(按需替换参数):
```sh
tccli cvm RunInstances --InstanceChargeType POSTPAID_BY_HOUR \
--Placement '{"Zone":"ap-guangzhou-2"}' --InstanceType S1.SMALL1 --ImageId img-xxx \
--SystemDisk '{"DiskType":"CLOUD_BASIC","DiskSize":50}' --InstanceCount 1 ...
```
**输出格式**:tccli 返回标准 JSON,包含 `Response` 字段。示例:
```json
{
"Response": {
"TotalCount": 1,
"InstanceSet": [{"InstanceId": "ins-xxx", "InstanceName": "test", ...}],
"RequestId": "eac6b301-..."
}
}
```
**空结果输出**:查询无匹配时,列表字段返回空数组,计数字段为 0:
```json
{
"Response": {
"TotalCount": 0,
"InstanceSet": [],
"RequestId": "eac6b301-..."
}
}
```
**效率约束**:腾讯云 API 默认限频为 **10 次/秒**(部分接口更低),批量操作时需控制调用频率,避免触发 `RequestLimitExceeded`。建议串行调用或加间隔,不要并发轰炸。
## Step 4:异常处理
调用失败时,tccli 会返回包含 `Error` 字段的 JSON:
```json
{
"Response": {
"Error": { "Code": "AuthFailure.SecretIdNotFound", "Message": "secretId is invalid" },
"RequestId": "xxx"
}
}
```
**常见错误及处理**:
| 错误码 | 含义 | 处理方式 |
|:------|:-----|:---------|
| `AuthFailure.SecretIdNotFound` | 凭证缺失或无效 | 执行 `tccli auth login` 重新授权 |
| `AuthFailure.UnauthorizedOperation` | 无权限 | 检查 CAM 策略,确认子账号有该接口权限 |
| `InvalidParameterValue` | 参数值不合法 | 查阅接口文档确认参数取值范围 |
| `ResourceNotFound` | 资源不存在 | 确认资源 ID 和地域是否正确 |
| `RequestLimitExceeded` | 请求频率超限 | 等待后重试,或减少并发调用频率 |
| 网络超时 / 连接失败 | 网络不通 | 检查网络连通性,确认是否需要代理 |
---
# 数据边界与安全声明
- 本 SKILL **只执行用户明确指定的 API 调用**,不会自动执行未经确认的写操作
- tccli 参数由用户指定或从接口文档获取,SKILL **不对参数做二次拼接或动态生成**,避免注入风险
- tccli 调用受腾讯云 **CAM 权限策略**约束,SKILL 不具备超出用户权限的能力
- tccli 输出为 **JSON 数据**,应作为数据解读,不应作为 shell 命令执行
- API 文档检索地址 `cloudcache.tencentcs.com` 为腾讯云官方文档缓存,内容可信
FILE:references/auth.md
# 配置 TCCLI(凭证)
**推荐:浏览器授权登录**,无需手填 SecretId/SecretKey,登录成功后凭证会自动写入本地。
```sh
tccli auth login
```
执行后 TCCLI 会在本机起一个临时端口,并打印 OAuth 授权链接;通常也会自动用默认浏览器打开该链接。用户在浏览器中完成登录与授权后,腾讯云会回调到该本地端口,TCCLI 收到回调即写入凭证并退出。若浏览器未自动打开,请将终端里打印的链接复制到浏览器中打开。成功后会提示「登录成功, 密钥凭证已被写入: ...」,可用 `tccli cvm DescribeRegions` 验证。
**Agent 场景**:当 Agent 通过工具执行 `tccli auth login` 时,该命令会**一直阻塞**直到用户完成浏览器登录(或超时)。Agent 应明确告知用户:「请打开终端/工具输出中显示的授权链接,在浏览器中完成登录;完成后该命令会自动结束。」
**多账户与登出**
- 默认账户凭证保存在 `default.credential`。指定账户名:`tccli auth login --profile user1`,凭证写入 `user1.credential`。
- 登出默认账户:`tccli auth logout`;登出指定账户:`tccli auth logout --profile user1`。
FILE:references/install.md
# 安装 TCCLI
**前提**:系统已安装 Python 2.7+ 与 pip。TCCLI 依赖 TencentCloudApi Python SDK,安装时会自动处理依赖。
**安装方式**:
```sh
# 方式一:pip(推荐,Windows / Mac / Linux 通用)
pip install tccli
# 若从 3.0.252.3 以下版本升级,需先卸载再装:
# pip uninstall tccli jmespath && pip install tccli
# 方式二:macOS Homebrew
brew tap tencentcloud/tccli
brew install tccli
# 更新:brew upgrade tccli
# 方式三:源码安装
# git clone https://github.com/TencentCloud/tencentcloud-cli.git && cd tencentcloud-cli && python setup.py install
```
验证安装:
```sh
tccli --version
```
FILE:references/refs.md
# 信息库检索步骤与示例
通过 curl + grep 检索业务、接口、最佳实践、数据结构。入口与结构见 SKILL.md「信息库」节。
## 发现业务
确定对应的 tccli 服务名(如 cvm、cbs)。
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/services.md | grep 云服务器
```
参考输出:
```
[cvm](service/cvm/index.md) | 云服务器 | 2017-03-12 | ...
```
## 检索业务最佳实践
优先检索是否有匹配当前场景的最佳实践。
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/practices.md | grep 重装
```
## 检索接口
若最佳实践未覆盖,在业务接口列表中检索(接口名即 tccli 的 <Action>)。
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/actions.md | grep "扩容\|磁盘"
```
## 阅读接口文档
确认参数、地域、版本(tccli 一般自动匹配版本):
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/action/ResizeInstanceDisks.md
```
## 阅读数据结构
文档中涉及的数据结构可进一步查看:
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/cvm/model/SystemDisk.md
```
Search the web using TencentCloud Web Search API (WSA). Prioritize using it when you need to retrieve network information.
---
name: tencent-yuanbao-standard-search
description: Search the web using TencentCloud Web Search API (WSA). Prioritize using it when you need to retrieve network information.
homepage: https://cloud.tencent.com/product/wsa
metadata: {"openclaw":{"emoji":"🔍︎","requires":{"bins":["python3"],"env":["TENCENTCLOUD_WSA_APIKEY"]}}}
---
# 元宝搜索标准版skill
元宝搜索标准版skill以互联网全网公开资源为基础,叠加腾讯优质内容生态,结合元宝APP联网搜索应用实践,从数据收录到智能检索召回,全链路构建AI友好搜索引擎。
针对用户对话中的关键问题(query)实时搜索互联网内容,返回最新相关的、高质量网页的内容信息,包括文章标题、摘要、url地址、发布时间、发布站点等,为大模型提供关键有效的决策信息,为用户提供更精准、更高质量的回答。
## 使用场景
通过接入元宝搜索标准版skill,为大模型拓展互联网信息实时检索能力,突破大模型知识困局,帮助大模型准确理解并快速回应用户的多样化问题。
- **信息查询**:搜索特定主题的互联网信息
- **热点追踪**:获取最新的新闻、资讯
- **知识检索**:查找百科、教程等知识类内容
## 工具特性
- **信息自动去噪,token消耗更友好**
- **毫秒级响应结果**
- **百亿索引内容库**
- **支持指定网址检索**
- **支持指定时间范围检索**
- **支持混合模式检索独家插件内容来源,如天气、股价、汇率等**
## 快速开始
### 开通服务并获取API KEY
使用元宝搜索标准版skill前,您需要在腾讯云联网搜索API控制台上完成准备工作:
- **[开通服务](https://console.cloud.tencent.com/wsapi/index?tab=apikey)**:登陆腾讯云联网搜索API控制台,开通标准版服务。
- **[创建服务API KEY](https://console.cloud.tencent.com/wsapi/index?tab=apikey)**:登陆腾讯云联网搜索API控制台,创建服务API KEY并保存。
- **[领取免费额度](https://console.cloud.tencent.com/wsapi/activity)**:登陆腾讯云联网搜索API活动专区页面,领取每日免费调用额度,活动限时,具体规则以页面信息为准。
### 配置API KEY环境变量
安装skill后,您需要将服务API KEY以环境变量的方式配置在Open Claw中。
变量名称:
TENCENTCLOUD_WSA_APIKEY
eg:'~/.openclaw/.env'
## 使用方法
** 注意:需要切换到该 skill 同目录下执行**
### 基于关键词搜索
```bash
python3 scripts/websearch.py --query="搜索关键词"
```
### 指定时间范围搜索相关信息
```bash
python3 scripts/websearch.py --query="搜索关键词" --freshness='year'
```
freshness包含以下选项:
|选项|解释|
|-----|----|
| ''| 空或者不带freshness不强调时间范围|
| day | 搜索最近一天的内容|
| week| 搜索最近一周的内容|
| month| 搜索最近一个月的内容|
| year| 搜索最近一年的内容|
### 搜索天气、金价、股价、医疗、汇率、油价、贵金属相关高时效高精度的垂类信息,混合搜索独家插件内容来源。
```bash
python3 scripts/websearch.py --query="搜索关键词" --mode=2
```
### 指定网页站点搜索
```bash
python3 scripts/websearch.py --query="搜索关键词" --site="sogou.com"
```
## 输入参数说明
| 参数 | 必填 | 说明 | 示例 |
|-------| ---- |------------------------|-------------------------------------------------------------------|
| query | 是 | 搜索关键词 | python3 scripts/websearch.py --query="搜索关键词" |
| site | 否 | 用于约束搜索内网的站点,要求是严格的站点格式 | python3 scripts/websearch.py --query="搜索关键词" --site="sogou.com" |
| mode | 否| 0-自然检索结果(默认),1-多模态VR结果(包括天气、金价、股价、医疗、汇率、油价、贵金属等相关信息),2-混合结果(多模态VR结果+自然检索结果)| python3 scripts/websearch.py --query="搜索关键词" --mode=2 |
| freshness| 否| 'day': 表示最近一天,'week': 表示最近一周,'month': 表示最近一个月,'year': 表示最近一年|python3 scripts/websearch.py --query="搜索关键词" --freshness='year'|
## 输出示例
输出结果为markdown格式
```
## 查询词:腾讯股价, 搜索结果:8条
1. [腾讯版“小龙虾”爆火,股价涨超7%、市值重回5万亿__财经头条__新浪财经](https://cj.sina.cn/articles/view/2587691232/9a3d08e002001mr1o%3Ffroms%3Dggmp%26vt%3D4)
- 摘要:同日,腾讯股价大涨。截至当日收盘,报553.5港元,涨7.27%,市值重回5万亿元。
- 内容发布时间:2026-04-22 00:56:18
- 网站:新浪财经
- 相关图片:https://k.sinaimg.cn/n/sinakd20260311s/475/w909h366/20260311/e169-cee03761e128389727727e2ea2a8766c.png/w700d1q75cms.jpg
2. [港股收盘 恒指收跌1.22% 联想集团领跑蓝筹 光通信概念股逆市大涨](http://stock.10jqka.com.cn/hks/20260422/c676189781.shtml)
- 摘要:股普遍承压,阿里(BABA)跌3.52%,腾讯(K80700)跌2.89%。光通信板块午后涨幅扩大,长飞光纤光缆(HK6869)、剑桥科技(HK6166)均大幅上涨,得益于光模块行业景气度提升及光纤市场量价齐升;AI次新股表现亮眼,极视角(HK6636)、爱芯元智(HK0600)、澜起科技(HK6809)、智谱(HK2513)等个股均有明显上涨。
- 内容发布时间:2026-04-22 00:00:00
- 网站:同花顺财经
3. [腾讯概念相关股票、成分股、图表和最新新闻 - 富途牛牛](https://www.futunn.com/sectors/Tencent-Ecosystem-BK1190)
- 摘要:腾讯概念 添加自选 2106.612 -45.690-2.12%已收盘 04/22 16:00 (北京) 2133.563最高价2097.097最低价2132.145今开2152.302昨收2.96亿股成交量6上涨15.86市盈率(静)140.48亿成交额3平盘0.18%换手率5.72万亿总市值11下跌5.32万亿流通值概览资讯评论腾讯概念板块 分时 5日日K 周K 月K 季K 年K
- 内容发布时间:2026-04-22 17:17:16
- 网站:
4. [港股收评:科指大跌1.93%!阿里腾讯跌近3%,长飞光纤逆势飙升17%创新高_财富号_东方财富网](https://caifuhao.eastmoney.com/news/20260422161755282718080)
- 摘要:今天港股大跌,主要是地缘风险、外部情绪和权重股表现这三大因素叠加的结果。截止收盘,恒生科技指数大幅下跌1.93%表现最弱,盘中曾跌至2.4%,恒生指数、国企指数分别下跌1.22%及1.59%。具体盘面上,作为市场风向标的权重科技股集体下挫拖累大市下行,阿里巴巴跌3.5%,腾讯、百度跌近3%,美团、京东跌超2%;昨日多只创新高的内银股集体低迷,中国银行、建设银行跌超2%;
- 内容发布时间:2026-04-22 16:23:38
- 网站:财富号东方财富网
- 相关图片:https://gbres.dfcfw.com/Files/iimage/20260422/73DECEA77DF5284865ACEDF050527BA4_w484h246.png
5. [腾讯控股](https://quote.eastmoney.com/us/TCTZF.html)
- 摘要:腾讯控股TCTZF(2026-04-22 03:52:48)交易币种:美元1美元约折合人民币6.8163元+加自选全球指数美股行情美股吧国际期货我的自选更多名称最新价涨跌幅三友医疗17.14-2.06%ST九州--科士达48.30-1.09%华谊兄弟1.91-3.05%爱尔眼科9.690.21%广电网络3.44-1.71%诚迈科技36.71-3.27%武商集团8.39-0.
- 内容发布时间:2026-04-22 03:52:48
- 网站:东方财富网
6. [笑看红绿的微博_微博](http://weibo.com/u/1334095513)
- 摘要:%。 看我有些惊异,太太说:“腾讯今年的涨幅高些,45%左右”。 腾讯2025年原来涨了这么多,我已经很久没看腾讯的股价了,应该全年没看一眼,不记得上次是 ...展开全文c 笑看红绿 2025年总结:收益平淡,生活平淡,知足常乐 2025年在股市的收益率是15.16% 有女生玩棉花娃娃,应该也是随着社会发展,年轻人情感消费提升的体现。 🔻就图1这事儿,老刘和吧友分享了一些截图。
- 内容发布时间:2026-04-22 19:35:54
- 网站:微博
7. [腾讯市值比阿里巴巴高1.4万亿,阿里“差”在哪里?-ZOL问答](https://ask.zol.com.cn/x/33980977.html)
- 摘要:已采纳 阿里差在增长预期、核心电商承压、云业务分拆遇阻、国际化进展慢,以及市场对消费复苏信心不足;而腾讯游戏、视频号、金融科技及AI布局更受青睐,估值溢价更高。 取消 评论 这样的现象在阿里巴巴这样的巨无霸企业身上是并不常见的,而且这样将会是最大的跌幅现象,会使阿里巴巴的股价降低,而且还会增加减持阿里的持仓 。 坊间传闻,美团王兴一直不喜欢马云。
- 内容发布时间:2026-04-22 09:27:29
- 网站:中关村在线
8. [野村下调腾讯音乐目标价至12.5美元](https://so.html5.qq.com/page/real/search_news?docid=70000021_69269e84da443152)
- 摘要:<p>野村将腾讯音乐美股的目标价从26美元下调至12.5美元。
- 内容发布时间:2026-04-22 12:26:38
- 网站:企鹅号
```
## 错误处理
使用本skill的常见报错况及处理方式如下:
- **服务不可用**:请登陆[腾讯云费用中心](https://console.cloud.tencent.com/expense/overview)检查账号是否欠费,并及时冲正。
- **服务未开通**:请登陆腾讯云[联网搜索API控制台](https://console.cloud.tencent.com/wsapi/index?tab=apikey),开通标准版服务。
- **未授权操作**:请登陆[联网搜索API控制台](https://console.cloud.tencent.com/wsapi/index?tab=apikey)创建服务API KEY并配置到环境变量(TENCENTCLOUD_WSA_APIKEY)中。若已配置,请检查填入的变量值是否正确。
FILE:scripts/websearch.py
import hashlib
import hmac
import json
import time
import os
from datetime import datetime, timedelta, timezone
from dataclasses import dataclass
from http.client import HTTPSConnection
import argparse
from typing import List
HOST = "api.wsa.cloud.tencent.com"
@dataclass
class Doc:
url : str
title: str
snippet: str
date: str
site: str
images: List[str]
@dataclass
class Option:
query:str
mode: int
site:str
from_time:int
to_time:int
def generate_header_and_payload(params:Option, secret_key:str):
# ************* 构造header *************
headers = {
"Authorization": "Bearer " + secret_key,
"Content-Type": "application/json; charset=utf-8",
"Host": HOST
}
# ************* 构造payload *************
body = {"Query": params.query, "Mode":params.mode}
if len(params.site) > 0:
body["Site"] = params.site
if params.from_time > 0 and params.to_time > 0:
body["FromTime"] = params.from_time
body["ToTime"] = params.to_time
payload = json.dumps(body)
return headers, payload
# api-key可前往官网控制台 https://console.cloud.tencent.com/wsapi/index 进行获取
def search(params:Option, secret_key:str):
headers,payload = generate_header_and_payload(params, secret_key=secret_key)
query = params.query
try:
req = HTTPSConnection(HOST)
req.request("POST", "/SearchPro", headers=headers, body=payload.encode("utf-8"))
resp = req.getresponse()
rsp = resp.read()
ret = json.loads(rsp)
docs, error_msg = parse(ret)
if error_msg:
print(f"## 搜索失败\n原因:{error_msg}")
return
print(f"## 查询词:{query}, 搜索结果:{len(docs)}条")
for idx, doc in enumerate(docs):
line = (
f"{idx + 1}. [{doc.title}]({doc.url})\n"
f" - 摘要: {doc.snippet}\n"
f" - 内容发布时间: {doc.date}\n"
f" - 网站: {doc.site}"
)
if doc.images and len(doc.images) > 0:
images_info = "\t".join(doc.images)
print(line + f"\n - 相关图片: {images_info}")
else:
print(line)
except Exception as err:
print(f"request error: {err}")
def parse(rsp:dict):
error_msg = ""
res = rsp.get("Response")
if res is None or not isinstance(res, dict):
print("response is null")
return [], error_msg
error = res.get("Error")
if error is not None:
error_msg = error.get("Message", "")
return [], error_msg
pages = res.get("Pages")
if pages is None:
return [], error_msg
docs = []
for page in pages:
json_page = json.loads(page)
is_vr = json_page.get("vr", False)
if is_vr:
display = json_page.pop("display", None)
if display is None:
continue
url = display.get("url")
title = display.get("title")
date = display.get("date")
content = page
docs.append(Doc(url=url, title=title, site="", date=date, snippet=content, images=[]))
else:
passage = json_page.get("passage")
url = json_page.get("url")
title = json_page.get("title")
site = json_page.get("site")
date = json_page.get("date")
if len(title) == 0 or len(passage) == 0:
continue
docs.append(Doc(url = url,title= title, site=site, date=date, snippet=passage, images=json_page.get("images")))
return docs, error_msg
if __name__=="__main__":
api_key = os.getenv("TENCENTCLOUD_WSA_APIKEY")
if api_key is None:
print("api-key are not set, 前往 https://console.cloud.tencent.com/wsapi/index 进行获取")
exit(1)
parser = argparse.ArgumentParser(description="websearch command arguments")
parser.add_argument("--query", type=str, help="search query", required=True)
parser.add_argument("--mode", type=int, help="返回结果类型,0-自然检索结果(默认),1-多模态VR结果,2-混合结果(多模态VR结果+自然检索结果)", default=0)
parser.add_argument("--site",type=str, help="指定站点搜索", default="")
parser.add_argument("--freshness", choices=['','day','week','month','year'], help="查询结果的时效性要求")
args = parser.parse_args()
if len(args.query) == 0:
print("invalid input arguments, query is empty")
exit(1)
reqOptions = Option(
query=args.query,
mode=args.mode,
site=args.site,
from_time=-1,
to_time=-1
)
current_time = datetime.now()
start_date = None
if args.freshness == 'day':
start_date = (current_time - timedelta(days=1))
elif args.freshness == 'week':
start_date = (current_time - timedelta(weeks=1))
elif args.freshness == 'month':
start_date = (current_time - timedelta(days=30))
elif args.freshness == 'year':
start_date = (current_time - timedelta(days=365))
if start_date is not None:
reqOptions.from_time = int(start_date.timestamp())
reqOptions.to_time = int(current_time.timestamp())
search(reqOptions, api_key)
A comprehensive skill for Tencent EdgeOne (Edge Security & Acceleration Platform), covering edge acceleration (DNS, certificates, caching, rule engine, L4 pr...
---
name: tencent-edgeone-skill
description: A comprehensive skill for Tencent EdgeOne (Edge Security & Acceleration Platform), covering edge acceleration (DNS, certificates, caching, rule engine, L4 proxy, load balancing), edge security (DDoS protection, Web protection, Bot management), edge media (real-time video / image processing), edge development (Edge Functions, EdgeOne Pages), and more. Use this skill whenever a user mentions any EdgeOne / EO-related configuration, operations, querying, or troubleshooting needs.
version: 1.0.0
metadata:
openclaw:
requires:
bins:
- tccli
- gunzip
anyBins:
- curl
- wget
- jq
- python3
config:
- ~/.tccli/default.credential
homepage: https://edgeone.ai
---
# Tencent EdgeOne Skill
A comprehensive Tencent EdgeOne skill that routes user requests to the appropriate module and loads the corresponding reference document.
Knowledge about EdgeOne APIs, configuration options, limits, and pricing may be outdated.
**Prefer retrieval over pre-trained knowledge** — the reference files in this skill are only a starting point.
> All tasks **must be completed by calling APIs**.
> See `references/api/README.md` for API calling conventions, environment checks, etc. **(must be read before starting any task)**.
## Security Red Lines
- **Write operations require user confirmation**: All write operations (Create\* / Modify\* / Bind\* / Delete\* / Apply\*, etc.) **must** clearly explain the action and its impact to the user before execution, and wait for user confirmation before calling the API.
- **Never** ask the user for SecretId / SecretKey
- **Refuse** any operation that might print credentials
## Interaction & Execution Guidelines
- **Use structured interaction tools**: When asking questions, requesting choices, or confirming operations, if the current environment provides `ask_followup_question` or similar structured interaction tools, you **must** prefer using them (instead of plain-text questions) so that the user can directly click options, reducing ambiguity and improving interaction efficiency. **Do not omit candidate options** — if there are too many to list in full, **must** state the total number first, show the most relevant items, and keep an "Other (please enter)" option as the last choice.
- **Prefer scripts for bulk / repetitive tasks**: For tasks involving large datasets or repetitive operations (batch purge, batch query, loop operations, etc.), prefer writing a script to execute everything at once rather than calling APIs one by one manually.
## Module Entry Points
Match the user's request to the appropriate module, load its entry document, and follow the instructions.
| Module | Entry | Description |
|---|---|---|
| API | `references/api/README.md` | Calling conventions, tool installation, credential configuration, API discovery, zone & domain discovery (ZoneId lookup) |
| Acceleration | `references/acceleration/README.md` | Site onboarding, cache purge / prefetch, certificate management |
| Security | `references/security/README.md` | Security policy template audit, blocklist IP group query, security report |
| Observability | `references/observability/README.md` | Traffic Daily Report Generation, Origin Health Inspection, Offline Log Download and Analysis |
## Fallback Retrieval
If the user's request **cannot match any module above**, or the module's reference files do not cover the scenario, fall back in the following order:
1. First read `references/api/api-discovery.md` and try to find the relevant API through API discovery.
2. If still unresolved, search the [Tencent EdgeOne product documentation](https://edgeone.ai/document) for the latest information.
When reference files conflict with official documentation, **the official documentation takes precedence**.
FILE:references/acceleration/README.md
# EdgeOne Site Acceleration Reference
Configuration and operational guidance for site onboarding, domain management, cache purge/prefetch, and HTTPS certificates.
## Files in This Directory
| File | Risk Level | Trigger Scenarios (User says...) |
|---|---|---|
| `zone-onboarding.md` | Medium-High | "Onboard example.com to EO" "Create a new site" "Apply for free certificate after onboarding" |
| `cache-purge.md` | Medium | "Purge all cache under /static/" "Prefetch these URLs" "Check purge task status" |
| `cert-manager.md` | Medium | "Renew certificate that's expiring soon" "Bind new certificate to these domains" "Check certificate status" |
FILE:references/acceleration/cache-purge.md
# Cache Purge and Prefetch
Manage EdgeOne node cache: query quotas, purge cache (URL / Directory / Host / All / Cache Tag), prefetch URLs, and check task progress. Supports batch URL input from files or paste.
## Core Interaction Guidelines
1. **Site Selection and Confirmation**: Before executing any cache operation, users must first confirm the target site (see Scenario 0)
2. **Check Quota Before Submission**: Before executing CreatePurgeTask / CreatePrefetchTask, call DescribeContentQuota to display remaining quota; warn users if insufficient
3. **Batch URL Input**: Support users to input URLs in batch from files or by pasting (see Scenario E)
4. **Poll Task Progress**: Actively query progress after task submission until completion or timeout
## Scenario 0: Select Site
**Trigger**: Before users request cache purge or URL prefetch, the target site must be confirmed first.
**Steps**:
1. **Call DescribeZones to query site list**
- **Important**: Filter out sites with `Status` as `initializing` (these sites are still initializing and haven't completed creation)
- **Critical**: **Must use pagination** to retrieve all sites:
- Set `Limit=100` (maximum value)
- Set `Offset=0` initially, increment by 100 each iteration
- Loop until `Offset + Limit >= TotalCount`
- Merge all paginated results
- Refer to [zone-discovery.md](../api/zone-discovery.md) for detailed pagination implementation
- Only display available sites
2. **Determine the number of sites**:
**a) Only one site**
- Directly use this site without user selection
- Continue to subsequent operations
**b) Multiple sites**
- List all available sites, including:
- Site domain name (ZoneName)
- Alias Zone Name (AliasZoneName, if any)
- Site ID (ZoneId)
- Access mode (Type)
- Guide users to select the site to operate on
**c) No available sites**
- Prompt user: "No available sites under current account, please create a site first"
- Terminate operation
3. **Handle sites with same name**:
- If multiple sites with the same name exist (same ZoneName), they must be distinguished by `AliasZoneName` (Alias Zone Name identifier)
- Display format: `Site domain (identifier)` or `Site domain [identifier]`
- Example: `example.com (prod)` and `example.com (test)`
4. **Get Site ID**:
- After user confirms the site, record the site's `ZoneId`
- All subsequent API calls use this `ZoneId`
> **Important Notes**:
> - Site selection is a prerequisite for all cache operations and cannot be skipped
> - Alias Zone Name (AliasZoneName) is used to distinguish sites with the same name created with different access modes (CNAME, DNSPod hosting)
> - If the user explicitly specifies the site domain or identifier in the request, you can directly use this information to query the corresponding site
## Scenario A: Query Quota
**Trigger**: User says "how many more can I purge", "check quota", "how much prefetch quota left".
> **Prerequisite** (optional): If user hasn't specified a site, complete [Scenario 0: Select Site](#scenario-0-select-site) first
Call `DescribeContentQuota`, passing parameters:
- `ZoneId`: Site ID (optional, query account-level quota if not provided)
**Output suggestion**: Display quota usage for each type in a table, marking types with less than 10% remaining.
## Scenario B: Cache Purge
**Trigger**: User says "purge cache", "clear CDN cache", "purge URL", "purge directory", "purge entire site".
> **Prerequisites**:
> 1. Complete [Scenario 0: Select Site](#scenario-0-select-site) to confirm the site to operate on
> 2. Call DescribeAccelerationDomains to confirm available acceleration domains under the site
> 3. Call DescribeContentQuota (Scenario A) to display remaining quota; warn user if insufficient for the corresponding type
### B1: Confirm Purge Type and Method
Before executing purge, **must** have users confirm the following information:
#### 1. Purge Type
| Type | Parameter Value | Description | Impact Scope |
|------|--------|------|----------|
| **URL Purge** | `purge_url` | Purge specified URLs | Exactly matched URLs |
| **Directory Purge** | `purge_prefix` | Purge all resources under specified directory | All files under directory and subdirectories |
| **Hostname Purge** | `purge_host` | Purge all resources under specified domain | All cache of the entire acceleration domain |
| **Full Site Purge** | `purge_all` | Purge all resources under the site | ⚠️ All cache on all nodes of the site |
| **Cache Tag Purge** | `purge_cache_tag` | Purge by cache tag | All cache with specified tags |
#### 2. Purge Method
Purge method is **only valid for the following three purge types**:
- ✅ **Directory Purge** (`purge_prefix`)
- ✅ **Hostname Purge** (`purge_host`)
- ✅ **Full Site Purge** (`purge_all`)
Other purge types (URL purge, Cache Tag purge) do not support the purge method parameter.
**Available Values**:
| Method | Parameter Value | Description | Recommended Scenario |
|------|--------|------|----------|
| **Invalidate Cache** | `invalidate` | Mark cache as expired, validate with origin on next request | Default method, less pressure on origin |
| **Delete Cache** | `delete` | Directly delete cache, always fetch from origin on next request | Emergency updates, forced refresh scenarios |
> **Important Notes**:
> - The `invalidate` method keeps the cache but marks it as expired; on the next request, it validates with the origin through mechanisms like If-Modified-Since, and can continue using cache if content hasn't changed
> - The `delete` method directly deletes the cache; all subsequent requests will fetch from origin, which will increase origin load in a short time
#### 3. User Confirmation Process
**Prompt user**:
1. Display purge types and their impact scope
2. If selecting `purge_prefix`, `purge_host`, or `purge_all`, ask about purge method
3. If selecting `purge_all` (full site purge), **must** specially warn:
> ⚠️ **Full site purge is a high-impact operation**: It will clear all node cache of this site; in a short time, a large number of requests will fetch from origin, which may cause origin pressure to surge. Please confirm whether to continue?
4. Wait for user's explicit confirmation before executing
> **No automatic purge**: Cache purge will invalidate node cache; subsequent requests will fetch the latest content from origin, which may increase origin load. **Must** explain the purge type and impact scope to users and wait for explicit confirmation before execution.
### B2: Execute Purge
**Call** `CreatePurgeTask`, passing parameters:
- `ZoneId`: Site ID (from Scenario 0)
- `Type`: Purge type
- `Method`: Purge method (only valid when Type is `purge_prefix`, `purge_host`, or `purge_all`)
- `Targets`: List of URLs / directories / domains to purge
**Follow-up**: Inform user that the task has been submitted and provide JobId. If confirmation of execution result is needed, go to [Scenario D](#scenario-d-query-task-progress).
## Scenario C: URL Prefetch
**Trigger**: User says "prefetch URL", "prefetch cache", "prefetch", "preload resources".
> **Prerequisites**:
> 1. Complete [Scenario 0: Select Site](#scenario-0-select-site) to confirm the site to operate on
> 2. Call DescribeContentQuota (Scenario A) to display remaining `prefetch_url` quota
> 3. (Optional) Call DescribePrefetchOriginLimit to query origin rate limit for the target domain
URL prefetch actively fetches resources from origin to edge node cache, suitable for preloading hot resources before major promotions or version releases.
### C1: URL Format Check
Before executing prefetch, **must** check if URL format meets requirements:
**✅ Supported URL Formats**:
- Complete HTTP/HTTPS URL: `https://example.com/path/to/file.jpg`
- URL with query parameters: `https://example.com/api/data?id=123&type=json`
- Specific file path: `https://cdn.example.com/images/banner.png`
**❌ Unsupported URL Formats**:
- ⚠️ **URLs with wildcards**: `https://example.com/path/*` or `https://example.com/*.jpg`
- Directory paths: `https://example.com/path/` (for directory prefetch, use multiple specific file URLs)
- Incomplete URLs: `example.com/path` (missing protocol)
**Check Rules**:
1. URL must start with `http://` or `https://`
2. URL cannot contain wildcards `*` or `?` (except `?` in query parameters)
3. It's recommended that each URL points to a specific file resource
**Processing Flow**:
```
Iterate through user-provided URL list
├─ Check if contains wildcards (* ?)
│ ├─ Contains → Prompt user: "Prefetch doesn't support wildcard URLs, please provide specific file URLs"
│ └─ Doesn't contain → Continue
├─ Check protocol
│ ├─ Missing http/https → Prompt user to add protocol
│ └─ Has protocol → Continue
└─ Add to valid URL list
```
> **Important Notes**:
> - Prefetch only supports URL granularity, not directory or domain level
> - To prefetch an entire directory, you need to provide a complete URL list of all files under that directory
> - Wildcard purge is partially supported in cache purge scenarios (such as directory purge), but not supported in prefetch scenarios
### C2: Execute Prefetch
**Call** `CreatePrefetchTask`, passing parameters:
- `ZoneId`: Site ID (from Scenario 0)
- `Targets`: List of URLs to prefetch (passed format check)
**Follow-up**: Inform user that the task has been submitted and provide JobId. If confirmation of execution result is needed, go to [Scenario D](#scenario-d-query-task-progress).
### C3: Query Prefetch Origin Rate Limit (DescribePrefetchOriginLimit)
> This interface is a whitelist beta feature, only use when user mentions "prefetch rate limit".
Call `DescribePrefetchOriginLimit`.
**Output suggestion**: If the domain has rate limit configuration, remind the user of the current bandwidth limit before prefetching; large-scale prefetch may be affected by this limit.
## Scenario D: Query Task Progress
**Trigger**: User says "is purge done", "check task progress", "prefetch status".
### D1: Query Purge Tasks
Call `DescribePurgeTasks`.
### D2: Query Prefetch Tasks
Call `DescribePrefetchTasks`, parameters and Filters similar to purge tasks.
**Output suggestion**: Display task list in a table, marking tasks with `failed` and `timeout` status. If there are failed tasks, suggest users to check if URLs are correct or retry later.
> Prefetch tasks additionally have `invalid` status, indicating origin response is non-2xx; need to check origin service.
### D3: Auto-poll Progress After Submission
After submitting purge / prefetch tasks, should actively poll task status until terminal state:
1. Get `JobId` after submitting task
2. Wait 5-10 seconds before querying status
3. If still `processing`, continue waiting and retry (suggest 10-second interval)
4. If reaching terminal state (`success` / `failed` / `timeout` / `canceled`), summarize results and display to user
> Usually URL purge completes in 1-2 minutes, directory / Host purge in 3-5 minutes; prefetch time depends on resource size and quantity.
## Scenario E: Batch URL Input
**Trigger**: User provides a large number of URLs (read from file or directly paste multiple lines).
### E1: Extract URLs from User's Pasted Text
When user pastes multiple lines of URLs:
1. Split text by lines, one URL per line
2. Filter out empty lines and comment lines (starting with `#`)
3. Ensure each URL starts with `http://` or `https://`
4. Summarize valid URL count and display to user for confirmation
### E2: Read URL List from File
When user says "import from file", "read URL list file":
1. Read user-specified file (support `.txt`, `.csv` and other plain text formats)
2. Parse by lines, filter empty lines and comments
3. Display parsed URL count and first few samples, ask user to confirm
### E3: Batch Submission Considerations
- **Check Quota**: First query DescribeContentQuota to ensure remaining quota ≥ URL count
- **Single Batch Limit**: Number of URLs submitted each time is subject to single batch upper limit; automatically submit in batches when exceeding limit
- **URL Deduplication**: Deduplicate before submission to avoid wasting quota
- **Result Summary**: After all batches are submitted, summarize JobId list and failed items, query progress uniformly
FILE:references/acceleration/cert-manager.md
# Certificate Automation Management
Manage EdgeOne domain HTTPS certificates: query certificate status, apply for free certificates, deploy custom certificates.
## Scenario A: Query Certificate Status
**Trigger**: User wants to view certificate list or check expiration time.
### A1: Locate Target Site
Call `DescribeZones`, using `zone-name` filter to match the site name specified by the user.
> **Important**: Filter out sites with `Status` as `initializing` (these sites are still initializing and haven't completed creation).
Handle based on results in three cases:
**Case 1: Only 1 available site matched**
Directly use this site's `ZoneId`, proceed to A2.
**Case 2: Multiple sites with same name matched**
Display all **available** matching results to user, listing key information for distinction, **wait for user's explicit selection** before continuing:
```
Found multiple sites named "xxx.com", please confirm which one to query:
1. ZoneId: zone-aaa Alias: prod Access Mode: NS Access Created: 2024-01-01
2. ZoneId: zone-bbb Alias: test Access Mode: CNAME Access Created: 2025-06-01
Please reply with the number or ZoneId.
```
> After receiving user's response, use the selected `ZoneId` to proceed to A2.
**Case 3: No available sites**
Prompt user: "No available site 'xxx.com' found, please check the site name or wait for site initialization to complete."
### A2: Query Domain Certificate Information
Call `DescribeAccelerationDomains`, read certificate information for each domain from the `AccelerationDomains[].Certificate` field in the response.
> You can specify a domain to query via `Filters.domain-name`; not passing Filters returns all domains under the site.
Key fields in each domain's `Certificate` structure:
| Field | Meaning |
|---|---|
| `Certificate.Mode` | Certificate configuration mode: `disable` / `eofreecert` / `eofreecert_manual` / `sslcert` |
| `Certificate.List[].CertId` | Certificate ID |
| `Certificate.List[].Alias` | Certificate alias |
| `Certificate.List[].Type` | Certificate type: `default` / `upload` / `managed` |
| `Certificate.List[].ExpireTime` | Expiration time |
| `Certificate.List[].Status` | Deployment status: `deployed` / `processing` / `applying` / `failed` / `issued` |
| `Certificate.List[].SignAlgo` | Signature algorithm |
**Output suggestion**: Display certificate information for each domain in table format, marking entries that are about to expire (≤30 days) or have abnormal status (`failed` / `applying`).
## Scenario B: Apply and Deploy Free Certificate
**Trigger**: User says "apply for free certificate", "certificate is expiring soon", "renew certificate".
### B0: Locate Target Site
If user hasn't directly provided ZoneId, call `DescribeZones` using `zone-name` filter to match the site name specified by the user.
> **Important**: Filter out sites with `Status` as `initializing` (these sites are still initializing and haven't completed creation).
- **Only 1 available site matched**: Directly use this site's `ZoneId`, proceed to B1.
- **Multiple sites with same name matched**: Display all **available** matching results to user, **wait for explicit selection** before continuing (display format same as Scenario A).
- **No available sites**: Prompt user: "No available site 'xxx.com' found, please check the site name or wait for site initialization to complete."
### Access Mode Determination
The result of calling `DescribeZones` is also used to determine the access mode (`Type` field), taking different routes based on the result:
| Access Mode | Free Certificate Application Method |
|---|---|
| NS Access / DNSPod Hosting | **Automatic Validation** — Directly call ModifyHostsCertificate |
| CNAME Access | **Manual Validation** — Need to call ApplyFreeCertificate first, complete validation, then deploy |
### B1: NS Access / DNSPod Hosting (Automatic Validation)
Call `ModifyHostsCertificate`.
> **Confirmation Prompt**: Deploying certificate will affect the domain's HTTPS service, need user confirmation before execution.
### B2: CNAME Access (Manual Validation)
Requires 4 steps:
**Step 1**: Call `ApplyFreeCertificate` to initiate application.
**Step 2**: Based on validation information in response, inform user to complete configuration.
> After informing user, **wait for user to confirm configuration completion** before continuing to next step.
**Step 3**: Call `CheckFreeCertificateVerification` to check validation result
- Success: Response contains certificate information, indicating certificate has been issued
- Failure: Need to check if validation configuration is correct
**Step 4**: Call `ModifyHostsCertificate` to deploy free certificate.
> **Confirmation Prompt**: Deploying certificate will affect the domain's HTTPS service, need user confirmation before execution.
## Scenario C: Deploy Custom Certificate
**Trigger**: User says "configure custom certificate", "uploaded certificate", or provides CertId.
### C0: Locate Target Site
If user hasn't directly provided ZoneId, call `DescribeZones` using `zone-name` filter to match the site name specified by the user.
> **Important**: Filter out sites with `Status` as `initializing` (these sites are still initializing and haven't completed creation).
- **Only 1 available site matched**: Directly use this site's `ZoneId`, proceed to next step.
- **Multiple sites with same name matched**: Display all **available** matching results to user, **wait for explicit selection** before continuing (display format same as Scenario A).
- **No available sites**: Prompt user: "No available site 'xxx.com' found, please check the site name or wait for site initialization to complete."
### C1: Query SSL Certificates Applicable to Target Domain
If user hasn't provided CertId, or wants to select from existing certificates, call `ssl:DescribeCertificates` to query certificate list, then filter out certificates applicable to the target domain.
**Call Parameter Suggestions**:
- `SearchKey`: Pass in target domain (e.g., `a-1.qcdntest.com`), can fuzzy match domain field to narrow return range
- `CertificateType`: Pass `SVR`, only query server certificates (exclude client CA certificates)
- `Limit`: Suggest passing `1000` to ensure no omissions
**Filter Rules**: For each returned certificate, check if any entry in the `SubjectAltName` list matches the target domain:
| Match Type | Description | Example |
|---|---|---|
| Exact Match | `SubjectAltName` has an entry exactly the same as target domain | `a-1.qcdntest.com` |
| Wildcard Match | `SubjectAltName` has a `*.xxx` entry, and target domain is its direct subdomain (only one level) | `*.qcdntest.com` matches `a-1.qcdntest.com` |
**Availability Determination**: After filtering matching certificates, mark availability status for each certificate based on the following fields:
| Field | Meaning | Availability Condition |
|---|---|---|
| `Status` | Certificate status | Must be `1` (Approved) |
| `CertEndTime` | Expiration time | Days until today > 0 (Not expired) |
| `Deployable` | Whether deployable | Must be `true` |
**Output suggestion**: Display all matching certificates in a table, marking availability:
```
Certificates applicable to a-1.qcdntest.com:
Cert ID Alias Expiration Days Left Status Deployable Availability
------------ -------------- -------------------- ---------- ---------- ----------- ------------
zVq87w0D my-cert 2032-09-23 05:10:56 2371 days Approved ✅ ✅ Available
QxbtGBIM old-cert 2025-01-01 00:00:00 -86 days Expired ❌ ❌ Expired
```
If no matching certificate is found, inform user that they need to first go to [SSL Certificate Console](https://console.cloud.tencent.com/ssl) to upload or apply for a certificate covering this domain.
### C2: Deploy Certificate
After user selects certificate from C1 results, call `ModifyHostsCertificate` (`Mode=sslcert`, `ServerCertInfo[{CertId}]`).
> **No Automatic Deployment**: **Must** confirm deployment domain and certificate ID with user before execution.
## Scenario D: Batch Certificate Inspection
**Trigger**: User says "check certificates for all domains", "which certificates are expiring soon".
### Process
1. Call `DescribeZones` to get all sites
- **Important**: Filter out sites with `Status` as `initializing` (these sites are still initializing and haven't completed creation)
- **Critical**: **Must use pagination** to retrieve all sites:
- Set `Limit=100` (maximum value)
- Set `Offset=0` initially, increment by 100 each iteration
- Loop until `Offset + Limit >= TotalCount`
- Merge all paginated results
- Refer to [zone-discovery.md](../api/zone-discovery.md) for detailed pagination implementation
2. Call `DescribeAccelerationDomains` for each **available** site, read certificate information from each `AccelerationDomains[].Certificate` field in the response
3. Summarize output, marking the following anomalies:
- Certificates with `Certificate.List[].Status` as `failed` or `applying`
- Certificates with `Certificate.List[].ExpireTime` ≤30 days from today
- Domains with `Certificate.Mode` as `disable` or `Certificate` is null (no certificate configured)
### Output Format Suggestion
```markdown
## Certificate Inspection Report
| Site | Domain | Cert ID | Expiration | Days Left | Status |
|---|---|---|---|---|---|
| example.com | *.example.com | teo-xxx | 2026-04-15 | 29 days | Expiring Soon |
| example.com | api.example.com | teo-yyy | 2026-09-01 | 168 days | Normal |
```
FILE:references/acceleration/zone-onboarding.md
# Site One-Click Onboarding Guide
End-to-end domain onboarding to EdgeOne: confirm plan → create site → verify ownership → add acceleration domain → apply and deploy certificate.
## Important Concepts
### Alias Zone Name (AliasZoneName)
When you create two or more sites with the same site name, you need to use an **Alias Zone Name** to distinguish them.
**Use Cases**:
- Same domain using different access modes (CNAME access, DNSPod hosting access)
- Same domain configured with different acceleration or security strategies
**Format Requirements**:
- Allows combination of numbers, English letters, `.`, `-`, and `_`
- Length limit: within 200 characters
- Examples: `site-prod`, `site-test`, `site_backup`
**Access Mode Restrictions**:
- ✅ **CNAME Access**: Supports creating multiple sites with same name
- ✅ **DNSPod Hosting Access**: Supports creating multiple sites with same name
- ❌ **NS Access**: A domain can only be accessed via NS once, does not support sites with same name
- ⚠️ **Mutual Exclusion Rule**: Domains accessed via NS cannot use other access modes; domains accessed via CNAME/DNSPod hosting cannot use NS access
### Site Status Determination Logic
When displaying site status to users, the effective status should be determined by evaluating the following fields from `DescribeZones` response **in order** (first match wins):
| Priority | Condition | Display Status |
|---|---|---|
| 1 | `Status == "initializing"` | Initializing — **must be filtered out, do not display to user** |
| 2 | `Status == "forbidden"` | forbidden |
| 3 | `Status == "deleted"` | Deleted |
| 4 | `ActiveStatus == "changing"` | Changing |
| 5 | `ActiveStatus == "inPausing"` | Pausing |
| 6 | `Paused == true` | Paused |
| 7 | None of the above | Active |
> **Important**: This logic must be applied in all scenarios where site status is displayed, including site selection (D0) and status queries (F).
## Access Mode Description
EdgeOne supports four site access modes:
### Access Modes with Domain
1. **DNSPod Hosting Access** (dnspodAccess)
- **Prerequisites**: Domain is hosted in DNSPod and status is normal
- **Advantages**: EdgeOne can directly and automatically complete ownership verification and configuration, best experience
- **Recommendation**: Strongly recommended if conditions are met
- **Available Features**: Layer 7 acceleration, Layer 4 proxy, security protection, edge functions
2. **NS Access** (full)
- **Requirements**: Need to switch NS records to EdgeOne-provided Name Servers at domain registrar
- **Characteristics**: EdgeOne fully takes over domain DNS resolution
- **Available Features**: Layer 7 acceleration, Layer 4 proxy, security protection, edge functions, DNS resolution
3. **CNAME Access** (partial)
- **Requirements**: Need to manually add TXT record to verify ownership
- **Characteristics**: Only need to configure CNAME record pointing to EdgeOne, NS record unchanged
- **Available Features**: Layer 7 acceleration, Layer 4 proxy, security protection, edge functions
### Access Mode without Domain
4. **No Domain Access** (noDomainAccess)
- **Applicable Scenarios**: Temporarily no domain, or only need Layer 4 proxy and edge functions
- **Characteristics**:
- Can create site without providing domain
- No ownership verification needed
- Can directly use Layer 4 proxy and edge functions after creation
- **Available Features**: Only supports Layer 4 proxy, edge functions
- **Future Extension**: After site creation, can add Layer 7 acceleration domains anytime
## End-to-End Process Overview
### Access Process with Domain
```
1. Confirm Plan (DescribePlans / DescribeAvailablePlans / CreatePlan)
↓
2. Create Site (CreateZone)
├─ B0: Determine if domain meets specifications
│ ├─ Meets → Continue with domain access process
│ └─ Doesn't meet → Prompt to use no-domain access
├─ B1: Detect DNSPod hosting status (DescribeDomain)
│ ├─ Meets conditions: Prioritize recommending DNSPod hosting access
│ └─ Doesn't meet: Provide CNAME access and NS access
├─ B1.5: Domain conflict pre-check (CreateZone DryRun)
│ ├─ No conflict → Continue creation
│ ├─ CNAME/DNSPod conflict → Require AliasZoneName
│ └─ NS conflict → Terminate creation (NS access is exclusive)
├─ B2: Confirm access mode and parameters
│ ├─ Select access mode (DNSPod hosting / NS / CNAME)
│ ├─ Select acceleration area (mainland/global requires ICP filing)
│ └─ If conflict exists, provide Alias Zone Name
├─ DNSPod hosting access: Automatically complete validation, jump directly to step 4
├─ NS access: Switch DNS server
└─ CNAME access: Add TXT record or file validation
↓
3. Verify Ownership (VerifyOwnership) — Can be skipped, verify later
↓
4. Add Acceleration Domain (CreateAccelerationDomain)
↓
5. Apply and Deploy HTTPS Certificate (see cert-manager.md)
↓
Onboarding Complete
```
### No Domain Access Process
```
1. Confirm Plan (DescribePlans / DescribeAvailablePlans / CreatePlan)
↓
2. Create Site (CreateZone, Type = noDomainAccess)
- Keep ZoneName empty
- Keep Area empty
- No ownership verification needed
↓
3. Configure Layer 4 proxy or edge functions
↓
Onboarding Complete
```
> Can check verification status anytime via DescribeIdentifications.
> For certificate-related queries and operations, refer to [cert-manager.md](cert-manager.md).
## Scenario A: Confirm Plan
**Trigger**: First step of the process, must confirm plan before creating site.
### A1: Query Existing Plans (DescribePlans)
Call `DescribePlans` to query plans under the account.
#### Filter Logic
From returned plan list, filter available plans by the following conditions:
1. **Bindable**: `Bindable == "true"`
2. **Normal Status**: `Status == "normal"`
3. **Sufficient Quota**: Bound sites < Site quota limit
> Only plans meeting all 3 conditions above can be used for binding.
#### Has Available Plans
> **No Automatic Binding**: Binding plan will consume site quota. **Must** first display plan information to user and wait for explicit selection before binding; never decide on your own.
**Must display all available plans** for user selection, no omissions or truncation:
- If **Only 1 plan**: Still need user confirmation before use
- If **Multiple plans**:
- **Display Info**: Plan ID, plan type, bound site count
- **Display Method**: If plan count exceeds structured interaction tool's option limit, display in batches or provide input method to ensure user can see and select all plans
- **Suggested Sorting**: Sort by bound site count from least to most, convenient for user to select plans with sufficient quota
- User can also choose **not to bind existing plan**, go to A2 to purchase new plan
#### No Available Plans
Go to A2.
### A2: Purchase New Plan (DescribeAvailablePlans → CreatePlan)
Call `DescribeAvailablePlans` to query plan types available for purchase under current account, display options to user.
> **No Automatic Purchase**: Purchasing plan will incur actual charges. **Must** display plan type and pricing info to user, and wait for explicit confirmation before calling `CreatePlan`. Never skip confirmation or decide on your own.
After user explicit confirmation, call `CreatePlan` to purchase plan.
If user confirms not to purchase, remind: **Site needs to be bound to plan to provide normal service**. Sites not bound to plan will be in `init` status and unable to take effect.
### Next Step
After plan confirmation, carry PlanId to [Scenario B: Create Site](#scenario-b-create-site).
## Scenario B: Create Site
**Trigger**: User says "onboard example.com to EdgeOne", "create site", "create new site", or subsequent step after plan confirmation.
> If user directly triggers this scenario (without going through Scenario A), must first guide through [Scenario A: Confirm Plan](#scenario-a-confirm-plan) before continuing.
> **No Automatic Creation**: Creating site will consume plan's site quota. **Must** confirm site domain, access mode, and acceleration area with user before execution; never decide on your own.
### B0: Determine Access Mode Type
**First determine if the site name provided by user meets domain specifications**:
1. **Meets domain specifications** (e.g., `example.com`, `test.com.cn`):
- Enter access process with domain → Go to [B1: Detect DNSPod Hosting Status](#b1-detect-dnspod-hosting-status)
2. **Doesn't meet domain specifications or user explicitly indicates no domain**:
- Prompt user: "The site name you provided doesn't meet domain specifications. EdgeOne supports no-domain access mode, which is limited to Layer 4 proxy and edge functions, and Layer 7 acceleration domains can be added later. Do you want to use no-domain access mode?"
- If user confirms → Go to [B3: No Domain Access](#b3-no-domain-access)
- If user declines → Prompt user to provide valid second-level domain
> **Domain Specifications**: Valid second-level domain (e.g., example.com), does not accept third-level and above domains (e.g., www.example.com) as site name.
### B1: Detect DNSPod Hosting Status
Before displaying access mode options to user, first try to detect if domain is hosted in DNSPod to prioritize recommending DNSPod hosting access mode.
**Steps:**
1. **Call DNSPod's DescribeDomain interface**, pass in the domain to onboard
2. **Determine if DNSPod hosting access conditions are met**:
- Domain exists in DNSPod
- `Status` field is `ENABLE` or `LOCK`
- `DnsStatus` field is empty string (normal status)
3. **Exception Handling**:
- If interface call fails (e.g., no permission, service unavailable), handle silently, don't display DNSPod hosting access option
- If domain doesn't exist or status doesn't meet conditions, don't display DNSPod hosting access option
> **Note**: The `DescribeDomain` interface only returns errors for no permission or domain not found, won't return service authorization related error codes.
### B1.5: Domain Conflict Pre-check
Before formally creating site, perform pre-check to determine if domain has been accessed to avoid creation failure due to domain conflict.
**Steps:**
1. **Call CreateZone interface for pre-check**
- Pass parameter `DryRun: true`
- Pass domain to onboard `ZoneName`
- Pass user-selected access mode `Type`
2. **Determine pre-check result**:
**a) Pre-check succeeds** (no error returned)
- Indicates domain hasn't been accessed, can directly create
- Go to [B2: Confirm Access Mode and Parameters](#b2-confirm-access-mode-and-parameters)
**b) Returns `ResourceInUse.Zone` error**
- Indicates domain has been accessed via CNAME or DNSPod hosting
- Prompt user: "This domain has been accessed, need to set Alias Zone Name to distinguish different sites"
- Guide user to provide `AliasZoneName` (Alias Zone Name)
- Go to [B2: Confirm Access Mode and Parameters](#b2-confirm-access-mode-and-parameters)
**c) Returns `ResourceInUse.Others` or `ResourceInUse.OthersNS` error**
- Indicates domain has been accessed via **NS access**
- Prompt user: "This domain has been accessed via NS access. NS access sites can only be accessed once and cannot use other access modes. If you want to use other access modes, please delete the existing NS access site first."
- **Terminate creation process**
> **Important Notes**:
> - NS access has exclusivity; a domain can only be accessed via NS once
> - Domains accessed via NS cannot use CNAME or DNSPod hosting access
> - Domains accessed via CNAME or DNSPod hosting cannot use NS access (but can continue using CNAME or DNSPod hosting to create sites with same name)
### B2: Confirm Access Mode and Parameters
**Call** `CreateZone`. User needs to provide:
- **Site Domain**: Root domain to onboard
- **Access Mode**: Display available options based on B1 detection results
- If DNSPod hosting conditions are met: **Prioritize recommending** DNSPod hosting access (dnspod), also provide CNAME access (partial) and NS access (full) options
- If conditions not met: Only provide CNAME access (partial) and NS access (full) options
- **Acceleration Area** (Area): Optional values are mainland (Mainland China), overseas (Global excluding Mainland China), global (Global)
- **mainland (Mainland China)**: ⚠️ **Requires domain to have completed ICP filing with MIIT**
- **global (Global)**: ⚠️ **Requires domain to have completed ICP filing with MIIT**
- **overseas (Global excluding Mainland China)**: No filing requirement
- **Alias Zone Name** (AliasZoneName): **Only needed when B1.5 pre-check returns domain conflict**
- Provided by user to distinguish sites with same name
- Format requirements: combination of numbers, English letters, `.`, `-`, `_`, within 200 characters
> **Important Notes**:
> - When selecting `mainland` or `global` area, must remind user to confirm domain has completed ICP filing, otherwise site will not be able to onboard and use normally.
> - If B1.5 pre-check finds domain conflict, must require user to provide `AliasZoneName` parameter.
Suggest passing PlanId directly when creating.
> When not passing PlanId, site is in `init` status, need to bind later via BindZoneToPlan.
>
> **No Automatic Binding**: Whether passing PlanId via `CreateZone` or later calling `BindZoneToPlan`, **must** obtain user's explicit confirmation beforehand; never decide on your own which plan to bind.
#### Exception Handling: Service Authorization Missing
**Only when user selects DNSPod hosting access mode**, calling `CreateZone` may return error code `OperationDenied.DNSPodUnauthorizedRoleOperation`, indicating service authorization is missing.
**Handling Steps**:
1. **Automatically create service authorization**: Call CAM's `CreateServiceLinkedRole` interface
- `QCSServiceName`: `["DnspodaccesEO.TEO.cloud.tencent.com"]`
- `Description`: `"This role is a service-linked role for Tencent EdgeOne Platform (TEO). This role will query your domain status and related DNS records in DNSPod within the permission scope of associated policies, and help you quickly complete DNS modification to switch acceleration service to EO in one-click DNS modification scenarios"`
2. **Retry site creation**:
- If service authorization creation succeeds, retry calling `CreateZone` to create site
- If service authorization creation fails, enable fallback mode: prompt user to use NS access or CNAME access
### Next Step for DNSPod Hosting Access
> In DNSPod hosting access mode, EdgeOne will automatically complete ownership verification.
After site creation succeeds:
- Site will automatically complete ownership verification
- Can directly go to [Scenario D: Add Acceleration Domain](#scenario-d-add-acceleration-domain)
### Next Step for NS Access
Inform user that they need to modify DNS server to NameServers returned in response at domain registrar, then go to [Scenario C: Verify Ownership](#scenario-c-verify-ownership).
### Next Step for CNAME Access
Inform user of two validation methods (choose one), get validation info from response:
1. **DNS Validation**: Add TXT record in DNS
2. **File Validation**: Place validation file in origin root directory
After user confirms configuration complete, go to [Scenario C: Verify Ownership](#scenario-c-verify-ownership).
### B3: No Domain Access
**Applicable Scenarios**: User temporarily has no domain or only needs Layer 4 proxy and edge functions.
**Call** `CreateZone`, parameters as follows:
- `Type`: `noDomainAccess`
- `ZoneName`: **Keep as empty string**
- `Area`: **Keep as empty string**
- `PlanId`: Pass confirmed plan ID
> **Important Note**: In no-domain access mode, ZoneName and Area parameters must be kept empty, otherwise interface call will fail.
**After creation succeeds**:
- Site needs no ownership verification
- Can directly use Layer 4 proxy and edge functions
- Inform user: "Site created successfully, you can now configure Layer 4 proxy or edge functions. If you need Layer 7 acceleration features, you can add acceleration domains anytime."
**Follow-up Operations**:
- Configure Layer 4 proxy: Refer to Layer 4 proxy related documentation
- Configure edge functions: Refer to edge functions related documentation
## Scenario C: Verify Ownership
**Trigger**: User says "verify site", "check DNS switch", "ownership verification", or subsequent step after site creation.
> User can choose to skip ownership verification and directly go to [Scenario D: Add Acceleration Domain](#scenario-d-add-acceleration-domain), verify later.
### C1: Query Verification Status (DescribeIdentifications)
Before triggering verification, first call `DescribeIdentifications` to query current verification status.
**Decision**:
- If `Status` is `finished`, no need to verify again, go directly to next step
- If `Status` is `pending`, inform user to configure DNS TXT record or file validation based on validation info in response
### C2: Trigger Verification (VerifyOwnership)
After user confirms DNS / file configuration complete, call `VerifyOwnership`.
**NS Access Scenario**: Verify if DNS server switch succeeded. DNS switch usually takes 24-48 hours to take effect globally; if verification fails, suggest user retry later.
**CNAME Access Scenario**: Verify if TXT record or file is configured correctly. If site passes ownership verification, adding domains later won't need verification again.
## Scenario D: Add Acceleration Domain
**Trigger**: User says "add domain", "configure acceleration domain", "onboard subdomain", or subsequent step after ownership verification completion (or skip).
> **No-Domain Access Sites**: Sites created via no-domain access mode can also add Layer 7 acceleration domains anytime; after adding, can use complete Layer 7 acceleration features.
### D0: Determine Target Site
> If entering this scenario from a continuous flow of Scenario B/C, ZoneId is already known; you can skip this step and go directly to D1.
When user directly triggers "add domain", you need to first determine which site to add the domain to.
**Steps:**
1. **Extract root domain**: Extract the root domain (e.g., `example.com`) from the acceleration domain provided by user (e.g., `www.example.com`).
2. **Query matching sites**: Call `DescribeZones` with `zone-name` filter by root domain:
```
--Filters '[{"Name":"zone-name","Values":["example.com"]}]'
```
3. **Filter and handle based on query results**:
First, filter out sites with `Status == "initializing"` — these sites are still initializing and must not be displayed.
Then handle the remaining sites:
- **No matching site** (or all filtered out): Prompt user that the root domain has not been onboarded to EdgeOne, guide to [Scenario A: Confirm Plan](#scenario-a-confirm-plan) to begin full onboarding process.
- **Only 1 site**: Display site info (ZoneId, alias, access mode, acceleration area, status) to user, proceed to D1 after confirmation.
- **Multiple sites** (same root domain may have multiple sites): **Must** display all matching sites for user selection; never automatically select the first one or any arbitrary one. Display info should include:
- **Site Alias** (AliasZoneName) — the most intuitive distinguishing identifier
- **ZoneId**
- **Access Mode** (Type: dnsPodAccess / partial / full)
- **Acceleration Area** (Area: mainland / overseas / global)
- **Status**: Determined using the [Site Status Determination Logic](#site-status-determination-logic)
- Suggest prioritizing sites with `Active` status
> **No Automatic Selection**: When multiple matching sites exist, **must** wait for user's explicit selection before continuing; never decide on your own.
### D1: Collect Parameters
Need to confirm following information with user before calling:
1. **Acceleration Domain** (DomainName): Subdomain to onboard, e.g., `www.example.com`
2. **IPv6 Access** (IPv6Status): Whether to enable IPv6 access, values:
- `follow`: Follow site IPv6 configuration (default)
- `on`: Enable
- `off`: Disable
3. **Origin Configuration** (OriginInfo): See [OriginInfo Data Structure](#origininfo-data-structure) below
4. **Origin Protocol** (OriginProtocol, optional): FOLLOW (default) / HTTP / HTTPS
5. **Origin Port** (optional): HTTP origin port (default 80) / HTTPS origin port (default 443)
#### OriginInfo Data Structure
OriginInfo is a required parameter for `CreateAccelerationDomain`, defining origin information.
##### Required Fields
| Field | Type | Description |
|---|---|---|
| **OriginType** | string | Origin type, see values below |
| **Origin** | string | Origin address, fill in different values based on OriginType |
##### OriginType Values and Origin Mapping
| OriginType | Description | Origin Value |
|---|---|---|
| `IP_DOMAIN` | IPv4, IPv6, or domain type origin | IP address or domain, e.g., `1.1.1.1`, `origin.example.com` |
| `COS` | Tencent Cloud COS object storage origin | COS bucket access domain |
| `AWS_S3` | AWS S3 object storage origin | S3 bucket access domain |
| `ORIGIN_GROUP` | Origin group type origin | Origin group ID; if referencing another site's origin group, format: `{OriginGroupID}@{ZoneID}` |
| `VOD` | Cloud VOD | Cloud VOD application ID |
| `LB` | Load balancer (whitelist only) | Load balancer instance ID; if referencing another site's LB, format: `{LBID}@{ZoneID}` |
| `SPACE` | Origin offload (whitelist only) | Origin offload space ID |
##### Optional Fields
| Field | Type | Applicable Scenario | Description |
|---|---|---|---|
| **HostHeader** | string | Only when `OriginType = IP_DOMAIN` | Custom origin HOST header. **Do not pass this field** for other origin types, otherwise it will cause errors |
| **PrivateAccess** | string | Only when `OriginType = COS` or `AWS_S3` | Whether to use private authentication: `on` / `off` (default off) |
| **PrivateParameters** | list | Only when `PrivateAccess = on` | Private authentication parameter list |
| **BackupOrigin** | string | Only when `OriginType = ORIGIN_GROUP` | Backup origin group ID (legacy feature, not recommended) |
| **VodOriginScope** | string | Only when `OriginType = VOD` | Origin scope: `all` (default, all files in app) / `bucket` (specified bucket) |
| **VodBucketId** | string | Only when `OriginType = VOD` and `VodOriginScope = bucket` | VOD bucket ID |
##### Most Common Scenario Examples
**IP/Domain origin** (most common):
```json
{
"OriginType": "IP_DOMAIN",
"Origin": "1.1.1.1"
}
```
**COS origin (private access)**:
```json
{
"OriginType": "COS",
"Origin": "bucket-xxx.cos.ap-guangzhou.myqcloud.com",
"PrivateAccess": "on",
"PrivateParameters": [{"Name": "SecretId", "Value": "xxx"}, {"Name": "SecretKey", "Value": "xxx"}]
}
```
**Origin group**:
```json
{
"OriginType": "ORIGIN_GROUP",
"Origin": "og-testorigin"
}
```
### D2: Call CreateAccelerationDomain
> **No Automatic Addition**: Adding acceleration domain will change online DNS configuration. **Must** complete parameter collection in D1 and obtain user's explicit confirmation before execution; never decide on your own.
Call `CreateAccelerationDomain` after user confirmation.
**Next Step**: Inform user that they need to add CNAME record in DNS, pointing domain to EdgeOne-assigned CNAME address (can query `Cname` field via `DescribeAccelerationDomains`).
## Scenario E: Apply and Deploy HTTPS Certificate
**Trigger**: After domain addition complete, user says "configure HTTPS", "apply for certificate", or as final step of onboarding process.
> Complete certificate management (CNAME manual validation, deploy custom certificate, batch inspection, etc.) refer to [cert-manager.md](cert-manager.md).
### E1: NS Access / DNSPod Hosting Access (Automatic Validation, One-Step Complete)
> **Applicable Scenarios**: In NS access mode or DNSPod hosting access mode, EdgeOne can directly control DNS records, so can automatically complete certificate application and deployment.
> **No Automatic Deployment**: Deploying certificate will directly affect domain's HTTPS service. **Must** inform user which domains will deploy which certificates, and wait for explicit confirmation before calling `ModifyHostsCertificate`.
### E2: CNAME Access (Manual Validation)
CNAME access needs to first apply for certificate, complete domain validation, then deploy; process is longer. Please refer to [cert-manager.md Scenario B2](cert-manager.md#b2-cname-access-manual-validation) for complete steps.
## Scenario F: View Onboarding Status
**Trigger**: User says "check site status", "is domain onboarded".
Call `DescribeZones` to query target site status.
> **Important**: When displaying site status, must use the [Site Status Determination Logic](#site-status-determination-logic) to determine and display the effective status. Sites with `Status == "initializing"` must be filtered out and not displayed to users.
> Refer to [../api/zone-discovery.md](../api/zone-discovery.md) for more query methods.
FILE:references/api/README.md
# EdgeOne API Reference
EdgeOne (Edge Security Acceleration Platform) is managed through Tencent Cloud API. Currently uses **tccli** (Tencent Cloud CLI) as the calling tool, with service name **teo**.
## Files in This Directory
| File | Applicable Scenarios |
|---|---|
| `install.md` | First-time setup: install tccli (pipx / Homebrew), prepare Python environment |
| `auth.md` | tccli is installed but missing credentials — browser OAuth login, logout, or multi-account management |
| `api-discovery.md` | Find API endpoints — search best practices, API lists, and documentation via cloudcache |
| `zone-discovery.md` | Get zone / domain info: ZoneId lookup, reverse domain lookup, pagination handling |
| `dnspod-integration.md` | DNSPod hosting access: detect domain hosting status, service authorization, access process |
## Overview
**tccli** is Tencent Cloud's official CLI tool, supporting all cloud API calls.
**Key elements:**
- **Calling format** — `tccli teo <Action> [--param value ...]`
- **Auto credentials** — Browser OAuth authorization is recommended, see `auth.md`
- **API discovery** — Search best practices, API lists, and documentation online via cloudcache
**Calling conventions:**
- **Check documentation before calling**: Except for verifying tool availability, you **must** consult the API documentation via `api-discovery.md` before calling any API to confirm the action name, required parameters, and data structures. **Never guess parameters from memory.**
- If a field's type is a struct, you **must** continue looking up the full field definitions of that struct, recursively until all nested structs have been identified — do not skip or guess.
| Item | Description |
|---|---|
| Invocation Form | `tccli teo <Action> [--param value ...]` |
| Region | No `--region` by default; add `--region <region>` if user explicitly specifies region |
| Parameter Format | Non-simple types must be standard JSON |
| Serial Invocation | tccli has config file competition issues with parallel calls, please call one by one |
| Error Capture | Every tccli command **must** end with `2>&1; echo "EXIT_CODE:$?"`, otherwise stderr will be swallowed and you won't see specific error messages |
## Quick Start
**Before first API call in each session**, execute tool check first:
```sh
tccli cvm DescribeRegions 2>&1; echo "EXIT_CODE:$?"
```
Determine next step based on result:
| Result | Meaning | Next Step |
|---|---|---|
| Normal JSON response | Tool is installed, credentials are valid | Proceed with API operations |
| `command not found` / `not found` | tccli is not installed | Read `install.md` to install |
| `secretId is invalid` or auth error | tccli is installed but missing credentials | Read `auth.md` to configure credentials |
## Fallback Retrieval Sources
When files in this directory don't cover content, or need to confirm latest values / limits, retrieve via the following sources.
When reference files conflict with official documentation, **official documentation takes precedence**.
| Source | Retrieval Method | Used For |
|---|---|---|
| EdgeOne API docs | [edgeone.ai/document/50454](https://edgeone.ai/document/50454) | API parameters, request examples, data structures |
| teo API discovery | cloudcache commands in `api-discovery.md` | Dynamically find APIs, best practices |
| Tencent Cloud CLI docs | [github.com/TencentCloud/tencentcloud-cli](https://github.com/TencentCloud/tencentcloud-cli) | tccli installation, configuration, usage |
FILE:references/api/api-discovery.md
# EdgeOne API Discovery
The tccli service name for EdgeOne is **teo**.
Typically, reference files already specify the API name to call — just look up the API documentation directly;
use fallback discovery only when references do not cover the scenario.
---
## Main Flow: Known API Name
### 1. Read the API Documentation
Get parameter descriptions and request examples for a specific API:
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/action/CreatePurgeTask.md
```
### 2. Read Data Structures
Complex data structures referenced in the API documentation can be further examined:
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/model/Task.md
```
---
## Fallback: Not Sure Which API to Call
When references do not specify an API, or you need to explore uncovered scenarios, search in the following order:
### 1. Search the API List
Search for keywords in the API list (the Action name is the second argument after `tccli teo`):
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/actions.md \
| grep -i "purge\|cache"
```
### 2. Search Best Practices
Check if there are best practices matching the current scenario (with complete call examples):
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/practices.md \
| grep -i "purge\|refresh"
```
### 3. Read Best Practice Details
```sh
curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/practice/practice-53.md
```
---
FILE:references/api/auth.md
# Configure TCCLI Credentials
## Login Method
First, run the following command to verify the current login status:
```sh
tccli cvm DescribeRegions
```
- If a normal result is returned, you are already logged in — no need to log in again.
- If it shows `secretId is invalid` or other authentication errors, you are not logged in and need to continue with the login command below.
Browser-based authorization login is recommended — no need to manually enter SecretId/SecretKey, credentials are automatically saved locally:
```sh
tccli auth login
```
After execution, TCCLI will start a temporary port on your machine and print an OAuth authorization link (it usually also opens automatically in the default browser). Once the user completes login and authorization in the browser, TCCLI receives the callback, writes the credentials, and exits.
- If the browser does not open automatically, copy the link printed in the terminal and open it manually in a browser.
- Upon success, it will display: "Login successful, credentials have been written to: ..."
---
## Agent Operating Guidelines
**Determining whether login is needed:**
1. First run `tccli cvm DescribeRegions`.
2. If a **reasonable success result** is returned, consider the user logged in and proceed with subsequent operations.
3. If an error is returned or the command cannot execute, you must first run `tccli auth login`.
4. Never ask the user for `SecretId` / `SecretKey`, and do not execute commands that might expose credential contents (especially `tccli configure list`).
> ⚠️ The Agent must not assume TCCLI is usable based solely on the user's verbal statement or potentially stale credential files on the machine.
**When running `tccli auth login`:**
- This command will **block** until the user completes browser login (or it times out).
- The Agent should clearly inform the user: "Please open the authorization link shown in the terminal/tool output and complete login in the browser; the command will end automatically once done."
---
## Multi-Account & Logout
| Operation | Command |
|------|------|
| Login default account | `tccli auth login` |
| Login specific account | `tccli auth login --profile user1` |
| Logout default account | `tccli auth logout` |
| Logout specific account | `tccli auth logout --profile user1` |
Credential file notes:
- Default account credentials are saved in `default.credential`
- Specific account credentials are saved in `<profile-name>.credential` (e.g., `user1.credential`)
## Security Reminder
> Using `tccli configure` to manually enter SecretId / SecretKey is **not recommended**. Manually configured keys are stored in plaintext locally and risk being leaked. Always use the `tccli auth login` browser authorization method.
FILE:references/api/dnspod-integration.md
# DNSPod Integration API Reference
EdgeOne supports DNSPod hosting access mode, enabling one-click domain onboarding and automated configuration. This document explains how to call related APIs.
## Query Domain Hosting Status
### DescribeDomain (DNSPod)
**Purpose**: Query whether domain is hosted in DNSPod and if hosting status meets EdgeOne access conditions.
**Invocation Example**:
```bash
tccli dnspod DescribeDomain --Domain "example.com"
```
**Key Response Fields**:
```json
{
"DomainInfo": {
"Domain": "example.com",
"DomainId": 12345678,
"Status": "ENABLE", // Domain status: ENABLE(normal), PAUSE(paused), SPAM(blocked)
"DnsStatus": "", // DNS status: empty string(normal), "dnserror"(abnormal)
"Grade": "DP_Pro", // Plan level
"DnspodNsList": [ // DNSPod's NS list
"ns1.dnspod.net",
"ns2.dnspod.net"
],
"ActualNsList": [ // Actual NS used by domain
"ns1.dnspod.net",
"ns2.dnspod.net"
]
}
}
```
**EdgeOne Access Condition Determination**:
Domain can use DNSPod hosting access when meeting all of the following conditions:
1. ✅ Domain exists (interface call succeeds)
2. ✅ `Status` field is `"ENABLE"` or `"LOCK"`
3. ✅ `DnsStatus` field is empty string `""`
**Common Error Handling**:
| Error Code | Description | Handling Method |
|--------|------|----------|
| `ResourceNotFound.NoDataOfDomain` | Domain doesn't exist in DNSPod | Don't display DNSPod hosting access option |
| `OperationDenied.DNSPodUnauthorizedRoleOperation` | Missing service authorization | Try to automatically create service authorization role |
| `UnauthorizedOperation` | User lacks DNSPod interface permission | Don't display DNSPod hosting access option |
## Create Service-Linked Role
### CreateServiceLinkedRole (CAM)
**Purpose**: Create service authorization role for EdgeOne to access DNSPod for user.
**Trigger Scenario**: When calling `DescribeDomain` interface returns `OperationDenied.DNSPodUnauthorizedRoleOperation` error.
**Invocation Example**:
```bash
tccli cam CreateServiceLinkedRole \
--QCSServiceName '["DnspodaccesEO.TEO.cloud.tencent.com"]' \
--Description "This role is a service-linked role for Tencent EdgeOne Platform (TEO). This role will query your domain status and related DNS records in DNSPod within the permission scope of associated policies, and help you quickly complete DNS modification to switch acceleration service to EO in one-click DNS modification scenarios"
```
**Response Example**:
```json
{
"RoleId": "4611686018428516331",
"RequestId": "abcd1234-5678-90ef-ghij-klmnopqrstuv"
}
```
**Follow-up Actions**:
- ✅ If creation succeeds: Retry calling `DescribeDomain` to detect domain status
- ❌ If creation fails: Enable fallback mode, don't display DNSPod hosting access option
## DNSPod Hosting Access Process
### Complete Invocation Flow
```mermaid
graph TD
A[Start Access] --> B[Call DescribeDomain]
B --> C{Domain Exists?}
C -->|No| D[Don't Display DNSPod Hosting Access]
C -->|Yes| E{Status and DnsStatus Meet Conditions?}
E -->|No| D
E -->|Yes| F[Display DNSPod Hosting Access as Preferred]
B --> G{Error Code is Authorization Error?}
G -->|Yes| H[Call CreateServiceLinkedRole]
H --> I{Creation Succeeds?}
I -->|Yes| B
I -->|No| D
G -->|No| J{Other Permission Errors?}
J -->|Yes| D
F --> K[User Selects DNSPod Hosting Access]
K --> L[Call CreateZone Type=dnspod]
L --> M[Automatically Complete Ownership Verification]
M --> N[Add Acceleration Domain]
```
### Access Mode Parameters
When calling `CreateZone`, optional values for `Type` parameter:
- `dnspod`: DNSPod hosting access
- `full`: NS access
- `partial`: CNAME access
**Recommendation Strategy**:
1. Prioritize detecting if DNSPod hosting access conditions are met
2. If met, display `dnspod` as **preferred recommendation** to user
3. Always provide `full` and `partial` as alternative options
## Best Practices
### 1. Silent Detection, Smart Recommendation
```python
# Pseudo-code example
def get_available_access_types(domain):
access_types = []
# Try to detect DNSPod hosting status
try:
response = dnspod.DescribeDomain(Domain=domain)
domain_info = response['DomainInfo']
# Determine if conditions are met
if (domain_info['Status'] in ['ENABLE', 'LOCK'] and
domain_info['DnsStatus'] == ''):
access_types.append({
'type': 'dnspod',
'name': 'DNSPod Hosting Access',
'recommended': True,
'description': 'No manual configuration needed, automatically completes validation'
})
except DNSPodUnauthorizedRoleOperation:
# Try automatic authorization
try:
cam.CreateServiceLinkedRole(...)
# Retry detection
return get_available_access_types(domain)
except:
pass # Authorization failed, use fallback plan
except:
pass # Other errors, use fallback plan
# Fallback: Always provide NS and CNAME access
access_types.extend([
{'type': 'full', 'name': 'NS Access'},
{'type': 'partial', 'name': 'CNAME Access'}
])
return access_types
```
### 2. User Experience Optimization
**Suggested Wording When Displaying to User**:
✅ **When DNSPod Hosting Conditions Are Met**:
> Detected that your domain `example.com` is hosted in DNSPod. Recommend using **DNSPod Hosting Access** mode, which can automatically complete validation and configuration without manual DNS operations.
>
> You can also choose other access modes:
> - NS Access: Need to modify domain's NS record
> - CNAME Access: Need to add TXT record to verify ownership
❌ **When DNSPod Hosting Conditions Are Not Met**:
> Please select access mode:
> - NS Access: EdgeOne fully takes over DNS resolution
> - CNAME Access: Only configure CNAME record, NS unchanged
### 3. Error Handling Suggestions
| Scenario | User Prompt | Technical Handling |
|------|----------|----------|
| Domain Not in DNSPod | No prompt, directly provide NS/CNAME options | Handle silently |
| Missing Service Authorization | "Configuring service authorization for you..." | Automatically call CreateServiceLinkedRole |
| Authorization Creation Failed | No prompt, directly provide NS/CNAME options | Silently fall back to fallback plan |
| Domain Status Abnormal | No prompt, directly provide NS/CNAME options | Handle silently |
## Reference Materials
- [DNSPod API Documentation](https://cloud.tencent.com/document/product/1427)
- [CAM Service-Linked Roles](https://cloud.tencent.com/document/product/598/19388)
- [EdgeOne CreateZone Interface](https://cloud.tencent.com/document/product/1552/80719)
FILE:references/api/install.md
# API Calling Environment Setup
EdgeOne APIs are called via **tccli** (Tencent Cloud CLI).
## 1. Choose an Installation Method
| Method | Use Case | Prerequisites |
|---|---|---|
| **pipx** | All platforms, auto-isolated environment | Python 3.8+ |
| **Homebrew** | macOS | No Python required |
- macOS with Homebrew already installed → go directly with the Homebrew route, skip to Step 4
- Otherwise → go with the pipx route, continue to Step 2
## 2. Ensure Python Environment (pipx Route)
```sh
python3 --version
```
If the version is ≥ 3.8, skip to the next step. If the command is not found or the version is too old, install by OS:
```sh
# macOS (via Homebrew)
brew install python@3
# Ubuntu / Debian
apt update && apt install -y python3 python3-pip
# Windows (via winget)
winget install Python.Python.3.12
# Or download from: https://www.python.org/downloads/
```
After installation, run `python3 --version` to confirm version ≥ 3.8.
## 3. Install pipx (pipx Route)
```sh
pipx --version
```
If already installed, skip to the next step; otherwise install by OS:
```sh
# macOS
brew install pipx && pipx ensurepath
# Linux
pip install --user pipx && pipx ensurepath
# Windows
pip install --user pipx && pipx ensurepath
```
## 4. Install tccli
### pipx Route
```sh
pipx install tccli
# If upgrading from a version below 3.0.252.3, uninstall first then reinstall:
# pipx uninstall tccli && pipx install tccli
```
### Homebrew Route (macOS Only)
```sh
brew tap tencentcloud/tccli
brew install tccli
# Update: brew upgrade tccli
```
### Verify
```sh
tccli --version
```
## Next Step
After installation is complete, read `auth.md` to configure credentials.
FILE:references/api/zone-discovery.md
# Zone & Domain Discovery
Nearly all EdgeOne API operations require a **ZoneId** (zone ID, in the format `zone-xxx123abc456`).
Below is the standard process for discovering ZoneIds and reverse-looking up domains.
## APIs Involved
| Action | Description | API Docs |
|---|---|---|
| DescribeZones | Query the list of zones | `curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/action/DescribeZones.md` |
| DescribeAccelerationDomains | Query the list of acceleration domains | `curl -s https://cloudcache.tencentcs.com/capi/refs/service/teo/action/DescribeAccelerationDomains.md` |
Check the API docs above before calling, to get the exact usage of Filters, pagination, and other parameters.
## 1. List All Zones
Call **DescribeZones** — the `Zones` array in the response contains each zone's `ZoneId`, `ZoneName` (zone domain), `Status`, and other info.
> **Important**:
>
> 1. **Pagination Mechanism**: DescribeZones returns a maximum of 20 records per request by default. **Must use pagination mechanism** to retrieve all zones:
>
> 2. **Status Filtering**: After retrieving the zone list, **must filter out** zones with `Status` as `initializing`:
> - These zones are initializing and have not completed creation
> - Should not be displayed to users or used for subsequent operations
> - Only display and use available zones with `Status` as `active`
>
> **Pagination Parameters**:
> - `Limit`: Maximum number of records returned per request, recommend setting to `100` (maximum value)
> - `Offset`: Offset, starting from `0`, increment by `Limit` value each iteration
>
> **Pagination Logic**:
> ```
> 1. Initial call: Offset=0, Limit=100
> 2. Check TotalCount (total number of zones) in response
> 3. If TotalCount > number of zones retrieved:
> - Offset += 100
> - Continue calling until all zones are retrieved
> 4. Merge all paginated results
> ```
>
> **Example (Pseudo Code)**:
> ```python
> all_zones = []
> offset = 0
> limit = 100
>
> # Paginate to retrieve all zones
> while True:
> response = DescribeZones(Offset=offset, Limit=limit)
> all_zones.extend(response.Zones)
>
> if offset + limit >= response.TotalCount:
> break
> offset += limit
>
> # Filter out zones that are initializing
> available_zones = [zone for zone in all_zones if zone.Status != 'initializing']
> ```
## 2. Query by Zone Name
When the user already knows the zone domain, call **DescribeZones** with `Filters` (`zone-name`) for an exact match.
> **Note**: The query results also need to check the `Status` field and filter out zones with `initializing` status.
## 3. Reverse-Lookup ZoneId from a Subdomain
When the user only provides a subdomain (e.g., `www.example.com`) and doesn't know the ZoneId:
1. First call **DescribeZones** to list all zones, and find the ZoneId corresponding to the root domain matching the subdomain
> **Must use pagination**: Refer to the pagination mechanism in "1. List All Zones" to ensure traversing **all** zones for matching
2. Then call **DescribeAccelerationDomains** with `Filters` (`domain-name`) to confirm the domain exists under the zone
## 4. List All Domains Under a Zone
Call **DescribeAccelerationDomains** with the `ZoneId`. Be sure to use pagination parameters to handle multi-page results.
## 5. ZoneId Caching Recommendation
Within the same session, previously discovered ZoneIds should be remembered and reused to avoid repeated calls to `DescribeZones`.
FILE:references/observability/README.md
# EdgeOne Observability Reference
Operational guides for traffic daily report generation, origin health inspection, offline log download, and log analysis.
## Quick Decision Tree
```
What does the user want to do?
│
├─ "Generate yesterday's traffic daily report"
│ "Show me the bandwidth peak over the last 24 hours"
│ └─ → `eo-traffic-daily-report.md` 🟢 Low Risk · Auto-collect L7/L4 data and generate a Markdown daily report
│
├─ "Check the origin status for example.com"
│ "Is the origin healthy?" "Is it a CDN issue or an origin issue?"
│ └─ → `eo-origin-health-check.md` 🟢 Low Risk · Origin status code distribution + health ratio + quick root cause analysis
│
├─ "Download the logs for example.com from yesterday afternoon"
│ "Download the last 6 hours of L4 logs"
│ └─ → `eo-log-downloader.md` 🟢 Low Risk · Natural language driven offline log download link retrieval
│
├─ "Analyze the logs — too many 502 errors"
│ "Which URIs have the most abnormal requests?"
│ "Show me per-URL download traffic breakdown"
│ └─ → `eo-log-analyzer.md` 🟢 Low Risk · Log download + local parsing + pattern recognition + fault inference + traffic aggregation
│
└─ Not sure which API to call
└─ → `../api/api-discovery.md`
```
## Prerequisites
All operations require API calls via tccli. Before first use, complete the following:
1. **Tool Setup** — Read `../api/README.md` to install tccli and configure credentials
2. **Get ZoneId** — Read `../api/zone-discovery.md` to obtain the zone ID
## Files in This Directory
| File | Risk Level | Core Trigger Scenario |
|---|---|---|
| `eo-traffic-daily-report.md` | 🟢 Low Risk | Query L7/L4 traffic trends daily and generate a Markdown report with bandwidth peak, request volume, and Top domains/regions |
| `eo-origin-health-check.md` | 🟢 Low Risk | Query origin status code distribution and origin health ratio for quick origin fault root cause analysis |
| `eo-log-downloader.md` | 🟢 Low Risk | Describe time range and domain in natural language to automatically retrieve offline log download links |
| `eo-log-analyzer.md` | 🟢 Low Risk | Automatically download and parse logs locally, extract anomaly details, provide pattern recognition conclusions with fault inference, or aggregate traffic by domain/URL |
## Reference Links
- [EdgeOne Product Documentation](https://edgeone.ai/document/56978)
- [EdgeOne API Documentation](https://edgeone.ai/document/50454)
- `../api/README.md`
FILE:references/observability/eo-log-analyzer.md
# eo-log-analyzer
Users describe a fault time period and domain to automatically download logs, parse them locally, extract anomaly details, and provide pattern recognition conclusions with fault inference recommendations. Also supports traffic aggregation analysis (e.g., per-URL download volume breakdown). This is an upgrade from `eo-log-downloader.md`, which only provides download links — this scenario further completes log parsing and analysis.
## APIs & Prerequisites
Same as `eo-log-downloader.md` — this scenario uses the same APIs (`DownloadL7Logs`, `DownloadL4Logs`, `DescribeAccelerationDomains`) and the same prerequisites (`tccli` authentication + ZoneId discovery). Refer to that document for full details.
## Special Design Notes
- **Read-only operations**: All API calls are read-only queries; log downloading and parsing are performed locally
- **Local file writes only**: Log parsing is completed on the client side locally; raw logs are not uploaded or modified
- **Aggregated summaries for high-traffic domains**: For domains with extremely high request volumes, use aggregated summary analysis (grouped statistics by URI/status code/IP) rather than line-by-line output of full logs, to avoid information overload
- **Cross-scenario integration**: Analysis conclusions can guide the user to use `eo-origin-health-check.md` for origin troubleshooting, or `../security/ip-threat-blacklist.md` to block anomalous IPs
## Scenario A: Fault Period Log Analysis
**Trigger**: User says "analyze the logs for example.com from 3 PM to 4 PM today", "too many 502 errors, help me figure out what's going on", "analyze the logs and find the anomalies".
### Workflow
**Step 1**: Confirm analysis parameters
- Confirm the target domain (must be specified by the user or obtained from context)
- Parse the time range (natural language to ISO 8601)
- Confirm the anomaly type of interest (default: 4xx + 5xx; the user may also specify a particular status code such as 502)
**Step 2**: Download log files
Confirm the domain, retrieve download links, and download locally:
- If the user specified a subdomain (e.g., `www.example.com`), use it directly in the `Domains` parameter
- If the user did not specify a domain, or provided a **root domain** (matching the `ZoneName` from `DescribeZones`), follow `eo-log-downloader.md` Scenario A Step 2 to call `DescribeAccelerationDomains` and discover subdomains first, then either let the user choose or omit `Domains` to download all
Call `DownloadL7Logs` with `StartTime`, `EndTime`, `ZoneIds`, and optionally `Domains`. Then download and decompress:
- Use `curl` or `wget` to download log files to a local temporary directory
- Log files are typically in `.gz` compressed format; use `gunzip` to decompress
- ⚠️ **Log format is JSON Lines** (one JSON object per line, newline-separated). Each line is a self-contained JSON object. Do NOT parse as tab-separated or CSV.
> If there are many or large log files, prioritize downloading files that cover the user's time period of interest to avoid unnecessary bulk downloads.
> **If results are empty**, follow the troubleshooting steps in `eo-log-downloader.md` Scenario A — Troubleshooting Empty Results (domain mismatch, time too recent, no traffic, wrong zone).
#### L7 Offline Log Field Reference
The following fields are available in offline logs (subset of all L7 fields). For the full field list, see [L7 Access Logs](https://edgeone.ai/document/61300).
| Field | Type | Description | Common Use |
|---|---|---|---|
| `RequestTime` | ISO8601 | Time the request was received (UTC+0) | Time-window aggregation |
| `RequestHost` | String | Requested hostname (subdomain) | Group by domain |
| `RequestUrl` | String | URL path (without query string) | Group by resource/URI |
| `RequestUrlQueryString` | String | Query string portion of the URL | Full URL reconstruction |
| `RequestMethod` | String | HTTP method (GET/POST/...) | Filter by method |
| `RequestBytes` | Integer | Total bytes sent from client to edge (headers + body + SSL) | Inbound traffic |
| `EdgeResponseStatusCode` | Integer | Status code returned to client | Error filtering (4xx/5xx) |
| `EdgeResponseBytes` | Integer | Total bytes sent from edge to client (headers + body + SSL) | **Outbound traffic / download volume** |
| `EdgeResponseBodyBytes` | Integer | Response body bytes only (no headers) | Content-only traffic |
| `EdgeCacheStatus` | String | Cache hit status: `hit` / `miss` / `dynamic` / `other` | Cache analysis |
| `EdgeInternalTime` | Integer | Time to first byte (ms) | Latency analysis |
| `EdgeResponseTime` | Integer | Time to last byte (ms) | Full response latency |
| `ClientIP` | String | Client IP address | IP concentration analysis |
| `ClientRegion` | String | Client country/region (ISO 3166-1 alpha-2) | Geographic analysis |
| `EdgeServerIP` | String | Edge node IP | Node-level analysis |
| `RequestUA` | String | User-Agent | Bot/crawler identification |
| `RequestReferer` | String | Referer header | Traffic source analysis |
> **Parsing tip**: Use `jq` for JSON Lines processing (install via `brew install jq` on macOS or `apt install jq` on Linux). Example:
> ```sh
> # Count requests grouped by status code
> cat logfile.log | jq -r '.EdgeResponseStatusCode' | sort | uniq -c | sort -rn
> # Sum download traffic by URL
> cat logfile.log | jq -r '[.RequestUrl, .EdgeResponseBytes] | @tsv' | awk -F'\t' '{sum[$1]+=$2} END {for(u in sum) printf "%s\t%.2f MB\n", u, sum[u]/1048576}' | sort -t$'\t' -k2 -rn
> ```
>
> **If `jq` is not available**, use Python as a fallback:
> ```sh
> python3 -c "
> import json, sys
> from collections import defaultdict
> agg = defaultdict(lambda: [0, 0])
> for line in sys.stdin:
> r = json.loads(line)
> agg[r.get('RequestUrl','')][0] += 1
> agg[r.get('RequestUrl','')][1] += r.get('EdgeResponseBytes', 0)
> for url, (cnt, b) in sorted(agg.items(), key=lambda x: -x[1][1]):
> print(f'{url}\t{cnt}\t{b/1048576:.2f} MB')
> " < logfile.log
> ```
**Step 3**: Parse logs and extract anomalies
Perform structured parsing on the decompressed logs:
- Filter anomalous requests by status code (4xx, 5xx)
- Aggregate anomalous request count and ratio by URI
- Aggregate by client IP to identify request concentration
- Aggregate by time window (e.g., 5-minute granularity) to identify anomaly peak periods
**Step 4**: Pattern recognition
Perform pattern analysis on the aggregated data:
| Pattern | Characteristics | Possible Cause |
|---|---|---|
| Single URI concentrated 502 | One URI has a much higher 502 ratio than others | The backend service for that URI is malfunctioning |
| IP-concentrated anomalous requests | A few IPs generate a large number of anomalous requests | Possible crawler, attack, or malfunctioning client |
| Global 5xx spike | All URIs show concentrated 5xx in the same time period | Overall origin server overload or failure |
| Intermittent 504 | 504 errors appear repeatedly in specific time windows | Origin response timeout, possible performance bottleneck |
**Step 5**: Output analysis report
Summarize the anomaly details table, pattern recognition conclusions, and fault inference recommendations.
**Output recommendation**: Present the response as "overview summary + anomalous URI Top N table + pattern recognition conclusions + recommendations".
## Scenario B: Identify Origin Failure Concentration Periods
**Trigger**: User says "show me when origin failures were concentrated recently", "which time period had the most 5xx errors", "help me find the fault peak periods".
### Workflow
**Step 1**: Confirm query parameters
- Confirm the target domain
- Default query range is the last 6 hours; the user may also specify a different time range
**Step 2**: Download and parse logs
Same as Scenario A Steps 2 and 3, but focus on time-window aggregation:
- Use 5-minute or 10-minute granularity to count 5xx requests per time window
- Calculate the 5xx ratio for each window
**Step 3**: Identify peak periods
- Mark the Top 3 time periods with the highest 5xx count
- Calculate the deviation multiplier of peak periods from the average
**Step 4**: Output time distribution
Organize the time distribution into a table, highlight peak periods, and provide further analysis recommendations.
**Output recommendation**: Present the response as "time distribution table + peak period annotations + next-step recommendations".
## Scenario C: Traffic / Download Volume Aggregation by Domain + URL
**Trigger**: User says "which resources use the most bandwidth", "show me download traffic by URL", "I want to see per-resource traffic breakdown", "which URLs are consuming the most traffic".
### Workflow
**Step 1**: Confirm analysis parameters
- Confirm the target domain (or all domains under the zone)
- Parse the time range (natural language to ISO 8601)
- Confirm the aggregation dimension: by URL only, by domain + URL, or by domain only
**Step 2**: Download log files
Same as Scenario A Step 2. Download and decompress the log files for the target time range.
**Step 3**: Aggregate traffic data
Parse each JSON line and aggregate by the requested dimension:
- **Primary metric**: `EdgeResponseBytes` (total bytes delivered to client, including headers) — this represents the **download traffic** from the user's perspective
- **Secondary metric**: `EdgeResponseBodyBytes` (response body only) — useful when isolating content size from protocol overhead
- Group by `RequestHost` + `RequestUrl` (or `RequestHost` alone, or `RequestUrl` alone, depending on user intent)
- Also count the number of requests per group for context
Example aggregation using `jq` + `awk` (if `jq` is available):
```sh
# Traffic by domain + URL, sorted descending by MB value
cat *.log | jq -r '[.RequestHost, .RequestUrl, (.EdgeResponseBytes | tostring)] | @tsv' \
| awk -F'\t' '{key=$1"\t"$2; sum[key]+=$3; cnt[key]++} END {for(k in sum) printf "%s\t%d\t%.2f\n", k, cnt[k], sum[k]/1048576}' \
| sort -t$'\t' -k4 -rn | head -50
```
If `jq` is not available, use Python:
```sh
python3 -c "
import json, sys, os
from collections import defaultdict
agg = defaultdict(lambda: [0, 0, 0]) # [requests, bytes, body_bytes]
for fname in sorted(f for f in os.listdir('.') if f.endswith('.log') or not '.' in f):
with open(fname) as fh:
for line in fh:
line = line.strip()
if not line: continue
r = json.loads(line)
key = (r.get('RequestHost',''), r.get('RequestUrl',''))
agg[key][0] += 1
agg[key][1] += r.get('EdgeResponseBytes', 0)
agg[key][2] += r.get('EdgeResponseBodyBytes', 0)
for (host, url), (cnt, b, bb) in sorted(agg.items(), key=lambda x: -x[1][1]):
print(f'{host}\t{url}\t{cnt}\t{b/1048576:.2f} MB')
"
```
**Step 4**: Enrich with cache and status insights (optional)
For the Top N resources by traffic, additionally aggregate:
- `EdgeCacheStatus` distribution (`hit` / `miss` / `dynamic`) — helps identify cacheable resources that are not being cached
- `EdgeResponseStatusCode` distribution — detect if high-traffic resources also have high error rates
**Step 5**: Output traffic report
Summarize the aggregation results with the Top N table and optional insights.
**Output recommendation**: Present as "overview summary + Top N traffic table + cache/status insights + optimization recommendations".
## Output Format
### Scenario A: Log Analysis Report
```markdown
## Log Analysis Report — <domain>
**Zone**: <zone name> (ZoneId: <zone-id>)
**Analysis Time Period**: <start time> – <end time>
**Total Requests**: <N> | **Anomalous Requests**: <M> (<ratio>%)
### Anomalous URI Top 10
| URI | Anomaly Count | Ratio of Total Anomalies | Primary Error Code | Source IP Concentration |
|---|---|---|---|---|
| ... | ... | ...% | 502 | Dispersed / Concentrated (N IPs) |
### Pattern Recognition
- <pattern 1 description>
- <pattern 2 description>
### Recommendations
1. <recommendation 1> (consider using eo-origin-health-check for origin troubleshooting)
2. <recommendation 2> (consider using ip-threat-blacklist to block anomalous IPs)
```
### Scenario B: Origin Failure Time Distribution
```markdown
## Origin Failure Time Distribution — <domain> (Last <N> Hours)
**Analysis Time Range**: <start time> – <end time>
| Time Period | 5xx Count | Ratio | Remarks |
|---|---|---|---|
| HH:MM–HH:MM | ... | ...% | Peak ⚠️ |
| ... | ... | ...% | |
### Conclusion
- Failures are concentrated in <time period>. Recommend focusing analysis on logs from that period (use Scenario A for deeper analysis).
```
### Scenario C: Traffic Aggregation Report
```markdown
## Traffic Aggregation Report — <domain / all domains>
**Zone**: <zone name> (ZoneId: <zone-id>)
**Analysis Time Period**: <start time> – <end time>
**Total Requests**: <N> | **Total Download Traffic**: <X> MB
### Top 20 Resources by Download Traffic
| Domain | URL | Requests | Download Traffic | Avg Size | Cache Hit Rate |
|---|---|---|---|---|---|
| example.com | /video/intro.mp4 | 1,234 | 456.78 MB | 380 KB | 92% hit |
| ... | ... | ... | ... MB | ... KB | ...% |
### Insights
- <cache optimization insight, e.g., "Top 3 resources account for 80% of total traffic, all have >90% cache hit rate">
- <anomaly insight, e.g., "/api/data has 45 MB traffic but 0% cache hit — consider enabling caching for this endpoint">
### Recommendations
1. <recommendation 1>
2. <recommendation 2>
```
## Notes
> - Log parsing is performed locally on the client; ensure sufficient disk space for temporary log files.
> - Log files for high-traffic domains can be very large; prefer aggregated statistics over line-by-line analysis.
> - If origin errors (e.g., 502 concentrated on certain IPs) are found, use `eo-origin-health-check.md` for origin health inspection.
> - If anomalous requests are concentrated on a few client IPs, use `../security/ip-threat-blacklist.md` for IP blocking.
FILE:references/observability/eo-log-downloader.md
# eo-log-downloader
Users describe a time range and domain in natural language to automatically retrieve the corresponding offline log download links, eliminating the tedious steps of manually selecting time ranges and domains in the console.
## APIs Involved
| Action | Description |
|---|---|
| DownloadL7Logs | Retrieve L7 offline log download links |
| DownloadL4Logs | Retrieve L4 offline log download links |
| DescribeAccelerationDomains | List acceleration domains under a zone — used to discover subdomains when user provides a root domain |
> **Command usage**: This document only lists API names and workflow guidance.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm complete parameters and response descriptions.
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli`. If no valid credentials are configured in the environment, guide the user to log in first:
```sh
tccli auth login
```
> The terminal will print an authorization link after execution and remain blocked until the user completes browser authorization, after which the command ends automatically.
> Never ask the user for `SecretId` / `SecretKey`, and do not execute any commands that could expose credential contents.
2. ZoneId must be obtained first. Refer to `../api/zone-discovery.md`.
## Scenario A: Download L7 Logs by Time and Domain
**Trigger**: User says "get me the logs for example.com from yesterday afternoon", "download the last 6 hours of logs for example.com", "help me download the logs".
### Workflow
**Step 1**: Parse time range
- Convert the user's natural language time description to ISO 8601 format start and end times
- Example: "yesterday from 2 PM to 4 PM" → `2026-03-24T14:00:00+08:00` to `2026-03-24T16:00:00+08:00`
- If the user only says "last N hours", calculate backward from the current time
- If the user does not specify a time range, ask for confirmation
**Step 2**: Confirm domain
- If the user specified a domain, use it directly
- If the user did not specify a domain, ask for confirmation (the `Domains` parameter can be omitted to download logs for all domains, but inform the user first)
> ⚠️ **Root domain vs subdomain**: The `Domains` parameter requires the **actual acceleration subdomain** (e.g., `www.example.com`, `singapore.example.com`), not the zone root domain (e.g., `example.com`). Logs are recorded per subdomain.
> If the user provides a root domain (which matches the `ZoneName` from `DescribeZones`), you must:
> 1. Call `DescribeAccelerationDomains` with the `ZoneId` to list all subdomains under the zone
> 2. Present the subdomain list to the user and ask which one(s) to download, or omit `Domains` to download all
**Step 3**: Retrieve log download links
Call `DownloadL7Logs` with:
- `StartTime` / `EndTime`: Start and end times parsed in Step 1
- `ZoneIds`: Zone ID
- `Domains`: Target subdomain list (optional; omit to get logs for all domains under the zone)
- If there are many results, use `Limit` and `Offset` for pagination (default Limit=20, max 300)
#### Troubleshooting Empty Results
If `Data` is empty, check in order:
1. **Domain mismatch** — Did you pass the zone root domain instead of a subdomain? Remove the `Domains` parameter and retry to see if logs exist for other subdomains
2. **Time too recent** — Offline logs have a delay of approximately 1–2 hours. If the user requested logs for a very recent period, suggest retrying later
3. **No traffic** — The domain may have no actual traffic in the queried time range. Use `DescribeTimingL7AnalysisData` or check the console to confirm
4. **Wrong zone** — If the account has multiple zones, the domain may belong to a different zone. Try `ZoneIds=["*"]` to query across all zones
**Step 4**: Organize output
Organize the returned log file list into a clickable download link table with directly accessible download links.
- The API returns `Size` in **bytes**; convert to human-readable units (KB/MB) in the output table
- If the user's intent goes beyond downloading (e.g., "I want to analyze traffic by URL", "which resources use the most bandwidth"), proactively suggest using `eo-log-analyzer.md` for local log parsing and aggregation
**Output recommendation**: Present the response as "query parameter summary + download link table" with directly clickable download links.
## Scenario B: Download L4 Logs
**Trigger**: User says "download L4 logs", "get me the L4 logs", "download the layer 4 proxy logs".
### Workflow
**Step 1**: Parse time range
Same as Scenario A Step 1.
**Step 2**: Confirm L4 proxy instance
- If the user specified a ProxyId, use it directly
- If the user did not specify, ask whether to download logs for all L4 instances
**Step 3**: Retrieve log download links
Call `DownloadL4Logs` with:
- `StartTime` / `EndTime`: Start and end times
- `ZoneIds`: Zone ID
- `ProxyIds`: L4 instance ID list (optional)
- Handle pagination (default Limit=20, max 300)
**Step 4**: Organize output
Same as Scenario A Step 4.
**Output recommendation**: Same format as Scenario A, with log type labeled as "L4 logs".
## Output Format
```markdown
## Log Download Links
**Zone**: <zone name> (ZoneId: <zone-id>)
**Domain**: <domain> / All domains
**Time Range**: <start time> – <end time>
**Log Type**: L7 Access Logs / L4 Proxy Logs
| Log File | Size | Log Start Time | Log End Time | Download Link |
|---|---|---|---|---|
| ... | ... KB/MB | ... | ... | [Download](url) |
Total: N log files.
```
## Notes
> - Offline logs have a certain delay (approximately 1–2 hours); logs for very recent time periods may not yet be generated. If results are empty, suggest the user retry later.
> - Download links have a limited validity period; please download promptly.
> - **Log format**: Offline logs use **JSON Lines** format (one JSON object per line). For field descriptions, see [L7 Access Logs](https://edgeone.ai/document/61300) and [L4 Proxy Logs](https://edgeone.ai/document/61301). For output format customization (CSV, TSV, etc.), see [Customizing Log Output Formats](https://edgeone.ai/document/64485).
> - **For further analysis**: If the user wants to analyze log content (anomaly detection, traffic breakdown by URL, per-resource bandwidth, etc.), use `eo-log-analyzer.md` which handles download + local parsing + aggregation automatically.
FILE:references/observability/eo-origin-health-check.md
# eo-origin-health-check
Query the origin status code distribution and origin health ratio for a specified domain or the entire account over a given time range, to quickly determine whether the issue lies with the CDN or the origin server.
## APIs Involved
| Action | Description |
|---|---|
| DescribeTimingL7AnalysisData | Query L7 time-series data (response status code analysis at edge nodes) |
| DescribeTimingL7OriginPullData | Query L7 origin-pull time-series data (origin status code distribution, origin traffic/bandwidth) |
| DescribeTopL7AnalysisData | Query top N domains/URLs/status codes by request count or traffic — used to discover which domains carry traffic |
> **Command usage**: This document only lists API names and workflow guidance.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm complete parameters and response descriptions.
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli`. If no valid credentials are configured in the environment, guide the user to log in first:
```sh
tccli auth login
```
> The terminal will print an authorization link after execution and remain blocked until the user completes browser authorization, after which the command ends automatically.
> Never ask the user for `SecretId` / `SecretKey`, and do not execute any commands that could expose credential contents.
2. ZoneId must be obtained first. Refer to `../api/zone-discovery.md`.
- **Shortcut**: Both `DescribeTimingL7OriginPullData` and `DescribeTimingL7AnalysisData` support `ZoneIds=["*"]` to query account-level aggregated data across all zones. Use this when the user does not specify a particular zone or wants an overview.
## Key API Parameter Notes
### Interval (applies to both DescribeTimingL7OriginPullData and DescribeTimingL7AnalysisData)
- ⚠️ **Do NOT use `min` (1-minute) granularity** — it generates excessive data volume and frequently triggers API errors (response size limit exceeded). It also puts unnecessary pressure on the data query cluster.
- Recommended granularity by query time range:
| Query Time Range | Recommended Interval | Notes |
|---|---|---|
| ≤ 2 hours | `5min` | Good balance of precision and data volume |
| 2–24 hours | `hour` | Suitable for most troubleshooting scenarios |
| 1–7 days | `hour` or `day` | Use `hour` if you need to identify anomaly time windows |
| > 7 days | `day` | Avoid finer granularity for long ranges |
- If a query returns empty or errors with a finer granularity, try a coarser granularity or narrow the time range.
### DescribeTimingL7OriginPullData
- `ZoneIds` is **required**. Pass `["*"]` for account-level data, or one or more specific ZoneIds.
- `DimensionName` controls grouping:
- `origin-status-code-category`: Group by category (1XX/2XX/3XX/4XX/5XX)
- `origin-status-code`: Group by exact status code (200, 301, 404, 502, etc.) — useful for pinpointing specific error codes
- ⚠️ **`DimensionName` does NOT support `domain`**. You cannot group origin-pull data by domain in a single call. To find which domains have origin errors, use `DescribeTopL7AnalysisData` to get the domain list first, then query each domain individually with the `domain` filter.
- **Do NOT** include `Filters` with key `mitigatedByWebSecurity` — this filter is not supported by this API and will cause errors.
- The `domain` filter works with both `ZoneIds=["*"]` and specific ZoneIds. The value must be the **actual domain that carries traffic** (typically a subdomain like `www.example.com` or `api.example.com`), not the zone root domain (e.g., `example.com`). If filtering returns empty results, try without the domain filter first to confirm data exists, then check what domains are carrying traffic.
#### Troubleshooting Empty Results
> This applies to both `DescribeTimingL7OriginPullData` and `DescribeTimingL7AnalysisData`.
If a query returns empty `TimingDataRecords` or `Data`, follow these steps in order:
1. **Remove the `domain` filter** and query at zone or account level (`ZoneIds=["*"]`) to confirm data exists in the time range
2. **If zone-level is also empty**, try `ZoneIds=["*"]` (account-level) — the domain may belong to a different zone than expected
3. **If account-level has data but domain filter returns empty**, the domain name may be wrong. Use `DescribeTopL7AnalysisData` with `MetricName=l7Flow_request_domain` to discover which domains actually carry traffic
4. **Use a coarser `Interval`** (e.g., `day` instead of `hour`) — finer granularity with wide time ranges can exceed API response limits
5. **Check the time range** — API data typically has a 5–10 minute delay; avoid querying the most recent 10 minutes
### DescribeTimingL7AnalysisData
- `ZoneIds` supports `["*"]` for account-level data.
- Use `Filters` with key `statusCode` (Operator: `equals`, Value: `["2XX"]`, `["4XX"]`, `["5XX"]`, etc.) to filter by status code category. Each filter call returns the time-series for that specific category.
- ⚠️ **You must make separate calls for each status code category** (2XX, 3XX, 4XX, 5XX). There is no single-call way to get the full breakdown.
- The `domain` filter follows the same rules as described above — use the actual traffic-carrying domain, not the zone root domain.
- **Response structure**: Returns `Data[].TypeValue[].Sum` for the aggregated count. The per-interval breakdown is in `Data[].TypeValue[].Detail[]` (each with `Timestamp` and `Value`). Do NOT use `Data[].Value[]` — that field does not exist.
### DescribeTopL7AnalysisData
- `ZoneIds` supports `["*"]` for account-level data.
- `MetricName` (singular, not array) controls the ranking dimension. Key values for origin health check:
- `l7Flow_request_domain`: Top domains by request count — **use this to discover all domains with traffic**.
- `Limit`: Maximum number of top entries to return (default 10, max 1000). Set to 50+ when scanning for anomalous domains.
- Returns `Data[].TypeKey` (grouping key, e.g., AppId) and `Data[].DetailData[]` where each entry has `Key` (the domain name) and `Value` (the count).
- This is an **edge-side** metric (not origin-pull), but it serves as the domain discovery step before per-domain origin queries.
## Scenario A: Query Origin Status Code Distribution
**Trigger**: User says "check the origin status for example.com", "are there any origin issues", "is the origin healthy", "which domains have origin errors".
> This scenario has two sub-flows depending on whether the user asks about a **specific domain** or wants an **account-wide scan**.
### Workflow A1: Specific Domain Origin Health Check
Use when the user specifies a domain (e.g., "check origin health for api.example.com").
**Step 1**: Confirm query parameters
- Confirm the target domain (must be specified by the user or obtained from context)
- Default query range is the last 1 hour; the user may also specify a different time range
- Recommend avoiding the most recent 10 minutes (API data has a delay)
**Step 2**: Query origin status code distribution
Call `DescribeTimingL7OriginPullData` with:
- `ZoneIds`: `["*"]` for account-level, or specific ZoneId(s) for targeted query
- `DimensionName=origin-status-code-category`: Group by origin status code category (2XX/3XX/4XX/5XX)
- `MetricNames=["l7Flow_request_hy"]`: Origin-pull request count
- `Interval`: `5min` or `hour` for identifying anomaly periods; `day` for longer time ranges (see Interval guidelines above)
- Use the `domain` key in `Filters` to filter for the target subdomain
> **Tip**: To identify the exact error codes (e.g., 502 vs 504), make a follow-up call with `DimensionName=origin-status-code`.
**Step 3**: Calculate health metrics
- Health ratio = 2xx requests / total origin requests × 100%
- Anomaly ratio = (4xx + 5xx) requests / total origin requests × 100%
- If 5xx ratio > 5%, mark as ⚠️ Anomaly
**Output recommendation**: Present as "health score + status code distribution table", with anomaly indicators highlighted.
### Workflow A2: Account-Wide Origin Anomaly Scan
Use when the user asks "which domains have origin errors", "are there any origin issues across all domains", or does not specify a particular domain.
**Step 1**: Get account-level overview
Call `DescribeTimingL7OriginPullData` with:
- `ZoneIds=["*"]`
- `DimensionName=origin-status-code-category`
- `MetricNames=["l7Flow_request_hy"]`
- `Interval=day` (or as appropriate for the time range)
Calculate overall health metrics. If 4xx + 5xx ratio is low (e.g., < 1%), report healthy and stop. Otherwise proceed to identify anomalous domains.
**Step 2**: Discover domains with traffic
Call `DescribeTopL7AnalysisData` with:
- `MetricName=l7Flow_request_domain`
- `ZoneIds=["*"]`
- `Limit=50` (or higher if the account has many domains)
This returns the top domains ranked by edge request count. Extract the domain list from `Data[].DetailData[].Key`.
**Step 3**: Per-domain origin status code check
For each domain from Step 2, call `DescribeTimingL7OriginPullData` with:
- `ZoneIds=["*"]`
- `DimensionName=origin-status-code-category`
- `MetricNames=["l7Flow_request_hy"]`
- `Filters`: `[{"Key": "domain", "Operator": "equals", "Value": ["<domain>"]}]`
Collect each domain's 2XX/3XX/4XX/5XX counts and calculate error ratios.
> ⚠️ This step requires one API call per domain. For large domain lists, consider only checking the top 20–30 domains, or parallelize calls where possible.
**Step 4**: Drill down into anomalous domains
For domains with error ratio > 5%, make a follow-up call with `DimensionName=origin-status-code` to get the exact error code breakdown (e.g., 404 vs 502 vs 503).
**Output recommendation**: Present as "account overview + anomalous domain table (sorted by error ratio descending) + top error codes for the worst domains".
## Scenario B: Quick Fault Root Cause Analysis
**Trigger**: User says "is it a CDN issue or an origin issue", "help me troubleshoot the origin failure", "where are the 5xx errors coming from", "is EO the problem or the origin".
### Workflow
> ⚠️ **Time alignment**: When comparing edge vs origin data, **always use the same `StartTime`, `EndTime`, and `Interval`** for both `DescribeTimingL7AnalysisData` and `DescribeTimingL7OriginPullData` calls. Mismatched time ranges or granularity will produce misleading comparisons.
**Step 1**: Collect edge node status code data
Call `DescribeTimingL7AnalysisData` **4 times** (once per status code category) to query edge-to-client response status codes:
- `MetricNames=["l7Flow_request"]`
- `ZoneIds`: `["*"]` for account-level, or specific ZoneId(s)
- `Interval`: use the same granularity as Step 2 (see Interval guidelines above)
- `Filters`: include `{"Key": "statusCode", "Operator": "equals", "Value": ["2XX"]}` (repeat for `"3XX"`, `"4XX"`, `"5XX"`)
- Optionally add `{"Key": "domain", "Operator": "equals", "Value": ["<domain>"]}` to `Filters` to scope to a specific subdomain
Extract the count for each category from `Data[].TypeValue[].Sum`.
**Step 2**: Collect origin status code data
Call `DescribeTimingL7OriginPullData` to query origin status codes (single call returns all categories):
- `DimensionName=origin-status-code-category`
- `MetricNames=["l7Flow_request_hy"]`
- `ZoneIds`: same scope as Step 1
- `Interval`: same as Step 1 (must match for accurate comparison)
- Optionally filter for a specific subdomain via `domain` in `Filters`
Extract the count for each category from `TimingDataRecords[].TypeValue[].Sum` where `TypeKey` is the category (2XX/3XX/4XX/5XX).
**Step 3**: Comparative analysis for root cause
Compare edge vs origin error counts side by side:
**5XX comparison:**
| Edge 5XX | Origin 5XX | Preliminary Conclusion |
|---|---|---|
| High | High (similar count) | 5XX errors originate from the origin server — **origin issue** |
| High | Low or zero | 5XX generated by CDN/EO nodes — **CDN issue** |
| Zero or low | High | Origin has 5XX but CDN cache/retry absorbs them — **origin issue, CDN mitigates** |
**4XX comparison:**
| Edge 4XX | Origin 4XX | Preliminary Conclusion |
|---|---|---|
| High | High (similar count) | 4XX errors originate from the origin (missing resources, auth issues) — **origin issue** |
| High | Low or zero | 4XX generated by EO rules (WAF, rate limiting, ACL) — **EO configuration issue** |
| Low | High | Origin returns 4XX but CDN cache serves valid responses — **origin issue, CDN mitigates** |
**Additional signals:**
- If edge has 3XX responses but origin does not, these are typically EO-generated redirects (e.g., HTTP→HTTPS forced redirect).
- If edge total ≈ origin total, most requests are being forwarded to origin (low cache hit rate).
**Step 4**: Provide root cause conclusion and recommendations
- Clearly label as "origin issue", "CDN/EO issue", or "mixed / further investigation needed"
- For origin issues: recommend checking origin server logs, resource availability, and backend health
- For CDN issues: recommend checking EO security rules, cache configuration, and node status
- For deeper analysis: recommend using `eo-log-analyzer.md` for log-level troubleshooting
**Output recommendation**: Present the response as "edge vs origin comparison table + root cause conclusion + recommendations".
## Output Format
### Scenario A: Origin Health Inspection Report
#### A1: Single Domain Report
```markdown
## Origin Health Inspection — <domain> (Last <N> Hours/Days)
**Zone**: <zone name> (ZoneId: <zone-id>) or "All Zones (account-level)"
**Query Time Range**: <start time> – <end time>
**Health Score**: <score> (✅ Healthy / ⚠️ Anomaly Detected / 🔴 Critical Anomaly)
### Origin Status Code Distribution
| Status Code Category | Request Count | Ratio |
|---|---|---|
| 2xx | ... | ...% |
| 3xx | ... | ...% |
| 4xx | ... | ...% ⚠️ (if > 5%) |
| 5xx | ... | ...% ⚠️ (if > 5%) |
### Top Error Codes (if anomaly detected)
| Status Code | Request Count | Ratio |
|---|---|---|
| 502 | ... | ...% |
| 404 | ... | ...% |
| ... | ... | ... |
> Use `DimensionName=origin-status-code` to get exact error code breakdown.
### Quick Root Cause
- <one-sentence root cause conclusion>
- Recommendation: <next step>
```
#### A2: Account-Wide Anomaly Scan Report
```markdown
## Origin Health Scan — Account Overview (Last <N> Hours/Days)
**Query Time Range**: <start time> – <end time>
**Scope**: All Zones (account-level)
### Account-Level Summary
| Status Code Category | Request Count | Ratio |
|---|---|---|
| 2xx | ... | ...% |
| 3xx | ... | ...% |
| 4xx | ... | ...% ⚠️ (if > 5%) |
| 5xx | ... | ...% |
### Anomalous Domains (Error Ratio > 5%)
| Domain | Total Requests | 2XX | 4XX | 5XX | Error Ratio |
|---|---|---|---|---|---|
| xxx.example.com | ... | ... | ... | ... | ...% ⚠️ |
| yyy.example.com | ... | ... | ... | ... | ...% ⚠️ |
| ... | ... | ... | ... | ... | ... |
### Top Error Code Breakdown (worst domains)
**xxx.example.com**:
| Status Code | Count | Ratio |
|---|---|---|
| 404 | ... | ...% |
| 502 | ... | ...% |
### Recommendations
1. <recommendation for most critical domain>
2. <recommendation for next domain>
```
### Scenario B: Fault Root Cause Analysis
```markdown
## Fault Root Cause Analysis — <domain> (Last <N> Hours)
**Query Time Range**: <start time> – <end time>
### Edge vs Origin Comparison
| Metric | Edge Node | Origin |
|---|---|---|
| Total Requests | ... | ... |
| 2XX Count | ... | ... |
| 4XX Count | ... | ... |
| 5XX Count | ... | ... |
| Error Ratio (4XX+5XX) | ...% | ...% |
### Root Cause Conclusion
**5XX Analysis**: <Edge 5XX vs Origin 5XX comparison and conclusion>
**4XX Analysis**: <Edge 4XX vs Origin 4XX comparison and conclusion>
**Overall**: <"origin issue" / "CDN/EO issue" / "mixed">
- <supporting evidence>
### Recommendations
1. <recommendation 1>
2. <recommendation 2>
```
## Common Abnormal Status Code Reference
When origin-pull or edge responses contain error status codes, use the following reference to understand the cause and guide troubleshooting. For the full list and detailed troubleshooting steps, see: [Abnormal Status Code Reference](https://edgeone.ai/document/58009) and [4XX/5XX Troubleshooting Guide](https://edgeone.ai/document/67228).
### Standard Status Codes (returned by EO edge nodes)
| Status Code | Meaning | Common Cause |
|---|---|---|
| 400 | Bad Request | Request method not allowed by EdgeOne. See [HTTP Limits](https://edgeone.ai/document/63624) |
| 403 | Forbidden | Token authentication failure (rule engine), or compliance interception |
| 416 | Range Not Satisfiable | Invalid Range request (e.g., `rangeStart < 0`, `rangeStart > rangeEnd`, or `rangeStart > FileSize`) |
| 418 | Config Not Found | Node cannot read domain config — check CNAME, domain binding, or dispatch |
| 423 | Request Loop | CDN-Loop detected (loops ≥ 16). See [CDN-Loop](https://edgeone.ai/document/54211) |
### EdgeOne Custom Status Codes (520–599)
> ⚠️ Business origins should **avoid using 520–599** status codes to prevent confusion with EO's custom codes.
#### Connection & Origin Communication Errors
| Status Code | Meaning | Troubleshooting |
|---|---|---|
| 520 / 550 | Origin connection reset (after request sent) | Origin sent RST after connection was established. Check origin server logs for forced disconnections (overload, firewall) |
| 521 / 551 | Origin refused connection (TCP handshake) | Origin sent RST during TCP handshake. Check origin port availability and firewall rules for EO IP ranges |
| 522 / 552 | Origin connection timeout (TCP handshake) | Origin did not respond during TCP handshake. Check if origin is down or network path is blocked |
| 523 / 553 | Origin DNS resolution failure | Domain-based origin DNS lookup failed. Verify the origin domain's DNS configuration |
| 524 / 554 | Origin response timeout | Connection established but origin did not respond in time. Check origin load and processing latency |
| 525 / 555 | SSL handshake failure | HTTPS origin SSL handshake failed. Check origin SSL certificate validity, port (443), and protocol compatibility |
#### Security & Rule Interception
| Status Code | Meaning | Troubleshooting |
|---|---|---|
| 566 | Managed Rules block | Blocked by [Web Protection — Managed Rules](https://edgeone.ai/document/56828). Check managed rule logs |
| 567 | Custom Rules / Rate Limiting / Bot Management block | Blocked by custom rules, rate limiting, or bot management. Review rule conditions and whitelist |
#### Platform Errors
| Status Code | Meaning | Troubleshooting |
|---|---|---|
| 545 | Edge Function execution error | Edge function runtime error (e.g., undefined variable). Check function code |
| 570 | Platform global rate limit | Hit platform-level rate limit. Contact EdgeOne support |
FILE:references/observability/eo-traffic-daily-report.md
# eo-traffic-daily-report
Periodically query L7/L4 traffic trends and automatically generate Markdown daily reports with bandwidth peaks, request counts, top domains, and top regions. Supports multi-day trend comparison and per-domain multi-dimensional drill-down analysis (country, province, IP, UA, status code, URL path, referer, etc.).
## APIs Involved
| Action | Description |
|---|---|
| DescribeTimingL7AnalysisData | Query L7 time-series traffic/bandwidth/request data |
| DescribeTopL7AnalysisData | Query L7 top domains, top regions, and other dimensional ranking data |
| DescribeTimingL4Data | Query L4 time-series traffic/bandwidth/connection data |
> **Command usage**: This document only lists API names and workflow guidance.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm complete parameters and response descriptions.
> For full filter condition documentation, see [Metric Analysis Filter Conditions](https://edgeone.ai/document/56985).
### DescribeTopL7AnalysisData Available Dimensions
`MetricName` determines the statistical dimension. Below is the complete dimension list:
| Dimension | Traffic MetricName | Request MetricName | Return Value Meaning |
|---|---|---|---|
| Domain | `l7Flow_outFlux_domain` | `l7Flow_request_domain` | Domain name (e.g. `example.com`) |
| Country/Region | `l7Flow_outFlux_country` | `l7Flow_request_country` | ISO 3166-1 alpha-2 country code (e.g. `CN`, `US`) |
| Province (Mainland China) | `l7Flow_outFlux_province` | `l7Flow_request_province` | Tencent Cloud province code (see mapping table below) |
| Status Code | `l7Flow_outFlux_statusCode` | `l7Flow_request_statusCode` | HTTP status code (e.g. `200`, `404`, `503`) |
| URL Path | `l7Flow_outFlux_url` | `l7Flow_request_url` | Request path (e.g. `/api/v1/data`) |
| Referer | `l7Flow_outFlux_referers` | `l7Flow_request_referers` | Referer source page |
| Client IP | `l7Flow_outFlux_sip` | `l7Flow_request_sip` | Client source IP address |
| User-Agent | `l7Flow_outFlux_ua` | `l7Flow_request_ua` | Full UA string |
| Device Type | `l7Flow_outFlux_ua_device` | `l7Flow_request_ua_device` | Device type (e.g. Mobile, Desktop) |
| Browser Type | `l7Flow_outFlux_ua_browser` | `l7Flow_request_ua_browser` | Browser type (e.g. Chrome, Safari) |
| OS | `l7Flow_outFlux_ua_os` | `l7Flow_request_ua_os` | Operating system (e.g. Windows, iOS) |
| Resource Type | `l7Flow_outFlux_resourceType` | `l7Flow_request_resourceType` | Resource type (e.g. html, jpg, js) |
> **Usage tips**:
> - When drilling down into a domain, select dimension combinations as needed — no need to query all dimensions at once
> - Common combinations: Country + Province + IP (locate traffic sources), Status Code + URL Path (locate abnormal requests), Referer (analyze traffic entry points), UA (identify client types)
## Data Conventions
When processing EdgeOne data analysis API responses and generating reports, the following conventions must be observed:
### Unit Conversion: Base-1000 (SI Standard)
EdgeOne uniformly uses **base-1000** (SI standard), consistent with networking/storage industry conventions:
| Source Unit | Conversion | Target Unit |
|---|---|---|
| Byte | ÷ 1,000 | KB |
| KB | ÷ 1,000 | MB |
| MB | ÷ 1,000 | GB |
| bps | ÷ 1,000 | Kbps |
| Kbps | ÷ 1,000 | Mbps |
| Mbps | ÷ 1,000 | Gbps |
> **Never use base-1024**. All traffic and bandwidth values in output reports must use base-1000 conversion.
### Timestamp Left-Alignment Rule
API time-series data points use **timestamp left-alignment**: a data point at timestamp `T` represents aggregated data for the interval **[T, T + interval)**.
For example, when querying with `day` granularity, timestamp `2026-03-23T00:00:00+08:00` represents the full day of **03-23 (00:00 – next day 00:00)**.
### Full Data vs Post-Mitigation Data
L7 access data query APIs (`DescribeTimingL7AnalysisData`, `DescribeTopL7AnalysisData`) **return post-mitigation clean traffic/request counts by default** (i.e., excluding requests blocked/challenged by the Web Security module).
- When generating **data reports** (daily, weekly, trend analysis, etc.), provide **complete full data** by including both mitigated and non-mitigated data in `Filters`:
```
--Filters '[{"Key":"mitigatedByWebSecurity","Operator":"equals","Value":["yes","no"]}]'
```
- Only filter by mitigation status when the user explicitly requests clean traffic or blocked traffic only.
> `mitigatedByWebSecurity` accepts: `yes` (blocked/challenged by Web Security module), `no` (normal requests not mitigated).
> Multiple values within the same filter key use **Or** logic; different filter keys use **And** logic.
### Domain Filtering
In `DescribeTimingL7AnalysisData` and `DescribeTopL7AnalysisData`, use the `domain` key in `Filters` to filter data for a specific domain:
```
--Filters '[{"Key":"domain","Operator":"equals","Value":["example.com"]},{"Key":"mitigatedByWebSecurity","Operator":"equals","Value":["yes","no"]}]'
```
> Note: `domain` filter and `mitigatedByWebSecurity` filter must be passed together (different keys use And logic).
### Country/Region Codes
The country/region dimension returns [ISO 3166-1 alpha-2](https://www.iso.org/iso-3166-country-codes.html) standard codes. Common mappings:
| Code | Country/Region | Code | Country/Region | Code | Country/Region |
|---|---|---|---|---|---|
| CN | China | US | United States | JP | Japan |
| KR | South Korea | SG | Singapore | DE | Germany |
| GB | United Kingdom | FR | France | AU | Australia |
| CA | Canada | BR | Brazil | IN | India |
| RU | Russia | ID | Indonesia | TH | Thailand |
> Refer to the ISO 3166-1 standard for the complete code table. Reports should display both the code and the country name.
### Province Code Mapping (Mainland China)
The province dimension returns Tencent Cloud internal province codes (integers) that must be mapped to province names. `-1` indicates overseas.
For complete mapping, refer to [Tencent Cloud Region/ISP Mappings](https://www.tencentcloud.com/document/product/228/6316#region.2Fisp-mappings). Common mappings:
| Code | Province | Code | Province | Code | Province | Code | Province |
|---|---|---|---|---|---|---|---|
| -1 | Overseas | 22 | Beijing | 1050 | Shanghai | 4 | Guangdong |
| 1442 | Zhejiang | 120 | Jiangsu | 121 | Anhui | 122 | Shandong |
| 2 | Fujian | 1135 | Hubei | 1465 | Jiangxi | 1466 | Hunan |
| 182 | Henan | 1177 | Hebei | 1051 | Chongqing | 1068 | Sichuan |
| 153 | Yunnan | 118 | Guizhou | 173 | Guangxi | 1441 | Hainan |
| 86 | Inner Mongolia | 1469 | Shanxi | 1464 | Liaoning | 1445 | Jilin |
| 1467 | Heilongjiang | 1468 | Tianjin | 145 | Gansu | 1076 | Ningxia |
| 119 | Qinghai | 152 | Xinjiang | | | | |
> Reports must map codes to province names; never output raw numeric codes.
### IP Segment Aggregation
When querying by client IP (`_sip`) dimension, the top IP list typically contains multiple IPs from the same /24 segment. Reports should include **IP segment aggregation analysis**:
- Aggregate IPs sharing the same /24 prefix (first 3 octets)
- Show total traffic and IP count per /24 segment
- Helps identify NAT gateways, CDN origin IP pools, proxy clusters, etc.
### Adaptive Traffic Unit Display
Traffic values in reports should automatically select appropriate units based on magnitude:
| Condition | Display Format | Example |
|---|---|---|
| ≥ 1 GB (10⁹ Byte) | `xx.xx GB` | 10.39 GB |
| ≥ 1 MB (10⁶ Byte) | `xxx.xx MB` | 138.44 MB |
| ≥ 1 KB (10³ Byte) | `xxx.xx KB` | 716.78 KB |
| < 1 KB | `xxx B` | 494 B |
### Large Number Abbreviation by Language
Request counts and other large numbers should be abbreviated differently based on the **report language context**:
#### Chinese Context (中文语境)
Use Chinese numeric units (万 / 亿):
| Condition | Display Format | Example |
|---|---|---|
| ≥ 1 亿 (10⁸) | `x.xx 亿` | 302,000,000 → `3.02 亿` |
| ≥ 1 万 (10⁴) | `x.xx 万` | 53,581 → `5.36 万` |
| < 1 万 | raw number with comma separator | 8,192 |
> **Rules**:
> - Always use `万` (10⁴) and `亿` (10⁸) as abbreviation tiers — never use `千` (K) in Chinese reports
> - Keep 2 decimal places; trailing zeros may be omitted (e.g. `5.30 万` → `5.3 万`)
> - Examples: `1,592,166 次` → `159.22 万次`, `53,581,130 次` → `5,358.11 万次`, `312,000,000 次` → `3.12 亿次`
#### English Context
Use standard K / M / B suffixes (base-1000):
| Condition | Display Format | Example |
|---|---|---|
| ≥ 1 B (10⁹) | `x.xx B` | 3,020,000,000 → `3.02 B` |
| ≥ 1 M (10⁶) | `x.xx M` | 5,358,113 → `5.36 M` |
| ≥ 1 K (10³) | `x.xx K` | 53,581 → `53.58 K` |
| < 1 K | raw number | 812 |
> **Rules**:
> - K = 10³, M = 10⁶, B = 10⁹ (base-1000, consistent with SI convention)
> - Keep 2 decimal places; trailing zeros may be omitted
> - Examples: `1,592,166 requests` → `1,592.17 K`, `5,358,113 requests` → `5.36 M`
#### Language Detection
- If the user communicates in Chinese or requests a Chinese report → use 万/亿
- If the user communicates in English or requests an English report → use K/M/B
- When in doubt, match the language used in the user's most recent message
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli`. If no credentials are configured in the environment, guide the user to log in first:
```sh
tccli auth login
```
> The terminal will print an authorization link and block until the user completes browser authorization; the command exits automatically upon success.
> Never ask the user for `SecretId` / `SecretKey`, and never execute commands that might expose credential contents.
2. A ZoneId must be obtained first. Refer to `../api/zone-discovery.md`.
3. Obtain the account UIN for report headers by calling:
```sh
tccli cam GetUserAppId
```
> The response contains `Uin` (current operator), `OwnerUin` (root account), and `AppId`.
> - Use **`OwnerUin`** as the account identifier displayed in reports (this is the Tencent Cloud root account UIN).
> - Do **not** use `AppId` (returned in data API responses as `TypeKey`) — it is an internal numeric ID, not the user-facing account identifier.
## Scenario A: Generate a Traffic Daily Report for a Specific Date
**Trigger**: The user says "generate yesterday's traffic report", "what was the peak bandwidth in the last 24 hours", "generate the traffic report for 2026-03-20", "analyze which countries/IPs/UAs the traffic for example.com comes from", "show me the status code distribution for this domain".
### Workflow
**Step 1**: Determine the report date range
- If the user says "yesterday" / "today", automatically calculate the corresponding start and end times (ISO 8601 format)
- Default time range is "yesterday 00:00 to 23:59"
- `DescribeTimingL7AnalysisData` has approximately 10-minute data delay; avoid querying the most recent 10 minutes
**Step 2**: Collect L7 time-series data
Call `DescribeTimingL7AnalysisData` to query the following metrics:
- `l7Flow_outFlux`: EdgeOne response traffic, in Bytes (for total traffic calculation)
- `l7Flow_outBandwidth`: EdgeOne response bandwidth, in bps (for peak bandwidth calculation)
- `l7Flow_request`: Access request count, in requests (for total request count)
- Recommend using `hour` granularity (`Interval=hour`) to identify peak periods
- **Must** include `Filters` for full data (see "Data Conventions > Full Data vs Post-Mitigation Data")
**Step 3**: Collect L7 Top data
Call `DescribeTopL7AnalysisData` to query:
- `l7Flow_outFlux_domain`: Response traffic Top by domain (default Top 10)
- `l7Flow_outFlux_country`: Traffic Top by country/region (default Top 5)
- Increase `Limit` parameter if the zone has many domains
- **Must** include `Filters` for full data (see "Data Conventions > Full Data vs Post-Mitigation Data")
**Step 4**: Collect L4 time-series data (if L4 proxy is enabled)
Call `DescribeTimingL4Data` to query:
- `l4Flow_outBandwidth`: Outbound bandwidth peak
- `l4Flow_connections`: Concurrent connections
- If the zone does not use L4 proxy, skip this step and note it in the report
**Step 5**: Compile and generate the Markdown daily report
Organize the above data into a structured report containing: bandwidth peak (with peak period), total request count, top domain table, top region table, and L4 data summary.
**Output recommendation**: Output as a complete Markdown document with title, time range, key metric summary, and detailed tables.
### Domain Drill-Down: Multi-Dimensional Traffic Distribution Analysis
**Trigger**: After viewing a daily/weekly report, the user requests further analysis of a specific domain's traffic distribution, e.g. "show which countries the traffic for example.com comes from", "analyze the visitor IPs and UAs for this domain", "what abnormal status codes does this domain have".
> This sub-workflow is an on-demand extension of Scenario A / B, invoked when the user needs deep analysis of a specific domain.
**Step 1**: Confirm drill-down parameters
- **Target domain**: Selected from user input or the previous step's top domain ranking
- **Time range**: Inherit the current report's time range, or as specified by the user
- **Analysis dimensions**: Select based on user needs. Common combinations:
| Analysis Purpose | Recommended Dimensions |
|---|---|
| Traffic source analysis | Country/Region (`_country`) + Province (`_province`) + Client IP (`_sip`) |
| Client profiling | User-Agent (`_ua`) + Device Type (`_ua_device`) + Browser (`_ua_browser`) + OS (`_ua_os`) |
| Request pattern analysis | Status Code (`_statusCode`) + URL Path (`_url`) + Referer (`_referers`) |
| Resource type analysis | Resource Type (`_resourceType`) |
| Full profile | All of the above as needed |
> No need to query all dimensions at once — select based on user focus. If the user says "full analysis", recommend: Country + Province + IP + UA + Status Code + URL Path + Referer.
**Step 2**: Collect Top data by dimension
For each selected dimension, call `DescribeTopL7AnalysisData`:
- **Must** include both `domain` filter and `mitigatedByWebSecurity` full-data filter in `Filters`
- Choose between traffic metrics and request count metrics as needed (typically prioritize traffic metrics `l7Flow_outFlux_*`; supplement with request metrics `l7Flow_request_*` when necessary)
- Recommend setting `Limit` to 20; adjust based on user needs
- Dimension queries can be executed in parallel for efficiency
**Step 3**: Collect domain time-series summary data (optional)
Call `DescribeTimingL7AnalysisData` with `domain` filter in `Filters` to query the domain's traffic, bandwidth, and request count totals as core report metrics.
**Step 4**: Data post-processing
- **Province code mapping**: Convert Tencent Cloud province codes to province names (see "Data Conventions > Province Code Mapping")
- **Country code annotation**: Append country names alongside ISO 3166-1 alpha-2 codes
- **IP segment aggregation**: Merge IPs in the same /24 segment (see "Data Conventions > IP Segment Aggregation")
- **Status code classification**: Group by 2xx/3xx/4xx/5xx categories. Note that EdgeOne uses custom status codes in the 520-599 range (e.g., 566 for managed rule blocks, 567 for custom rule/rate limiting/bot management blocks). See [Abnormal Status Code Reference](https://edgeone.ai/document/58009) for full details
- **UA classification**: Identify primary client types (e.g. Dart apps, crawlers, browsers, etc.)
**Step 5**: Generate traffic distribution report
Output the report following the "Output Format > Domain Drill-Down" template below, including core metrics + per-dimension Top tables + comprehensive insights.
## Scenario B: Multi-Day Trend Comparison
**Trigger**: The user says "compare the traffic trend over the last 7 days", "how has bandwidth changed this week vs last week", "what are the daily request counts for the past week".
### Workflow
**Step 1**: Determine the comparison time range
- Parse the user-described time range and calculate start/end times
- `DescribeTimingL7AnalysisData` supports a maximum of 31 days per query
**Step 2**: Collect multi-day aggregated data
Call `DescribeTimingL7AnalysisData` with `day` granularity (`Interval=day`) to query:
- `l7Flow_outFlux`: Daily response traffic (for total and trend calculation)
- `l7Flow_outBandwidth`: Daily bandwidth peak
- `l7Flow_request`: Daily total requests
- **Must** include `Filters` for full data (see "Data Conventions > Full Data vs Post-Mitigation Data")
- When not specifying `ZoneIds` (or passing `["*"]`), account-level aggregated data is returned
**Step 2.1**: Collect L7 Top data (optional but recommended)
Call `DescribeTopL7AnalysisData` to query Top domains by response traffic:
- When not specifying `ZoneIds` (or passing `["*"]`), account-level aggregated data is returned; specify individual ZoneIds to filter by zone
- **Must** include `Filters` for full data
**Step 3**: Calculate trend metrics
For each day's data, calculate:
- Day-over-day change rate (percentage change compared to previous day)
- Week-over-week change rate (if time range exceeds 7 days)
- Peak day and trough day markers
**Output recommendation**: Output as a trend table including date, bandwidth peak, day-over-day change, total requests, day-over-day change, with peak day and anomaly markers.
## Output Format
### Scenario A: Daily Traffic Report
```markdown
## Traffic Daily Report — <Date>
**Account**: <OwnerUin>
**Zone**: <zone name> (ZoneId: <zone-id>)
**Report Time Range**: <start time> – <end time>
### Key Metrics
| Metric | Value | Peak Period |
|---|---|---|
| L7 Total Response Traffic | x.xx GB | — |
| L7 Peak Bandwidth | x.xx Mbps | HH:MM |
| L7 Total Requests | x,xxx K / x.xx 万 | — |
| L4 Peak Bandwidth | x.xx Mbps | HH:MM |
| L4 Peak Concurrent Connections | x,xxx | HH:MM |
### Top N Domains (by Traffic)
| Domain | Traffic | Share |
|---|---|---|
| ... | ... | ... |
### Top 5 Regions (by Traffic)
| Region | Traffic | Share |
|---|---|---|
| ... | ... | ... |
```
### Domain Drill-Down: Traffic Distribution Analysis
```markdown
## Traffic Distribution Report — <Domain>
**Account**: <OwnerUin>
**Domain**: <domain>
**Report Date**: <date> (<day of week>)
**Data Type**: Full data (including requests mitigated by Web Security)
**Unit Conversion**: Base-1000 (SI standard)
---
### Key Metrics
| Metric | Value |
|---|---|
| Total Response Traffic | **xx.xx GB** |
---
### Distribution by Country/Region (Top N)
| Rank | Country Code | Country/Region | Traffic | Share |
|---|---|---|---|---|
| 1 | XX | <country name> | xx.xx GB | xx.xx% |
| ... | ... | ... | ... | ... |
**Analysis**: <summarize traffic geographic concentration>
---
### Distribution by Province (Mainland China + Overseas)
| Rank | Province | Traffic | Share |
|---|---|---|---|
| 1 | Overseas | xx.xx GB | xx.xx% |
| 2 | <province name> | xxx.xx MB | x.xx% |
| ... | ... | ... | ... |
**Analysis**: <summarize domestic vs overseas traffic ratio>
---
### Distribution by Client IP (Top N)
**IP Segment Aggregation**:
| IP Segment | Total Traffic | IP Count |
|---|---|---|
| x.x.x.0/24 | xxx.xx MB | x IPs |
| ... | ... | ... |
**IP Details**:
| Rank | Client IP | Traffic | Share |
|---|---|---|---|
| 1 | x.x.x.x | xx.xx MB | x.xx% |
| ... | ... | ... | ... |
**Analysis**: <analyze IP segment clustering patterns, such as NAT gateways, proxy pools, etc.>
---
### Distribution by User-Agent (Top N)
| Rank | User-Agent | Traffic | Share |
|---|---|---|---|
| 1 | <UA string> | xx.xx GB | xx.xx% |
| ... | ... | ... | ... |
**Analysis**: <identify primary client types, such as app SDKs, crawlers, browsers>
---
### Distribution by Status Code (Top N)
**Status Code Category Summary**:
| Category | Traffic | Share | Description |
|---|---|---|---|
| 2xx | xx.xx GB | xx.xx% | Success |
| 3xx | xxx.xx MB | x.xx% | Redirect |
| 4xx | xxx.xx MB | x.xx% | Client Error |
| 5xx | xxx.xx MB | x.xx% | Server Error |
**Status Code Details**:
| Rank | Status Code | Traffic | Share |
|---|---|---|---|
| 1 | 200 | xx.xx GB | xx.xx% |
| 2 | 304 | xxx.xx MB | x.xx% |
| ... | ... | ... | ... |
**Analysis**: <flag abnormal status code ratios; if 4xx/5xx is disproportionately high, investigate further. For EdgeOne custom status codes (520-599), see [Abnormal Status Code Reference](https://edgeone.ai/document/58009) for details>
---
### Distribution by URL Path (Top N)
| Rank | URL Path | Traffic | Share |
|---|---|---|---|
| 1 | /path/to/resource | xx.xx GB | xx.xx% |
| ... | ... | ... | ... |
**Analysis**: <identify hot paths and large file downloads>
---
### Distribution by Referer (Top N)
| Rank | Referer | Traffic | Share |
|---|---|---|---|
| 1 | https://example.com/page | xx.xx GB | xx.xx% |
| 2 | (empty Referer) | xxx.xx MB | x.xx% |
| ... | ... | ... | ... |
**Analysis**: <analyze traffic entry points, identify external links and direct access>
---
### Comprehensive Insights
1. **Traffic source characteristics**: <summarize geographic distribution and concentration>
2. **Client characteristics**: <summarize primary UA and access patterns>
3. **Request characteristics**: <summarize status code health, hot paths>
4. **Areas of concern**: <list anomalies requiring further investigation>
```
> **Domain drill-down template usage notes**:
> - Dimension sections are optional; include only the dimensions actually queried, omit the rest
> - The "Analysis" paragraph after each dimension table is mandatory — do not list data without analysis
> - Status code dimension must output both "Category Summary" and "Details" tables
> - IP dimension must output both "IP Segment Aggregation" and "IP Details" tables
> - "Comprehensive Insights" is mandatory and must synthesize findings across multiple dimensions
### Scenario B: Multi-Day Trend Comparison
```markdown
## Traffic Trend Report — <Start Date> – <End Date>
**Account**: <OwnerUin>
**Report Time Range**: <start time> – <end time> (UTC+8)
**Data Granularity**: Day
**Data Type**: Full data (including requests mitigated by Web Security)
**Unit Conversion**: Base-1000 (SI standard)
---
### Key Metrics Summary
| Metric | Value |
|---|---|
| L7 Total Response Traffic | **xx.xx GB** |
| L7 Peak Bandwidth | **x.xx Mbps** (<peak date> <day of week>) |
| L7 Total Requests | **x,xxx.xx K** |
---
### Daily Traffic Trend
| Date | Response Traffic | DoD Change | Peak Bandwidth | DoD Change | Total Requests | DoD Change | Notes |
|---|---|---|---|---|---|---|---|
| MM-DD (Day) | xx.xx GB | — | x.xx Mbps | — | xxx.xx K / x.xx 万 | — | 📈 Highest traffic day |
| MM-DD (Day) | xx.xx GB | ±x.x% | x.xx Mbps | ±x.x% | xxx.xx K / x.xx 万 | ±x.x% | |
| ... | ... | ... | ... | ... | ... | ... | |
| MM-DD (Day) | xx.xx GB | ±x.x% | x.xx Mbps | ±x.x% | xxx.xx K / x.xx 万 | ±x.x% | 📉 Lowest traffic day |
| MM-DD (Day) | xx.xx GB | ±x.x% | x.xx Mbps | ±x.x% | xxx.xx K / x.xx 万 | ±x.x% | ⚠️ Peak bandwidth day |
**Trend Analysis**:
- <describe overall traffic trend patterns, such as weekday/weekend differences>
- <flag anomalous fluctuations and analyze possible causes, e.g. bandwidth peak DoD far exceeding traffic growth indicates short bursts>
- <describe correlation between request volume and traffic trends>
---
### Top N Domains (by Response Traffic)
| Rank | Domain | Traffic | Share |
|---|---|---|---|
| 1 | example.com | xx.xx GB | xx.xx% |
| 2 | cdn.example.com | xxx.xx MB | x.xx% |
| ... | ... | ... | ... |
**Domain Analysis**:
- <identify high-traffic domains and their share>
- <group domains by category, such as subdomains of the same site, test domains, etc.>
- <flag anomalous or noteworthy domains>
```
> **Template usage notes**:
> - Date column format: `MM-DD (Day)`, annotate dates according to the timestamp left-alignment rule
> - DoD change: first day shows "—", subsequent days calculate `(current - previous) / previous × 100%`
> - Notes column markers: 📈 Highest traffic day, 📉 Lowest traffic day, ⚠️ Peak bandwidth day
> - Traffic ≥ 1 GB displays as `xx.xx GB`, < 1 GB displays as `xxx.xx MB`
> - Request counts use language-appropriate abbreviation: Chinese → 万/亿, English → K/M/B (see "Data Conventions > Large Number Abbreviation by Language")
> - Trend analysis and domain analysis are mandatory — provide meaningful interpretation based on the data
FILE:references/security/README.md
# EdgeOne Security Protection Reference
Configuration and operations guide for security policy configuration snapshots, template coverage audits, and domain IP group blocklist identification.
## Quick Decision Tree
```
What does the user want to do?
│
├─ "Generate a security status report for this week"
│ "Check the current security configuration"
│ └─ → `security-weekly-report.md` 🟢 Low risk · Sequential data collection, output conclusions first with concise snapshot
│
├─ "Which domains don't have a security template"
│ "Help me check template coverage"
│ └─ → `security-template-audit.md` 🟢 Low risk · List unbound domains, prompt for manual confirmation
│
├─ "Check which IP group in example.com's security policy is a blocklist"
│ "Which IP group blocks traffic for this domain"
│ └─ → `domain-blacklist-inspector.md` 🟢 Low risk · Read-only query, identify blocklist IP groups
│
├─ "Help me analyze recent attack IP concentration"
│ "Block these IPs" "IP ban"
│ └─ → `ip-threat-blacklist.md` 🔴 High risk · Mandatory Diff display + double confirmation before write operations, only allowed to write to designated blocklist group
│
└─ Not sure which API to call
└─ → `../api/api-discovery.md`
```
## Prerequisites
All operations require calling APIs via tccli. Before first use, complete the following:
1. **Tool check** — Read `../api/README.md` to complete tccli installation and credential configuration
2. **Get ZoneId** — Read `../api/zone-discovery.md` to obtain the zone ID
## Files in This Directory
| File | Risk Level | Core Trigger Scenario |
|---|---|---|
| `security-weekly-report.md` | 🟢 Low risk | Periodically generate security configuration snapshots to detect abnormal policy changes |
| `security-template-audit.md` | 🟢 Low risk | Audit security policy template coverage, find domains without bound templates |
| `domain-blacklist-inspector.md` | 🟢 Low risk | Query security policies associated with a specific domain, identify IP groups serving as blocklists |
| `ip-threat-blacklist.md` | 🔴 High risk | Analyze L7 high-concentration threat IPs, execute IP blocklist banning (write operations require double confirmation) |
## Reference Links
- [EdgeOne Product Documentation](https://edgeone.ai/document)
- [EdgeOne API Documentation](https://edgeone.ai/document/50454)
- `../api/README.md`
FILE:references/security/domain-blacklist-inspector.md
# domain-blacklist-inspector
Query the security policies associated with a specified EdgeOne domain, parse IP group references in rules with `action=block`, and output a blocklist IP group mapping report.
> **Purpose**: This skill is a **prerequisite diagnostic step** for write operations (such as `eo-ip-threat-blacklist`). Before writing entries to a blocklist IP group, use this skill to identify the correct target blocklist group ID to avoid operating on the wrong IP group.
## APIs Involved
| Action | Description |
|---|---|
| `DescribeSecurityPolicy` | Query the security policy configuration associated with a domain |
| `DescribeSecurityIPGroup` | Query all IP groups under the zone |
| `DescribeSecurityIPGroupContent` | Query detailed entries of a specified IP group |
> **Command usage**: This document only lists API names and process guidelines.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm the complete parameters and response descriptions.
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli` — confirm login authentication is complete before execution.
2. You need to obtain the ZoneId first — see `../api/zone-discovery.md`.
3. The user must explicitly provide the target domain (e.g., `example.com`); if the user says "this domain" without specifying, ask for clarification first.
## Execution Flow
**Trigger**: User says "check which IP group in example.com's security policy is a blocklist", "which IP group blocks traffic for this domain", "help me check example.com's IP ban list", "I want to add IPs to the blocklist — first help me identify the blocklist group".
Call the following APIs in order to build the blocklist IP group mapping report:
### Step 1: Get the Security Policy Associated with the Domain
Call the `DescribeSecurityPolicy` API, extract all rules from the result, identify rules with **blocking or banning semantics** (e.g., `action=block`, `Action.Name=Deny`, etc. — determine based on the actual meaning of field values), and record the IP group IDs referenced by these rules.
### Step 2: Get All IP Groups Under the Zone
Call the `DescribeSecurityIPGroup` API, cross-reference the IP group IDs identified in Step 1 with this list to fill in metadata like IP group names.
### Step 3: Query Detailed Entries of Blocklist IP Groups
For each blocklist IP group identified in Step 1, call the `DescribeSecurityIPGroupContent` API to query its detailed contents.
### Blocklist IP Group Identification Rules
**Use rule action as the primary criterion**:
- **Confirmed as blocklist group**: IP groups directly referenced by `action=block` rules in security policies
- **Auxiliary reference**: IP group names containing semantic keywords like `blacklist`, `blocklist`, `deny`, or localized equivalents can serve as supplementary evidence, but should not be the sole criterion
> ⚠️ **Note**:
> - Do not judge solely by IP group name — must verify against rule actions in security policies.
> - If multiple IP groups are referenced by `action=block` rules, list them all and explain the rule context for each.
> - If determination is unclear, state this honestly and list all IP groups referenced by blocking rules for the user to manually confirm.
## Output Format
> **Language note**: Adapt the report language to match the user's language. The template below is an example — output should be in the same language the user is using.
```markdown
## Blocklist IP Group Query Results
**Domain**: example.com (ZoneId: zone-xxx)
**Query Date**: YYYY-MM-DD
**Data Sources**: `DescribeSecurityPolicy` / `DescribeSecurityIPGroup` / `DescribeSecurityIPGroupContent`
### Blocklist IP Groups (Referenced by action=block Rules)
> The following IP groups are directly referenced by blocking rules in the security policy — they are the blocklist IP groups for this domain:
| IP Group Name | **IP Group ID** | Entry Count | Associated Rule ID | Rule Action |
|---|---|---|---|---|
| blacklist-prod | **ipg-xxxxxxxx** | 42 | rule-001 | Block |
> To write entries to the above IP group, use IP Group ID: `ipg-xxxxxxxx`
### IP Group Detailed Entries
**blacklist-prod** (ipg-xxxxxxxx)
| # | IP / CIDR | Notes |
|---|---|---|
| 1 | 1.2.3.4 | ... |
| 2 | 5.6.7.8/24 | ... |
> If there are more than 20 entries, show the first 20 and note "N total entries, showing first 20".
### Security Policy Associated Rules Summary
| Rule ID | Rule Name | Action | Referenced IP Group | Notes |
|---|---|---|---|---|
| rule-001 | Blocklist Block | Block | blacklist-prod | ... |
### Additional Notes (if any)
- Anomalous entry notes (e.g., empty IP group, overly broad CIDR like `0.0.0.0/0`, etc.)
- Other noteworthy blocking rules
```
> **Read-only disclaimer**: This skill only performs query operations and does not modify any IP groups or policy configurations. To modify blocklists, use the console or call the appropriate write APIs — confirm the impact scope before operating.
FILE:references/security/ip-threat-blacklist.md
# ip-threat-blacklist
Analyze high-concentration threat IPs based on EdgeOne L7 access data, write them to a designated blocklist IP group, and automate IP banning.
## APIs Involved
| Action | Description |
|---|---|
| `DescribeTopL7AnalysisData` | Query L7 traffic Top data (analyze access concentration by IP dimension) |
| `DescribeSecurityIPGroup` | Query the security IP group list under the zone (confirm target blocklist group) |
| `ModifySecurityIPGroup` | Modify security IP group entries (write blocklist IPs) |
> **Command usage**: This document only lists API names and process guidelines.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm the complete parameters and response descriptions.
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli`. If no valid credentials are configured in the environment, you must first guide the user to complete login:
```sh
tccli auth login
```
> After execution, the terminal will print an authorization link and block until the user completes browser authorization — the command ends automatically upon success.
> Never ask the user for `SecretId` / `SecretKey`, and do not execute commands that might expose credential contents.
2. You need to obtain the ZoneId first — see `../api/zone-discovery.md`.
3. **Write operation security red lines**:
- Before executing `ModifySecurityIPGroup`, you **must** show the complete change Diff to the user and wait for explicit confirmation before executing.
- **Only write to the blocklist IP group specified by the user** — never select or create an IP group on your own.
- If the user has not explicitly specified a target IP group, guide them to first use `domain-blacklist-inspector.md` to identify the target blocklist group ID before proceeding.
## Scenario A: Analyze High-Concentration Threat IPs
**Trigger**: User says "help me analyze recent attack IP concentration", "which IPs have abnormal access volume", "help me check for suspicious IPs".
### Process
1. Confirm the analysis time range (default "last 24 hours"), and explicitly state the start and end time in the report
2. Call the `DescribeTopL7AnalysisData` API, querying Top access data by IP dimension:
- Recommended metrics: request count (`l7Flow_request`) or bandwidth (`l7Flow_flux`)
- Results are sorted by request volume in descending order, take Top N (default Top 20)
3. Perform concentration analysis on the returned IP list:
| Analysis Dimension | Description |
|---|---|
| Request share | Single IP request count / total requests — a high share (e.g., > 5%) indicates abnormal concentration |
| Request rate | Average QPS for a single IP within the time window — significantly exceeding normal levels is suspicious |
| Concentration ranking | Gap between Top 1 IP and Top 10 IP request volumes — a large gap suggests a single-point concentrated attack |
4. Output the analysis report, flag suspected threat IPs, and ask the user whether to proceed with banning
**Output suggestion**: Respond with "Top IP list + concentration analysis + threat assessment", and at the end prompt the user whether to enter Scenario B for banning.
## Scenario B: Execute IP Blocklist Banning
**Trigger**: User says "block these IPs", "IP ban", "add top attack IPs to blocklist".
> ⚠️ **This scenario involves write operations — the double confirmation process must be strictly followed and cannot be skipped.**
### Process
#### Step 1: Confirm Target Blocklist IP Group
1. Ask the user which blocklist IP group to write to (must be explicitly specified by the user — never decide on your own)
2. If the user is unsure about the target IP group, guide them to first execute `domain-blacklist-inspector.md` to look it up
3. Call the `DescribeSecurityIPGroup` API to query and display the target IP group's current status (name, ID, existing entry count)
#### Step 2: Confirm IPs to Be Blocked
1. If the user is coming from Scenario A, use the threat IP list analyzed in Scenario A
2. If the user directly provides an IP list, use it as-is
3. Perform basic validation on the IP list to be blocked:
- Format validation: Ensure each entry is a valid IP address or CIDR (e.g., `1.2.3.4` or `1.2.3.0/24`)
- Duplicate check: Filter out entries already existing in the target IP group to avoid duplicate writes
- Range warning: If large CIDR ranges like `/8` or `/16` exist, specifically alert the user for confirmation
#### Step 3: Display Change Diff + Double Confirmation
Before executing the write operation, you **must** show the following Diff to the user:
```
The following changes will be applied to the IP group:
Target IP group: <IP group name> (ID: ipg-xxxxxxxx)
Current entry count: N entries
New entries (M total):
+ 1.2.3.4
+ 5.6.7.8
+ 9.10.11.0/24
...
Entry count after change: N + M entries
⚠️ This operation takes effect immediately — requests from blocked IPs will be intercepted.
Do you want to proceed? (Yes/No)
```
**You must wait for the user to explicitly reply "Yes" or "Confirm" before calling `ModifySecurityIPGroup`.**
#### Step 4: Execute Write and Verify
1. Call the `ModifySecurityIPGroup` API to write the new IPs to the target blocklist group
2. After writing, call `DescribeSecurityIPGroup` again to query the target IP group and verify the entry count matches expectations
3. Output the operation result summary
## Scenario C: Query Current Blocklist IP Group Status
**Trigger**: User says "check what IPs are in the blocklist", "how many entries are in the blocklist group now".
Call the `DescribeSecurityIPGroup` API to query and display the current entry list of the specified IP group.
> To find the blocklist IP group ID associated with a domain, see `domain-blacklist-inspector.md`.
## Output Format
> **Language note**: Adapt the report language to match the user's language. The templates below are examples — output should be in the same language the user is using.
### Scenario A: Threat IP Analysis Report
```markdown
## Threat IP Concentration Analysis Report
**Zone**: example.com (ZoneId: zone-xxx)
**Analysis Time Range**: 2026-03-16 00:00 – 2026-03-23 00:00
**Data Source**: `DescribeTopL7AnalysisData` (Metric: request count)
### Top IP Access Ranking
| Rank | IP Address | Request Count | Share of Total | Avg QPS | Threat Assessment |
|---|---|---|---|---|---|
| 1 | 1.2.3.4 | 1,200,000 | 12.5% | 1,984/s | 🔴 High risk |
| 2 | 5.6.7.8 | 800,000 | 8.3% | 1,322/s | 🔴 High risk |
| 3 | 9.10.11.12 | 200,000 | 2.1% | 330/s | 🟡 Suspicious |
| ... | ... | ... | ... | ... | ... |
### Concentration Analysis
- Total requests: 9,600,000
- Top 1 IP share: 12.5% (exceeds 5% threshold — abnormally concentrated)
- Top 3 IPs combined share: 22.9%
- Concentration assessment: **Clear signs of single-point concentrated attack**
### Suggested IPs to Block
The following IPs have request share exceeding the threshold or abnormal QPS — recommended for blocklist:
- `1.2.3.4` (share 12.5%, QPS 1,984/s)
- `5.6.7.8` (share 8.3%, QPS 1,322/s)
> To add the above IPs to the blocklist, specify the target blocklist IP group, or reply "block" to proceed.
```
### Scenario B: Banning Operation Result
```markdown
## IP Blocklist Write Result
**Target IP Group**: blacklist-prod (ID: ipg-xxxxxxxx)
**Operation Time**: 2026-03-23 19:00:00
**Data Source**: `ModifySecurityIPGroup`
### Change Summary
| Item | Count |
|---|---|
| New entries added | 2 |
| Skipped (already exist) | 0 |
| Total entries after operation | 44 |
### New Entry Details
| IP / CIDR | Source | Notes |
|---|---|---|
| 1.2.3.4 | Top L7 analysis | Request share 12.5% |
| 5.6.7.8 | Top L7 analysis | Request share 8.3% |
### Verification Result
✅ Write successful — IP group currently has 44 entries, matching expectations.
```
## Important Notes
> ⚠️ **Operation safety notice**:
> - `ModifySecurityIPGroup` is a **full overwrite** API — when calling it, you must pass the complete entry list of the IP group (existing entries + new entries), not just the new entries. Always query existing entries first, merge them, then write — **never overwrite directly and lose existing entries**.
> - Banning takes effect immediately — requests from banned IPs will be intercepted in real time. Execute only after confirming threat IPs.
> - For bulk banning of many IPs (e.g., more than 50), prefer writing a script for one-time execution to avoid omissions from multiple calls.
FILE:references/security/security-template-audit.md
# security-template-audit
Audit EdgeOne security policy template coverage, output template-to-bound-resource mappings, and find domains without any bound templates — suitable for security audit scenarios.
## APIs Involved
| Action | Description |
|---|---|
| `DescribeWebSecurityTemplates` | Query all security policy templates under the zone |
| `DescribeWebSecurityTemplate` | Query detailed configuration of a single template |
| `DescribeSecurityTemplateBindings` | Query the binding relationship between templates and domains |
> **Command usage**: This document only lists API names and process guidelines.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm the complete parameters and response descriptions.
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli` — confirm login authentication is complete before execution.
2. You need to obtain the ZoneId first — see `../api/zone-discovery.md`.
## Execution Flow
**Trigger**: User says "which domains don't have a security template", "help me check template coverage", "are there any domains that missed binding a security policy", "help me audit security templates", "which domains have no security protection", "what's the template binding status", "help me check security templates", "are there domains without bound templates".
Call the following APIs in order to progressively build the template-binding resource mapping:
### Step 1: Get All Security Policy Templates
Call the `DescribeWebSecurityTemplates` API, record each template's `TemplateId` and `TemplateName` as input for subsequent queries.
### Step 2: Get Detailed Configuration for Each Template
Call the `DescribeWebSecurityTemplate` API, focusing on the following fields for subsequent status labeling:
- Whether the template is enabled (overall toggle status)
- Whether each protection module (WAF, CC, Bot, etc.) has rule configurations
### Step 3: Query Domain Binding Relationships for Each Template
Call the `DescribeSecurityTemplateBindings` API, collect the list of domains bound to each template, and aggregate into a global "covered domains set".
### Step 4: Get the Complete Domain List for the Zone
To identify uncovered domains, you need the complete domain list under the zone — call the `DescribeZoneRelatedDomains` API.
> If this API is unavailable or returns empty, try finding other APIs to get the domain list (such as `DescribeAccelerationDomains`) via `../api/api-discovery.md`.
### Step 5: Cross-Compare to Identify Uncovered Domains
Compute the difference between the "covered domains set" (aggregated from Step 3) and the complete zone domain list to produce the list of domains not bound to any template.
> ⚠️ **Note**: If the complete domain list cannot be obtained, explicitly state "currently can only output known coverage based on template binding relationships — cannot confirm whether there are missing domains" — do not make assumptions.
## Output Format
> **Language note**: Adapt the report language to match the user's language. The template below is an example — output should be in the same language the user is using.
```markdown
## Security Template Coverage Audit Report
**Zone**: example.com (ZoneId: zone-xxx)
**Audit Date**: YYYY-MM-DD
**Data Sources**: `DescribeWebSecurityTemplates` / `DescribeWebSecurityTemplate` / `DescribeSecurityTemplateBindings`
### Template-Binding Resource Mapping
| Template Name | Template ID | Bound Domains Count | Bound Domain List | Template Status |
|---|---|---|---|---|
| Production Template | template-xxx | 3 | a.com, b.com, c.com | ✅ Normal |
| Test Template | template-yyy | 0 | — (no domains bound) | ⚠️ Empty template |
### Coverage Summary
- Total templates: N
- Templates with bound domains: N / Empty templates (no domains bound): N
- Total covered domains: N
- **Uncovered domains: N**
### Uncovered Domain List
> The following domains are not bound to any security policy template and have a protection gap:
- example.com
- test.example.com
- staging.example.com
(If the complete domain list is unavailable, note here: "Data is incomplete, showing known coverage only")
### Recommended Actions
- Uncovered domains: Evaluate and bind an appropriate security template
- Empty templates: Confirm whether they are reserved templates; consider cleanup if unused
```
> **Read-only disclaimer**: This skill only performs query operations and does not perform any binding or modification. To bind templates, use the console or call the appropriate write APIs — confirm the impact scope before operating.
FILE:references/security/security-weekly-report.md
# security-weekly-report
Manage the security protection status of EdgeOne zones: generate configuration snapshots, detect abnormal policy changes, and produce security reports.
## APIs Involved
| Action | Description |
|---|---|
| `DescribeSecurityPolicy` | Query the zone's security policy configuration |
| `DescribeWebSecurityTemplates` | Query all security policy templates under the zone |
| `DescribeSecurityIPGroup` | Query the security IP group list |
> **Command usage**: This document only lists API names and process guidelines.
> Before execution, consult the API documentation via `../api/api-discovery.md` to confirm the complete parameters and response descriptions.
## Prerequisites
1. All Tencent Cloud API calls are executed via `tccli`. If no valid credentials are configured in the environment, you must first guide the user to complete login:
```sh
tccli auth login
```
> After execution, the terminal will print an authorization link and block until the user completes browser authorization — the command ends automatically upon success.
> Never ask the user for `SecretId` / `SecretKey`, and do not execute commands that might expose credential contents.
2. You need to obtain the ZoneId first — see `../api/zone-discovery.md`.
## Scenario A: Generate Current Security Configuration Snapshot
**Trigger**: User says "check the current security configuration", "help me compile a security policy snapshot for this zone".
For the same `ZoneId`, call the following 3 APIs in sequence: `DescribeSecurityPolicy`, `DescribeWebSecurityTemplates`, `DescribeSecurityIPGroup`.
**Output suggestion**: Respond with "current snapshot + risk alerts", appending a concise JSON snapshot at the end (see appendix format) for future report comparisons.
## Scenario B: Generate Security Protection Report
**Trigger**: User says "generate a security status report for this week", "check if there have been any security policy changes recently".
### Process
1. Confirm the time range (default "this week"), and explicitly state the start and end time in the report
2. Call the 3 APIs from Scenario A in sequence to collect the current configuration
3. If the user provides a historical baseline (last week's report, old snapshot, or historical results from the conversation), perform a diff comparison:
| Comparison Dimension | Description |
|---|---|
| Additions | Policies, templates, IP groups added this week |
| Deletions | Configurations deleted or unbound this week |
| Modifications | Configuration items changed this week |
| Risk escalation | Protection capabilities disabled, policies downgraded from block to observe, etc. |
| Risk de-escalation | Protection capabilities restored, policies tightened, etc. |
4. If **no historical snapshot is available**, explicitly state "only a configuration snapshot can be generated at this time — cannot determine whether changes are abnormal", and output the current result as the new baseline snapshot
**Output suggestion**: Structure the output as "Report scope → Current configuration summary → This week's changes & risks → Recommended actions → Appendix snapshot" (see output format).
## Scenario C: Risk Inspection
**Trigger**: User says "help me check if there are security configuration issues", "are there any high-risk configurations".
After completing the data collection from Scenario A, highlight the following anomalies:
- Critical protection capabilities disabled
- Policies downgraded from block to observe or off
- Overly broad allow configurations
- Security templates unbound or in abnormal status
- IP groups empty, containing overly broad CIDR ranges, or test groups active long-term
> ⚠️ **Note**: Do not declare "abnormal changes" without evidence — you must provide evidence from the current configuration or historical snapshot.
## Output Format
> **Language note**: Adapt the report language to match the user's language. The template below is an example — output should be in the same language the user is using.
```markdown
## Security Protection Report
**Zone**: example.com (ZoneId: zone-xxx)
**Time Range**: 2026-03-13 – 2026-03-19
**Data Sources**: `DescribeSecurityPolicy` / `DescribeWebSecurityTemplates` / `DescribeSecurityIPGroup`
### Current Configuration Summary
- Core protection capability status: ...
- Template binding summary: ...
- IP group summary: ...
### This Week's Changes & Risks
- Configuration changes found this week: ...
- Suspected abnormal changes: ...
- Items requiring manual confirmation: ...
### Recommended Actions
- Immediate action: ...
- Action within this week: ...
- Continue monitoring: ...
```
### Appendix: Concise JSON Snapshot
Append the following format snapshot at the end of the report, for future report comparisons:
```json
{
"zoneId": "",
"generatedAt": "",
"timeRange": { "start": "", "end": "" },
"securityPolicy": {},
"webSecurityTemplates": [],
"securityIPGroups": []
}
```
WorkRally CLI (workrally) — 面向 AI Agent 的 AIGC 漫剧视频创作全流程工具集。 支持 AI 生图、AI 生视频、项目管理、资产库、媒资管理、无限画布、文件上传下载等。 Use when user asks to generate images, generate vide...
---
name: workrally
description: >-
WorkRally CLI (workrally) — 面向 AI Agent 的 AIGC 漫剧视频创作全流程工具集。
支持 AI 生图、AI 生视频、项目管理、资产库、媒资管理、无限画布、文件上传下载等。
Use when user asks to generate images, generate videos, manage projects,
upload files, download assets, manage materials, or interact with
WorkRally platform via command line.
version: 2.3.1
license: MIT-0
author: WorkRally Team
homepage: https://workrally.qq.com
user-invocable: true
metadata: {"openclaw":{"emoji":"🎬","requires":{"bins":["workrally"],"env":["WORKRALLY_API_KEY","WORKRALLY_ENDPOINT","WORKRALLY_CONFIG_DIR","WORKRALLY_NO_UPDATE_CHECK"]},"primaryEnv":"WORKRALLY_API_KEY","credentials":{"storage":"~/.workrally/config.json","configDirEnv":"WORKRALLY_CONFIG_DIR","description":"workrally auth login 写入的 API Key 持久化文件,JSON 格式,仅存储 api_key 和 endpoint。非持久化容器中可通过 WORKRALLY_CONFIG_DIR 环境变量指定配置目录"},"install":[{"id":"npm","kind":"node","package":"workrally","bins":["workrally"],"label":"Install WorkRally CLI (npm)"}],"category":"AIGC","tags":["workrally","aigc","cli","video-generation","image-generation","ai-tools"]}}
---
# WorkRally CLI (workrally)
面向 AI Agent 的 AIGC 漫剧视频创作全流程命令行工具,封装 WorkRally 平台 20+ 核心能力。
## 安装 & 配置
```bash
npm install -g workrally
# 配置 API Key(三选一)
workrally auth login # 交互式登录(推荐)
workrally auth login --token <YOUR_API_KEY> # 命令行传入
export WORKRALLY_API_KEY=<YOUR_API_KEY> # 环境变量(仅推荐 CI/CD,Agent/子进程可能读不到 shell 配置)
# ↑ auth login 自动将 Token 写入配置文件:
# 若 WORKRALLY_CONFIG_DIR 已设置 → $WORKRALLY_CONFIG_DIR/config.json
# 否则 → ~/.workrally/config.json
workrally auth status # 验证登录状态
```
API Key 申请:[龙虾配置](https://workrally.qq.com/open-api)
## 命令速查
```bash
# === 项目管理 ===
workrally project list [--search "关键词"] # 列出/搜索项目
workrally project create "项目名" # 创建项目
workrally project get <id> # 项目详情
workrally project update <id> --name "新名称" # 更新项目
# === 上传 / 下载 ===
workrally upload ./file.png -o json # 上传文件 (COS SDK 直传)
workrally download <asset_id> [-d ./output/] # 下载素材 (自动处理访问凭证)
# === AI 生图 ===
workrally generate image-models # 查看可用模型(必须先调用!)
workrally generate image --prompt "描述" --model <model_id> [--aspect-ratio 16:9] [--input-images "url"] --poll
# === AI 生视频 (4 种驱动模式) ===
workrally generate video-models # 查看可用模型(必须先调用!)
workrally generate video --prompt "描述" --model <provider_id> --poll # 纯文生视频(默认 Text 模式)
workrally generate video --prompt "描述" --model <provider_id> --single-image-url "url" --poll # 图生视频(Text 模式 + 参考图)
workrally generate video --mode FirstLastFrame --prompt "描述" --model <provider_id> --first-frame-url "url" --poll # 首尾帧
# 其他模式: FrameSequence(--sequence-frames) SubjectToVideo(--reference-assets)
# --mode 默认 Text;通用选项: --duration <秒> --count 1-4 --enable-sound --poll
# === 媒资库 (asset) — 项目级媒体文件池 ===
workrally asset create --url <cdn_url> --project-id <id> -o json # 入库(返回可访问 URL)
workrally asset search --project-id <id> # 搜索
workrally asset get <asset_id> # 详情
workrally asset update <asset_id> --name "新名称" # 更新素材 (目前仅支持改名)
# === 资产库 (material) — 树形管理:人物/道具/场景/网盘 ===
workrally material list role_person # 人物 | role_prop 道具 | role_scene 场景 | root 网盘文件夹
workrally material add ... # 创建素材/文件夹(从媒资库挂载)
workrally material get <material_id> # 素材详情
workrally role get <role_id> # 角色详情(LoRA/提示词/版本)
# === 画布 ===
workrally canvas list # 列出画布
workrally canvas create "名称" # 创建画布
workrally canvas build-draft <canvas_id> --file nodes.json # 增量合并(默认保留已有节点)
workrally canvas build-draft <canvas_id> --nodes '[...]' # 同上,直接传 JSON
workrally canvas build-draft <canvas_id> -d "id1,id2" # 删除指定节点
workrally canvas build-draft <canvas_id> -n '[...]' -d "old1" # 同时增删改
workrally canvas build-draft <canvas_id> -n '[...]' --mode overwrite # 全量覆盖(清空后重建)
# === 任务查询 ===
workrally generate task <task_id> [--poll] # 查询/轮询生成任务状态
# === 通用透传(调用任意 MCP 工具)===
workrally tools list # 列出所有工具
workrally tools describe <tool_name> # 查看参数 schema
workrally tools call <tool_name> --arg key=value [--json-args '{}']
# === URL / 升级 ===
workrally url build "页面名" [--params '{}'] # 构建 WorkRally 前端链接
workrally url parse <url> # 解析 URL
workrally upgrade [--check] # 升级 / 仅检查
```
输出格式: `-o json`(默认, Agent 推荐) | `-o table`(人类阅读) | `-o text`(管道/脚本) | `workrally config set output_format <fmt>`
## 关键工作流:上传文件
**概念**:媒资库(asset) = 项目级文件池;资产库(material) = 树形目录(人物/道具/场景/网盘文件夹)。资产库的素材只能从媒资库挂载。
```bash
# 步骤 1: 上传 → CDN URL
workrally upload ./character.png -o json
# 步骤 2: 入媒资库(必须!返回 asset_id + asset_details)
workrally asset create --url <cdn_url> --project-id <project_id> -o json
# 步骤 3(按需): 挂载到资产库(必传 asset_id + 完整 asset_details)
workrally material add --json-list '[{"material_id":"<asset_id>","material_name":"名称","material_type":2,"parent_id":"<target_id>","material_detail":<asset_details_json>}]' \
--project-ids <project_id>
```
> **步骤 1→2 强制绑定**,上传后必须入媒资库。视频/音频为私有读,需经媒资库才能正常访问。
>
> **步骤 3 由 Agent 判断**:"上传文件" → 两步 | "上传到角色/道具/场景/文件夹" → 三步 | "媒资素材添加到资产库" → 仅步骤 3
## ⚠️ 重要规则
1. **前端链接必须用 `workrally url build` 生成**,严禁自行拼接 URL
2. **模型 ID 必须动态获取**:`image-models` / `video-models`,严禁猜测或硬编码
3. **`canvas` ≠ `project`**:画布用 `canvas`,项目用 `project`,两者 ID 不能互换
4. **`build-draft` 实时协同**:写入后所有在线用户立即看到变更,默认增量合并(只传变更节点),支持多人并发安全操作
5. **`build-draft` 节点校验**:8种节点类型各有必填字段,详见 [`canvas-guide.md`](references/canvas-guide.md)
6. **AI 生成自动占位**:`generate image/video` 传入 `--project-id`(画布ID)后自动在画布创建占位节点,**无需**再手动 `build-draft`
7. **素材命名**:`--name` 传入"画布名_素材特征"(画布场景)或 prompt 关键词(非画布场景)
8. **不确定参数时**用 `--help` 或 `tools describe` 自行探索
9. **URL 白名单**:所有 URL 类参数(生图/生视频的 `--*-url` / `--*-assets` / `--*-images`、`asset create --url` 等)仅接受 WorkRally 官方媒资 URL。合法来源:① `workrally upload` 返回值 ② `asset get/search` 返回值(可直接传入) ③ 用户已提供的官方 URL。本地文件或第三方 URL 必须先 `workrally upload`。如遇"非法或已过期"提示,通过 `asset get/search` 重新获取即可。
## 📚 深度指南 (references/)
本 Skill 附带详细参考文档,覆盖复杂工作流:
| 文档 | 内容 |
|------|------|
| [`references/canvas-guide.md`](references/canvas-guide.md) | 无限画布操作 — 8种节点类型、画板嵌套、build-draft 增量/覆盖模式、协同编辑 |
| [`references/upload-and-assets-guide.md`](references/upload-and-assets-guide.md) | 上传与素材管理 — 三步上传流程、媒资库 vs 资产库、树形目录操作 |
| [`references/ai-generation-guide.md`](references/ai-generation-guide.md) | AI 生成 — Kontext 生图、4种视频驱动模式、模型动态获取、任务轮询 |
| [`references/common-pitfalls.md`](references/common-pitfalls.md) | 常见易错点 — 项目/画布混淆、模型硬编码、上传缺步骤等10类典型错误 |
> 遇到画布、上传、AI生成相关的复杂操作时,请优先查阅对应的参考文档。
## 环境变量
- `WORKRALLY_API_KEY` — API Key (Bearer Token)
- `WORKRALLY_ENDPOINT` — API 端点 (默认 `https://workrally.qq.com/zenstudio/api/mcp`)
- `WORKRALLY_CONFIG_DIR` — 配置文件目录 (默认 `~/.workrally`,非持久化容器建议指向持久卷)
- `WORKRALLY_NO_UPDATE_CHECK=1` — 禁用自动版本检查 (CI/CD 推荐)
FILE:LICENSE.txt
Tencent is pleased to support the open source community by making workrally available.
Copyright (C) 2026 Tencent. All rights reserved.
workrally is licensed under the MIT-0.
Terms of the MIT-0:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
FILE:README.md
# WorkRally CLI — Agent Skill
🎬 面向 AI Agent 的 AIGC 漫剧视频创作全流程工具集。
本目录是 WorkRally CLI 的 Agent Skill 定义。
## 目录结构
```
skill/
├── SKILL.md ← Skill 入口(元数据 + 指令),ClawHub 解析此文件
└── references/ ← 深度参考文档,AI Agent 按需加载
├── canvas-guide.md 无限画布操作指南
├── upload-and-assets-guide.md 上传与素材管理指南
├── ai-generation-guide.md AI 生成指南
└── common-pitfalls.md 常见易错点
```
## 核心能力
- **AI 生图** — Kontext 模型,支持多参考图
- **AI 生视频** — 4 种驱动模式(文本/首尾帧/序列帧/参考主体)
- **无限画布** — Yjs 协同编辑,8 种节点类型,实时同步
- **项目 & 媒资管理** — 项目 CRUD、素材上传入库、资产库树形管理
- **通用透传** — 可调用 WorkRally MCP Server 全部工具
## 第三方商店
- [SkillHub](https://skillhub.cn/skills/workrally)
- [ClawHub](https://clawhub.ai/tencent-adm/workrally)
- [Skills](https://skills.sh/tencent/workrally/workrally)
## 快速开始
```bash
npm install -g workrally
workrally auth login
workrally auth status
```
详细用法、命令速查、工作流指南请参阅 **[SKILL.md](./SKILL.md)**。
FILE:references/ai-generation-guide.md
# AI 生成指南(图片 & 视频)
本文档帮助 AI Agent 正确使用 WorkRally 的 AI 图片/视频生成能力。
---
## 1. 核心规则
> ⚠️ **模型 ID 必须动态获取,严禁猜测或硬编码!**
> 模型列表是动态下发的,不同环境(开发/预发/正式)的可用模型可能完全不同。
> 🔒 所有 URL 类参数仅接受 WorkRally 官方媒资 URL,详见 SKILL.md 规则 9。
```bash
# 生图前必须先获取模型列表
workrally generate image-models -o json
# 生视频前必须先获取模型配置
workrally generate video-models -o json
```
---
## 2. 图片生成 (Kontext)
### 2.1 获取可用模型
```bash
workrally generate image-models -o json
```
返回包含:
- `models[]` — 每个模型的 `model_id`、`name`、`support_resolutions`、`kontext_config`
- `aspect_ratios[]` — 全局可用宽高比列表(如 "1:1", "16:9", "9:16" 等)
- `resolutions[]` — 所有模型支持的分辨率并集
- `count_options[]` — 可选的生成数量
**关键字段**:
- `model_id` → 传给 `--model` 参数
- `kontext_config.max_input_images` → 该模型允许的最大参考图数量(不同模型不同,不要写死)
- `support_resolutions` → 该模型支持的分辨率列表
### 2.2 纯文生图
```bash
workrally generate image \
--prompt "一只橘猫坐在樱花树下" \
--model <model_id> \
--aspect-ratio 16:9 \
--poll
```
### 2.3 参考图生图
通过 `--input-images` 传入参考主体图片 URL,在 prompt 中用 "第一张图片"、"第二张图片" 引用:
```bash
workrally generate image \
--prompt "第一张图片趴在第二张图片路中间" \
--model <model_id> \
--input-images "https://cat.png,https://shrine.png" \
--poll
```
> 📌 `--input-images` 的最大数量取决于模型配置中的 `kontext_config.max_input_images`,不要写死。
> 📌 **只允许图片类型的素材**作为参考图。
### 2.4 在画布中生图
```bash
workrally generate image \
--prompt "描述" \
--model <model_id> \
--project-id <画布ID> \
--poll
```
传入 `--project-id`(画布 ID)后:
- 系统会**自动**在画布中创建 running 状态的占位节点(橙色边框 + 进度条)
- **无需**再手动调用 `build-draft` 放置生成器节点
- 生成完成后,前端自动更新节点状态
> ⚠️ `--project-id` 此处是**画布 ID**(通过 `canvas list` 获取),**不是项目 ID**!
### 2.5 参数说明
| 参数 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| `--prompt` | ✅ | — | 图片描述 |
| `--model` | ✅ | — | 模型 ID(从 `image-models` 获取) |
| `--aspect-ratio` | — | `16:9` | 宽高比 |
| `--resolution` | — | `0` | 分辨率等级: 0=1K, 1=2K, 2=4K(各模型支持范围不同,以 `image-models` 返回的 `kontext_config.support_resolutions` 为准) |
| `--count` | — | `1` | 生成数量 1-4(后端一个任务生成 1 张,count>1 会并发发起 N 个独立任务并返回 task_ids 数组) |
| `--input-images` | — | — | 参考图 URL(逗号分隔) |
| `--project-id` | — | — | 画布 ID(传入后自动创建占位节点) |
| `--short-series-project-id` | — | — | 项目 ID |
| `--name` | — | — | 素材名称 |
| `--poll` | — | false | 自动轮询直到完成 |
| `--poll-interval` | — | `3` | 轮询间隔(秒) |
---
## 3. 视频生成
### 3.1 获取可用模型配置
```bash
workrally generate video-models -o json
```
返回按**驱动模式**分组:
- `text_providers[]` — Text(单图/纯文)模式
- `first_last_frame_providers[]` — 首尾帧模式
- `frame_sequence_providers[]` — 序列帧模式
- `subject_to_video_providers[]` — 参考主体模式
每个模型包含:
- `provider` → 传给 `--model` 参数
- `label` — 模型显示名称
- `duration_options[]` — 可用时长列表(秒)
- `can_upload_image/video/audio` — 支持的输入类型
- `max_image_count/video_count/audio_count` — 各类型最大数量
- `support_audio` — 是否支持音效
### 3.2 四种驱动模式
#### Text 模式(默认)— 纯文生视频 / 单图驱动
```bash
# 纯文生视频(不传图片)
workrally generate video \
--prompt "夕阳下海浪拍打沙滩" \
--model <provider_id> \
--poll
# 图生视频(传入参考图)
workrally generate video \
--prompt "图片中的角色缓缓转身" \
--model <provider_id> \
--single-image-url "https://example.com/character.png" \
--poll
```
#### FirstLastFrame 模式 — 首尾帧驱动
```bash
workrally generate video \
--mode FirstLastFrame \
--prompt "角色从左走到右" \
--model <provider_id> \
--first-frame-url "https://example.com/start.png" \
--last-frame-url "https://example.com/end.png" \
--poll
```
> 可以只传首帧或只传尾帧(至少一个)。
#### FrameSequence 模式 — 序列帧驱动
```bash
workrally generate video \
--mode FrameSequence \
--prompt "连贯的动画过渡" \
--model <provider_id> \
--sequence-frames '[{"url":"https://frame1.png","timestamp":0},{"url":"https://frame2.png","timestamp":2}]' \
--poll
```
#### SubjectToVideo 模式 — 参考主体驱动
```bash
workrally generate video \
--mode SubjectToVideo \
--prompt "角色在场景中行走" \
--model <provider_id> \
--reference-assets '[{"type":"image","url":"https://character.png"},{"type":"video","url":"https://bg.mp4"}]' \
--poll
```
### 3.3 通用选项
| 参数 | 必填 | 默认值 | 说明 |
|------|------|--------|------|
| `--prompt` | ✅ | — | 动画描述 |
| `--model` | ✅ | — | Provider ID(从 `video-models` 获取) |
| `--mode` | — | `Text` | 驱动模式: Text/FirstLastFrame/FrameSequence/SubjectToVideo |
| `--duration` | — | — | 视频时长(秒),可选值取决于模型 |
| `--count` | — | `1` | 生成数量 1-4(后端一个任务生成 1 个视频,count>1 会并发发起 N 个独立任务并返回 task_ids 数组) |
| `--enable-sound` | — | false | 生成音效(仅部分模型支持) |
| `--project-id` | — | — | 画布 ID(传入后自动创建占位节点) |
| `--short-series-project-id` | — | — | 项目 ID |
| `--name` | — | — | 素材名称 |
| `--poll` | — | false | 自动轮询直到完成 |
| `--poll-interval` | — | `5` | 轮询间隔(秒) |
### 3.4 在画布中生视频
与生图类似,传入 `--project-id`(画布 ID)即可自动创建占位:
```bash
workrally generate video \
--prompt "海浪翻涌" \
--model <provider_id> \
--project-id <画布ID> \
--poll
```
---
## 4. 任务轮询
### 4.1 使用 --poll 自动轮询(推荐)
```bash
workrally generate image --prompt "..." --model <id> --poll
```
使用 `--poll` 后,CLI 自动:
1. 提交生成任务
2. 每隔 N 秒查询状态(默认3秒/图片,5秒/视频)
3. 显示进度条和状态(排队中/运行中/成功/失败)
4. 完成后输出最终结果
### 4.2 手动查询任务
```bash
# 单次查询
workrally generate task <task_id> -o json
# 手动轮询
workrally generate task <task_id> --poll
```
### 4.3 任务状态
| state | 含义 | 说明 |
|-------|------|------|
| 1 | 排队中 (QUEUED) | 等待资源 |
| 2 | 运行中 (RUNNING) | 正在生成 |
| 3 | 暂停 (PAUSED) | 暂停中 |
| 4 | 成功 (SUCCESS) | `output_products` 包含结果 |
| 5 | 失败 (FAILED) | `error_message` 包含错误信息 |
| 6 | 已取消 (CANCELLED) | 用户取消 |
### 4.4 多任务并发
后端「一个任务只生成 1 个素材」,当 `--count` > 1 时,CLI 会并发发起 N 个独立任务,返回的 `task_ids` 是长度为 N 的数组,每个 task 产出 1 个素材。使用 `--poll` 时 CLI 会自动并发轮询所有任务,总耗时约等于单任务耗时。
---
## 5. 素材命名最佳实践
### 画布内生成
使用 `--name` 传入"画布名称_素材特征":
```bash
# 先获取画布名称
workrally canvas get <canvas_id> -o json
# 生成时传入有意义的名称
workrally generate image --prompt "蓝色运动鞋" --model <id> --project-id <canvas_id> \
--name "产品设计画布_蓝色运动鞋" --poll
```
### 非画布生成
从 prompt 中提取核心关键词作为名称:
```bash
workrally generate image --prompt "一只可爱的橘猫在夕阳下奔跑" --model <id> \
--name "橘猫_夕阳奔跑" --poll
```
---
## 6. 生成后素材处理
AI 生成的图片/视频会**自动入库到媒资系统**(后台自动完成,无需额外调用 `asset create`)。
### 如果需要上传到资产库
```bash
# 1. 生成完成后,从结果中获取 asset_id
# 2. 获取 asset_details
workrally asset get <asset_id> -o json
# 3. 挂载到资产库
workrally material add --json-list '[{"material_id":"<asset_id>","material_name":"角色名","material_type":2,"parent_id":"<role_condition_id>","material_detail":<asset_details>}]' \
--project-ids <project_id>
```
### 如果需要在画布上展示
传入 `--project-id` 即可,系统自动处理。无需手动调用 `build-draft`。
FILE:references/canvas-guide.md
# 无限画布操作指南
本文档帮助 AI Agent 正确操作 WorkRally 无限画布(Infinite Canvas)。画布基于 Yjs 协同编辑引擎,CLI 写入的内容会**实时同步**给所有在线用户,无需刷新页面。
---
## 1. 核心概念
### 两种"项目"(容易混淆,务必区分)
| 概念 | 管理命令 | 用途 | 必要性 |
|------|---------|------|--------|
| **项目** (project) | `workrally project list/create/get` | 所有素材都必须归属一个项目,范围更大 | **必须** — 素材不关联项目则在 web 端不可见 |
| **画布** (canvas) | `workrally canvas list/create/get` | 无限画布空间,可在其中排布节点 | **可选** — 仅当用户要在画布中操作时才需要 |
> ⚠️ **两者的 ID 不能互相替代!**
> - `workrally project list` 返回的是项目 ID
> - `workrally canvas list` 返回的是画布 ID
> - 在画布场景下,素材需要**同时关联两者**
### 判断用户意图
| 用户说 | 含义 | 使用命令 |
|--------|------|---------|
| "我的项目"、"项目列表" | 项目 | `workrally project list` |
| "我的画布"、"画布列表" | 无限画布 | `workrally canvas list` |
| "在画布上生成图片" | 画布 + AI 生成 | `workrally generate image --project-id <画布ID>` |
| "上传到项目" | 仅入媒资库 | `upload` → `asset create` |
| "在画布上展示素材" | 需要 build-draft | `upload` → `asset create` → `canvas build-draft` |
---
## 2. 画布节点类型 (8 种)
### 类型一览
| type | 说明 | 必填 data 字段 | 可放入画板 |
|------|------|---------------|-----------|
| `image` | 图片素材 | `data.asset.id` (已有素材) 或 `data.task` (生成中占位) | ✅ |
| `video` | 视频素材 | `data.asset.id` 或 `data.task` | ✅ |
| `audio` | 音频素材 | `data.asset.id` (**必须**,音频无生成器) | ✅ |
| `imageGenerator` | 图片生成器 | 无必填(params 可选) | ❌ |
| `videoGenerator` | 视频生成器 | 无必填(params 可选) | ❌ |
| `artboard` | 画板容器 | 无(建议设置 `style.width/height`) | ❌ (画板不可嵌套) |
| `text` | 文本 | `data.text.content` (字符串,最大2000字符) | ❌ |
| `freehand` | 画笔涂鸦 | `data.freehand.points` + `data.freehand.initialSize` | ❌ |
### 节点通用结构
```json
{
"id": "node_unique_id",
"type": "image",
"position": { "x": 100, "y": 200 },
"data": { },
"style": { "width": 512, "height": 512 },
"parentId": "artboard_id",
"measured": { "width": 512, "height": 512 }
}
```
**字段说明**:
- `id` — 节点唯一标识,可使用任意唯一字符串
- `position` — 节点左上角坐标(缺失时堆叠在原点 0,0)
- `style` — 节点显示尺寸
- `parentId` — 仅画板内子节点需要,指向父画板的 id
- `measured` — 渲染尺寸,可选,缺失时服务端自动补全
---
## 3. 各节点类型详细说明
### 3.1 图片/视频节点 (image / video)
两种来源:
1. **已有素材** — 必须有 `data.asset.id`
2. **生成中占位** — 必须有 `data.task`(由 AI 生成命令自动创建,通常不需要手动构造)
```json
{
"id": "img_001",
"type": "image",
"position": { "x": 0, "y": 0 },
"data": {
"asset": { "id": "asset_abc123" }
},
"style": { "width": 512, "height": 512 }
}
```
**带生成任务标记的节点**(用于"再次编辑"功能):
```json
{
"id": "gen_img_001",
"type": "image",
"position": { "x": 0, "y": 0 },
"data": {
"asset": { "id": "asset_abc123" },
"task": { "taskId": "task_xyz789", "status": "success" }
},
"style": { "width": 512, "height": 512 }
}
```
> 💡 `data.task` 字段决定前端是否显示"再次编辑"按钮。AI 生成的图片/视频应包含此字段。
### 3.2 音频节点 (audio)
音频**没有生成器**,不支持 task 占位,必须有 `data.asset.id`。
```json
{
"id": "audio_001",
"type": "audio",
"position": { "x": 0, "y": 0 },
"data": {
"asset": { "id": "asset_audio_456" }
},
"style": { "width": 260, "height": 80 }
}
```
> 建议尺寸 **260×80**(与前端默认一致)。
### 3.3 画板节点 (artboard)
画板是**容器**,子节点通过 `parentId` 关联到画板。
```json
{
"id": "board_001",
"type": "artboard",
"position": { "x": 0, "y": 0 },
"data": {},
"style": { "width": 600, "height": 800 }
}
```
**画板子节点示例** — 在画板内放置一张图片:
```json
{
"id": "img_in_board",
"type": "image",
"position": { "x": 20, "y": 20 },
"data": { "asset": { "id": "asset_abc123" } },
"style": { "width": 256, "height": 256 },
"parentId": "board_001"
}
```
**画板规则**:
- ✅ `image`、`video`、`audio` 可以放入画板
- ❌ `imageGenerator`、`videoGenerator`、`text`、`freehand` 不可放入画板
- ❌ 画板不可嵌套(画板内不能放画板)
- ❌ **不要设置 `extent: "parent"`**,否则子节点会被锁定在画板内无法拖出
- 画板缺少尺寸时自动补全为 **600×800**
### 3.4 文本节点 (text)
```json
{
"id": "text_001",
"type": "text",
"position": { "x": 0, "y": 0 },
"data": {
"text": {
"content": "这是一段文本",
"fontSize": 24,
"fontWeight": 400,
"textAlign": "left",
"color": "#ffffff"
}
},
"style": { "width": 200 }
}
```
**必填**: `data.text.content`(字符串,最大2000字符)
**可选**(有默认值): `fontSize`(24), `fontWeight`(400, 加粗用700), `textAlign`("left"), `color`("#ffffff")
建议设置 `style.width`(默认200)。
### 3.5 画笔涂鸦节点 (freehand)
```json
{
"id": "freehand_001",
"type": "freehand",
"position": { "x": 0, "y": 0 },
"data": {
"freehand": {
"points": [[10, 20, 0.5], [30, 40, 0.7], [50, 60, 0.5]],
"initialSize": { "width": 200, "height": 200 },
"color": "rgba(242,72,34,1)",
"size": 7
}
}
}
```
**必填**: `data.freehand.points`(二维数组,每个点为 `[x, y, pressure]`)、`data.freehand.initialSize`(`{width, height}`)
**可选**: `color`(默认红色 `rgba(242,72,34,1)`)、`size`(画笔粗细 1-100,默认7)
`pressure` 值范围 0-1,超出自动截断。
### 3.6 生成器节点 (imageGenerator / videoGenerator)
> ⚠️ **通常不需要手动创建!** `workrally generate image/video --project-id <画布ID>` 会**自动**在画布中创建 running 状态的占位节点。
仅在极特殊场景(如手动构建已完成的生成器节点)才需要:
```json
{
"id": "gen_001",
"type": "imageGenerator",
"position": { "x": 0, "y": 0 },
"data": {
"task": { "taskId": "task_abc", "status": "success" },
"params": {}
},
"style": { "width": 512, "height": 512 }
}
```
---
## 4. build-draft 操作模式
### 4.1 增量合并(默认模式)
```bash
workrally canvas build-draft <canvas_id> --nodes '[...]'
```
规则:
- **同 id → 覆盖更新**:传入的节点 id 与已有节点相同时,用新数据替换旧数据
- **新 id → 追加**:已有画布中不存在的 id 会被添加
- **未提及 → 保留**:已有节点不在传入列表中的,原样保留
### 4.2 删除节点
```bash
workrally canvas build-draft <canvas_id> --delete-node-ids "id1,id2"
```
可与 `--nodes` 同时使用(先删除,再合并新节点):
```bash
workrally canvas build-draft <canvas_id> --nodes '[...]' --delete-node-ids "old1,old2"
```
### 4.3 全量覆盖
```bash
workrally canvas build-draft <canvas_id> --nodes '[...]' --mode overwrite
```
**清空画布**后仅保留传入的节点。传 `--nodes '[]' --mode overwrite` 可清空整个画布。
> ⚠️ 全量覆盖会删除所有已有节点,包括其他用户的内容。在多人协作场景下应优先使用增量合并。
### 4.4 从文件加载节点
```bash
workrally canvas build-draft <canvas_id> --file nodes.json
```
适合节点数据量大或结构复杂的场景。
---
## 5. 常见工作流示例
### 场景 A:在画布上排列已有素材
```bash
# 1. 搜索项目中的素材
workrally asset search --project-id <project_id> -o json
# 2. 从搜索结果中获取 asset_id,构建节点写入画布
workrally canvas build-draft <canvas_id> --nodes '[
{"id":"n1","type":"image","position":{"x":0,"y":0},"data":{"asset":{"id":"<asset_id_1>"}},"style":{"width":512,"height":512}},
{"id":"n2","type":"image","position":{"x":600,"y":0},"data":{"asset":{"id":"<asset_id_2>"}},"style":{"width":512,"height":512}}
]'
```
### 场景 B:创建画板并放入多张图片
```bash
workrally canvas build-draft <canvas_id> --nodes '[
{"id":"board","type":"artboard","position":{"x":0,"y":0},"data":{},"style":{"width":800,"height":600}},
{"id":"img1","type":"image","position":{"x":20,"y":20},"data":{"asset":{"id":"<id1>"}},"style":{"width":350,"height":250},"parentId":"board"},
{"id":"img2","type":"image","position":{"x":420,"y":20},"data":{"asset":{"id":"<id2>"}},"style":{"width":350,"height":250},"parentId":"board"}
]'
```
### 场景 C:更新画布中某个节点的位置
```bash
# 只传需要修改的节点,其他节点自动保留
workrally canvas build-draft <canvas_id> --nodes '[
{"id":"existing_node_id","type":"image","position":{"x":300,"y":400},"data":{"asset":{"id":"<asset_id>"}},"style":{"width":512,"height":512}}
]'
```
### 场景 D:删除部分节点并添加新节点
```bash
workrally canvas build-draft <canvas_id> \
--nodes '[{"id":"new1","type":"text","position":{"x":0,"y":0},"data":{"text":{"content":"新标题","fontSize":48,"fontWeight":700,"color":"#00ff00"}},"style":{"width":400}}]' \
--delete-node-ids "old_node_1,old_node_2"
```
---
## 6. 服务端自动修正
服务端会对传入的节点进行以下自动修正(不需要 Agent 操心):
| 场景 | 自动行为 |
|------|---------|
| 画板缺少 style | 补全 600×800 |
| 文本节点缺少样式属性 | 补全 fontSize=24, fontWeight=400, textAlign=left, color=#ffffff |
| 文本节点缺少 style.width | 补全 200 |
| 文本内容为空 | 填充"在此输入文本" |
| 画笔节点缺少颜色/大小 | 补全 color=rgba(242,72,34,1), size=7 |
| 画笔 size 超出范围 | 截断到 1-100 |
| 画笔 pressure 超出范围 | 截断到 0-1 |
| 子节点设置了 extent | 自动清除(防止锁定) |
**但以下关键字段缺失会被拒绝**:
| 场景 | 错误 |
|------|------|
| image/video 既没有 asset.id 也没有 task | ❌ 空节点 |
| audio 没有 asset.id | ❌ 音频无生成器 |
| text 没有 data.text.content 或类型非字符串 | ❌ |
| text 内容超过 2000 字符 | ❌ |
| freehand 缺少 points 或 initialSize | ❌ |
| 不允许的节点类型(如 group) | ❌ |
| 画板子节点类型不是 image/video/audio | ❌ |
FILE:references/common-pitfalls.md
# 常见问题与易错点
本文档汇总 AI Agent 使用 WorkRally CLI 时最容易犯的错误和混淆点,帮助避免常见陷阱。
---
## ❌ 错误 1:混淆"项目"和"画布"
### 问题
```bash
# ❌ 错误:把项目 ID 当画布 ID 用
workrally generate image --prompt "..." --model <id> --project-id <project_list返回的ID>
```
### 正确做法
```bash
# ✅ 先获取画布 ID
workrally canvas list -o json
# 再传画布 ID
workrally generate image --prompt "..." --model <id> --project-id <canvas_list返回的canvas_id>
```
### 区分规则
| 获取方式 | 返回的是 | 传给谁 |
|----------|---------|--------|
| `workrally project list` | 项目 ID | `asset create --project-id`、`asset search --project-id` |
| `workrally canvas list` | 画布 ID | `generate image --project-id`、`generate video --project-id`、`canvas build-draft` |
---
## ❌ 错误 2:硬编码模型 ID
### 问题
```bash
# ❌ 错误:猜测或硬编码模型 ID
workrally generate image --prompt "..." --model "kontext_v2"
```
### 正确做法
```bash
# ✅ 动态获取
workrally generate image-models -o json
# 从返回结果中读取 model_id
workrally generate image --prompt "..." --model <从返回结果中获取的model_id>
```
> 模型列表是**动态下发**的,不同环境的可用模型可能完全不同。
---
## ❌ 错误 3:自行拼接前端 URL
### 问题
```bash
# ❌ 错误:自己拼接 URL(域名和路由因环境而异)
echo "https://workrally.qq.com/workrally/toolbox/canvas/abc123"
```
### 正确做法
```bash
# ✅ 使用 url build 命令
workrally url build "无限画布" --params '{"id":"abc123"}'
```
---
## ❌ 错误 4:生成后手动调用 build-draft
### 问题
```bash
# ❌ 不必要:在画布中生成图片后,又手动创建节点
workrally generate image --prompt "..." --model <id> --project-id <canvas_id> --poll
# 然后又调用 build-draft 创建节点 ← 多余操作
workrally canvas build-draft <canvas_id> --nodes '[...]'
```
### 正确理解
传入 `--project-id` 后,系统**自动**在画布创建 running 状态的占位节点。**无需手动 build-draft。**
### build-draft 的正确使用场景
- 在画布上放置**已有素材**(非 AI 生成的图片/视频/音频)
- 管理**画板布局**(创建画板、调整子节点位置)
- 添加**文本**或**涂鸦**节点
- **删除**画布上的节点
- **重新排列**已有节点
---
## ❌ 错误 5:上传素材缺少入库步骤
### 问题
```bash
# ❌ 错误:上传后直接使用 CDN URL
workrally upload ./file.png -o json
# 然后直接把 cdn_url 作为 asset_id 用 ← 这不是 asset_id!
```
### 正确做法
```bash
# ✅ 上传后必须入媒资库
workrally upload ./file.png -o json
workrally asset create --url <cdn_url> --project-id <project_id> -o json
# 现在才有 asset_id
```
> 上传只是把文件传到 CDN,**必须**调用 `asset create` 入库才能被系统使用。
---
## ❌ 错误 6:资产库挂载缺少关键字段
### 问题
```bash
# ❌ 错误:JSON 中缺少 material_id 或 material_detail
workrally material add --json-list '[{"material_name":"素材","material_type":2,"parent_id":"role_person"}]'
# 素材不会在资产库列表中显示!
```
### 正确做法
```bash
# ✅ 必须在 JSON 中传 material_id(=asset_id)和完整的 material_detail(=asset_details)
workrally material add --json-list '[{
"material_id": "<asset_id>",
"material_name": "素材名",
"material_type": 2,
"parent_id": "<parent_id>",
"material_detail": <完整的 asset_details 对象>
}]' --project-ids <project_id>
```
---
## ❌ 错误 7:画板内放入不允许的节点类型
### 问题
```bash
# ❌ 错误:把文本节点放入画板
workrally canvas build-draft <id> --nodes '[
{"id":"board","type":"artboard","position":{"x":0,"y":0},"data":{},"style":{"width":600,"height":800}},
{"id":"txt","type":"text","position":{"x":20,"y":20},"data":{"text":{"content":"标题"}},"parentId":"board"}
]'
# 服务端会拒绝!
```
### 正确理解
画板只接受 `image`、`video`、`audio` 类型的子节点。
---
## ❌ 错误 8:给画板子节点设置 extent
### 问题
```json
{
"id": "img1",
"type": "image",
"parentId": "board",
"extent": "parent"
}
```
### 后果
子节点被 ReactFlow 锁定在画板内,用户无法拖出。服务端会自动清除此属性,但不要主动设置。
---
## ❌ 错误 9:混淆 material_id 和 role_id
### 问题
```bash
# ❌ 错误:用 material_id 查角色详情
workrally role get "abc_0"
# material_id 格式: "abc_0" (带后缀)
# role_id 格式: "abc" (不带后缀)
```
### 正确做法
```bash
# ✅ 先获取 role_id
workrally material get "abc_0" -o json
# 从返回结果中找到 role_id 字段
workrally role get "abc" -o json
```
---
## ❌ 错误 10:音视频 URL 使用 original_url
### 问题
```bash
# ❌ 错误:音视频使用 original_url(不含访问凭证)
# original_url 是原始 CDN 路径,音视频无法直接访问
```
### 正确做法
始终使用 `url` 或 `download_url`,这些是可直接访问的临时 URL。过期后通过 `asset get` 重新获取即可,返回的新 URL 可直接作为其他工具的 URL 参数传入。
---
## 常见判断速查表
| 场景 | 需要什么 |
|------|---------|
| "上传一张图片" | `upload` → `asset create` (2步) |
| "上传到人物角色" | `upload` → `asset create` → `material add` (3步) |
| "在画布上生成图片" | `generate image --project-id <画布ID> --poll` (1步) |
| "把已有图片放到画布上" | `asset search` → `canvas build-draft` |
| "生成4张图片" | `generate image --count 4 --poll` |
| "查看生成进度" | `generate task <task_id> --poll` |
| "创建一个画板放三张图" | `canvas build-draft` (一次传画板+3个子节点) |
| "删除画布上的某个节点" | `canvas build-draft --delete-node-ids "node_id"` |
| "清空整个画布" | `canvas build-draft --nodes '[]' --mode overwrite` |
| "查看角色的 LoRA 版本" | `material get` → `role get` |
| "搜索项目中的视频素材" | `asset search --project-id <id>` |
---
## 通用工具透传
当高级封装命令无法满足需求时,使用通用透传直接调用任何 MCP 工具:
```bash
# 列出所有可用工具
workrally tools list -o json
# 查看某个工具的参数 schema
workrally tools describe <tool_name>
# 直接调用(适合复杂参数场景)
workrally tools call <tool_name> --json-args '{"key":"value"}'
```
---
## 输出格式建议
| 格式 | 用途 | 命令 |
|------|------|------|
| `json` | **Agent 推荐** — 结构化数据便于解析 | `-o json` |
| `table` | 人类阅读 — 表格格式 | `-o table` |
| `text` | 管道/脚本 — 纯文本 | `-o text` |
设置全局默认格式:
```bash
workrally config set output_format json
```
FILE:references/upload-and-assets-guide.md
# 上传与素材管理指南
本文档帮助 AI Agent 正确执行文件上传和素材管理流程。WorkRally 有两套素材体系,理解它们的关系是正确操作的前提。
---
## 1. 两套素材体系
### 媒资库 (Asset) — 项目级文件池
- **管理命令**: `workrally asset search/create/get/update`
- **本质**: 扁平的文件列表,每个素材必须归属一个项目
- **特点**: 视频/音频为**私有读存储**,必须入库后才能正常访问
- **何时使用**: 所有素材都**必须**经过媒资库(`asset create`)才能被系统使用
### 资产库 (Material) — 树形目录管理
- **管理命令**: `workrally material list/add/update/get/breadcrumb`
- **本质**: 树形文件夹结构,对媒资库素材的**组织视图**
- **三个预设根目录**: `role_person`(人物)、`role_prop`(道具)、`role_scene`(场景)
- **附加根目录**: `root`(用户自建网盘文件夹)
- **何时使用**: 仅当用户要将素材"归档到角色/道具/场景/文件夹"时才需要
### 数据层次关系
```
资产 (material_type=0)
└─ 角色状态 (material_type=5)
├─ 图片素材 (material_type=2)
├─ 视频素材 (material_type=3)
└─ 音频素材 (material_type=4)
```
---
## 2. 三步上传流程
这是 WorkRally 最核心的文件处理流程,**严格按顺序执行**:
### 步骤 1: 上传文件到 CDN
```bash
workrally upload ./character.png -o json
```
返回:
```json
{
"url": "https://cdn.example.com/path/to/file.png",
"original_url": "https://cdn.example.com/path/to/file.png",
"signed_url": "https://cdn.example.com/path/to/file.mp4?sign=..."
}
```
**URL 字段说明**:
- `url` — 可直接访问的地址。图片为公开 URL;音视频为临时访问 URL
- `original_url` — 原始 CDN 路径(不含访问凭证),图片可直接访问,音视频无法直接访问
- `signed_url` — 仅音视频返回,与 `url` 相同
> ⚠️ 音视频文件为私有读存储,**必须使用 `url` 或 `signed_url`**,不要使用 `original_url`。
### 步骤 2: 入媒资库(必须!)
> 🔒 `--url` 仅接受 WorkRally 官方媒资 URL(即 `upload` 返回值或媒资库 URL),详见 SKILL.md 规则 9。
```bash
workrally asset create --url <cdn_url> --project-id <project_id> -o json
```
返回:
```json
{
"id": "asset_abc123",
"asset_details": {
"url": "https://signed.url/...",
"download_url": "https://signed.download.url/...",
"width": 1024,
"height": 1024,
"format": "png"
}
}
```
**重要返回值**:
- `id` — 即 `asset_id`,后续所有操作都需要这个 ID
- `asset_details` — 完整素材元数据,**步骤 3 必须完整传入**
> ⚠️ `asset_details.url` 和 `asset_details.download_url` 为临时访问 URL,过期后需通过 `workrally asset get` 重新获取。获取到的 URL 可直接作为其他工具的 URL 参数传入。
### 步骤 3: 挂载到资产库(按需)
```bash
workrally material add --json-list '[{
"material_id": "<asset_id>",
"material_name": "角色名_状态",
"material_type": 2,
"parent_id": "<目标位置的 material_id>",
"material_detail": <完整的 asset_details 对象>
}]' --project-ids <project_id>
```
**关键字段**(JSON 数组中每个对象):
- `material_id` — **必须**传 `asset_id`(步骤 2 返回的 `id`)
- `material_detail` — **必须**传完整的 `asset_details`(步骤 2 返回的 `asset_details` 对象)
- `material_type` — 素材类型:`2`=图片,`3`=视频,`4`=音频,`1`=文件夹
- `parent_id` — 目标位置:`role_person`/`role_prop`/`role_scene` 或已有文件夹/状态的 `material_id`
> ⚠️ 步骤 3 如果 JSON 中缺少 `material_id` 或 `material_detail`,素材不会在资产库列表中显示!
---
## 3. 判断需要几步
| 用户意图 | 所需步骤 | 说明 |
|----------|---------|------|
| "上传文件" / "上传图片" | 步骤 1 → 2 | 入媒资库即可在 web 端查看 |
| "上传到角色/道具/场景" | 步骤 1 → 2 → 3 | 还需挂载到资产库树形目录 |
| "上传到文件夹" | 步骤 1 → 2 → 3 | 同上,parent_id 为文件夹的 material_id |
| "把媒资素材添加到资产库" | 仅步骤 3 | 素材已在媒资库,只需挂载 |
| "在画布上放一张已有图" | 无需上传 | 直接 `asset search` 找到 asset_id → `canvas build-draft` |
| "上传并放到画布上" | 步骤 1 → 2 → `build-draft` | 入媒资库后用 build-draft 写入画布 |
---
## 4. 画布场景下的素材上传
画布素材需要**同时关联项目和画布**:
```bash
# 步骤 1: 上传
workrally upload ./file.png -o json
# 步骤 2: 入媒资库(必须传 project-id)
workrally asset create --url <cdn_url> --project-id <项目ID> -o json
# 步骤 3: 写入画布节点(使用返回的 asset_id)
workrally canvas build-draft <画布ID> --nodes '[
{"id":"node1","type":"image","position":{"x":0,"y":0},"data":{"asset":{"id":"<asset_id>"}},"style":{"width":512,"height":512}}
]'
```
> 📌 项目 ID 通过 `workrally project list` 获取。用户未指定项目时,查找名为"默认项目"的项目。
---
## 5. 资产库目录操作
### 查看各根目录下的内容
```bash
# 查看人物列表
workrally material list role_person -o json
# 查看道具列表
workrally material list role_prop -o json
# 查看场景列表
workrally material list role_scene -o json
# 查看网盘文件夹列表
workrally material list root -o json
```
### 查看角色的状态列表
```bash
# parent-id 传角色的 material_id
workrally material list <角色的material_id> -o json
```
### 查看某状态下的素材文件
```bash
# parent-id 传状态的 material_id
workrally material list <状态的material_id> -o json
```
### 创建文件夹
```bash
workrally material add --json-list '[{"material_name":"新文件夹","material_type":1,"parent_id":"role_person"}]'
```
### 获取角色详情(含 LoRA/提示词)
```bash
# 注意:role get 需要 role_id,不是 material_id
# material_id 格式如 "abc_0",role_id 格式如 "abc"
# 先通过 material get 获取 role_id
workrally material get <material_id> -o json
# 返回中有 role_id 字段,再查角色详情
workrally role get <role_id> -o json
```
> ⚠️ `material_id`(如 "abc_0",带 `_0` 后缀)≠ `role_id`(如 "abc")。如果只有 `material_id`,先通过 `material get` 获取 `role_id`。
---
## 6. 素材 URL 访问说明
| 素材类型 | 存储策略 | URL 行为 |
|----------|---------|---------|
| 图片 | **公开读** | `url` 可直接访问 |
| 视频 | **私有读** | `url` 为临时访问地址,过期后需重新获取 |
| 音频 | **私有读** | `url` 为临时访问地址,过期后需重新获取 |
> 📎 媒资 API 返回的素材 URL 可直接作为其他工具的 URL 参数传入,**无需手动处理**。如遇"非法或已过期"提示,通过下列命令重新获取即可。
过期后重新获取:
```bash
workrally asset get <asset_id> -o json
# 返回中的 url 和 download_url 为新的可访问地址
```
---
## 7. 批量操作
### 批量创建素材到资产库
`material add` 支持 `--json-list` 参数传入 JSON 数组,一次添加多个素材。也可通过 `tools call` 直接调用底层 MCP 工具:
```bash
workrally tools call material_manage --json-args '{
"action": "add",
"material_list": [
{"material_id":"asset_id_1","material_name":"素材1","material_type":2,"parent_id":"<状态ID>","material_detail":{...}},
{"material_id":"asset_id_2","material_name":"素材2","material_type":3,"parent_id":"<状态ID>","material_detail":{...}}
],
"project_ids": ["<project_id>"],
"source": 1
}'
```
### 批量获取素材详情
```bash
workrally asset get <id1> <id2> <id3> -o json
# 最多 50 个 ID
```
### 搜索媒资库
```bash
workrally asset search --project-id <id> -o json
# 可选筛选: --keyword "关键词" --type image/video/audio
```
腾讯出行服务叫车助手。处理“帮我叫车”“我下班了,帮我叫车”“我要从亚洲金融大厦去腾讯总部”这类请求。关键词:"打车"、"叫车"、"去[地点]"、"回家"、"上班"、"下班"、"司机"、"订单"、"查询订单"。注意:即使用户未明确说"打车",只要涉及从A地到B地、通勤、或交通方式选择,都应触发。不触发场景:开发打...
---
name: tms-takecar
description: '腾讯出行服务叫车助手。处理“帮我叫车”“我下班了,帮我叫车”“我要从亚洲金融大厦去腾讯总部”这类请求。关键词:"打车"、"叫车"、"去[地点]"、"回家"、"上班"、"下班"、"司机"、"订单"、"查询订单"。注意:即使用户未明确说"打车",只要涉及从A地到B地、通勤、或交通方式选择,都应触发。不触发场景:开发打车应用、使用其他导航app、订外卖、查公交时刻表、股票/财报查询。'
argument-hint: '输入叫车需求,例如:我下班了,帮我叫车'
user-invocable: true
---
# 腾讯出行服务打车助手
欢迎使用腾讯出行服务打车助手。我会帮你完成叫车、查单、取消订单等操作,并在开始前先检查当前环境是否就绪。
简单使用方式:
- 直接输入你的出行需求,例如:`帮我叫车`、`我下班了,帮我回家`、`从亚洲金融大厦去腾讯总部`
- 如果是首次使用,我会先引导你完成 token 和常住城市配置
- 如果已经有订单,也可以直接说:`帮我查订单`、`司机到哪了`、`帮我取消当前订单`
这个 skill 用于响应用户的打车需求,主文档仅保留核心约束。
服务提供方:腾讯出行服务
---
## 1. 核心约束(⚠️必须严格遵守)
**⚠️必须先阅读以下文档**
- [Quick Start 流程](./references/quick-start-workflow.md)
- [异常处理流程](./references/error_handling.md)
- [接口契约](./references/api-contract.md)
**的工具调用规则优先级高于通用对话习惯**
❌ 禁止在 workflow 约束的场景外自由回答地址、价格、订单等信息
✅ 只能在工具调用后根据结果进行正常对话
**每次进入 skill 必须先调用 `preflight`**
- `python3 ./scripts/tms_takecar.py preflight` 返回 `0` 才允许进入后续业务流程
- 返回 `1` 时必须进入 [Quick Start](./references/quick-start-workflow.md#quick-start-main) 补齐 `setup_token` / `setup_resident_city`
### 1.1 流程选择器
根据用户意图,选择并执行对应完整流程。每个流程必须走完,不能中途停止,不能自行跳过脚本调用。
| 用户意图 | 触发条件 | Few-shot 示例 | 执行流程 |
|--------|---------|--------------|---------|
| 首次使用 / 环境未就绪 | `preflight` 返回非 `0` | "怎么配置 token" / "我第一次用这个" | → [Quick Start](./references/quick-start-workflow.md#quick-start-main) |
| 快捷叫车 | 用户只说场景词触发固定目的地(如"回家"、"去公司"、"接孩子") | "回家" / "去公司" / "接孩子" | → [快捷叫车流程](./references/short-cut-workflow.md#short-cut-main) |
| 打车 / 叫车 / 出行 | 有从 A 到 B 的出行需求 | "从腾讯滨海到深圳北站" / "亚洲金融大厦打车到腾讯总部" | → [打车流程](./references/takecar-workflow.md#takecar-main-flow) |
| 取消订单 | 用户要取消当前订单 | "帮我取消当前订单" / "不坐了,取消吧" | → [取消订单流程](./references/order-workflow.md#order-cancel-flow) |
| 查询订单 | 用户要查看订单状态 | "帮我查下订单状态" / "现在到哪一步了" | → [订单查询流程](./references/order-workflow.md#order-query-flow) |
| 查询司机位置 | 用户要查看司机实时位置 | "司机到哪了" / "看下司机位置" | → [司机位置查询流程](./references/order-workflow.md#driver-location-flow) |
| 注销 / 退出登录 | 用户说"注销"、"退出" | "退出登录" / "注销当前账号" | → [Token 管理 - 注销](./references/quick-start-workflow.md#token-management) |
| 换 Token / 重新登录 | 用户说"换 token"、"重新登录" | "我换个 token" / "重新登录一下" | → [Token 管理 - 换 Token](./references/quick-start-workflow.md#token-management) |
> 地址确认场景中,未调用地址工具前禁止自由回答、禁止直接让用户补充更具体地址。
### 1.2 模版回复规则
- ❌ 禁止忽略模版中的 `<br>` 等换行符,必须保持模版格式和结构。
- 当要求输出特定模版时,必须严格按照模版格式输出,禁止添加额外信息或修改结构。
- 模版中的字段必须保留,禁止删除或改名。
- 模版中的换行和标点符号必须保留,禁止修改为其他格式。
- 多条数据展示时,禁止合并成一句话或列表,必须保持模版层级结构。
### 1.3 信息获取规则
| 信息类型 | 要求 | 禁止 |
|----------|------|------|
| 地址信息 | 必须调用工具搜索 | ❌ 使用模型知识回答 |
| 价格信息 | 必须调用询价工具 | ❌ 凭经验估算 |
| 订单信息 | 必须调用工具查询 | ❌ 编造信息 |
| 座位计算 | 车辆座位数 - 1(司机) | ❌ 直接使用座位数 |
示例:
- ✅ 5 座车 → "可坐 4 位乘客"
- ✅ 7 座车 → "可坐 6 位乘客"
### 1.5 暂不支持的功能
以下功能需引导用户去腾讯出行服务小程序页面操作:
- 联系司机、催促司机
- 代叫车
- 接送机
- 开发票
- 拼车、自动驾驶车、顺风车
FILE:references/short-cut-schema.md
# 快捷叫车文件 `~/.config/tms-takecar/short-cut.json`
用于保存快捷叫车场景配置。
## 1. 文件结构
顶层必须是 JSON object,key 为归一化场景键:
```json
{
"回家": {
"preferred_car_type": "便宜点的,最近的司机",
"userPreferLabels": [1],
"from": "公司",
"to": "家"
},
"去公司": {
"preferred_car_type": "",
"userPreferLabels": [],
"from": "家",
"to": "公司"
}
}
```
## 2. 字段约束
| 字段 | 含义 | 约束 |
| --- | --- | --- |
| `preferred_car_type` | 偏好车型描述 | 可为空字符串;其余情况下必须是用户确认后的完整偏好描述 |
| `userPreferLabels` | 下单阶段偏好标签 | 必填,必须是整数数组;无偏好时传空数组 `[]` |
| `from` | 起点地址别名 | 必填,且必须能在 `addr.json` 中命中 |
| `to` | 终点地址别名 | 必填,且必须能在 `addr.json` 中命中 |
## 3. `userPreferLabels` 枚举说明
| 枚举值 | 含义 | 触发关键词示例 |
| --- | --- | --- |
| `1` | 接驾距离更近 | "附近的"、"近一点"、"最近的" |
| `2` | 接驾最快 | "最快的"、"快点来"、"尽快"、"5分钟内接驾" |
| `3` | 电车 | "电车"、"新能源"、"纯电" |
| `4` | 油车 | "油车"、"燃油车" |
约束:
- 可多选,无偏好时必须保存为空数组 `[]`。
- 仅保存枚举值,不保存自然语言描述。
- 禁止给用户透出标签编号,只在内部作为 `create-order --user-prefer-labels` 的参数来源。
## 4. 场景键规则
- key 必须是归一化后的场景键,例如 `回家`、`去公司`、`接孩子`。
- 同一场景键全局唯一,禁止重复。
- 未命中场景键时,必须进入快捷场景设置流程,禁止猜测。
## 5. 读写规则
1. 快捷叫车主流程必须先读取本文件,再按 `from` / `to` 到 `addr.json` 查找对应地址对象。
2. 新增或更新场景时,必须保证 `from` 与 `to` 在 `addr.json` 中都已存在。
3. 允许单独更新 `preferred_car_type` 或 `userPreferLabels`;若修改 `from` 或 `to`,必须按整条场景记录覆盖。
4. `userPreferLabels` 的值必须与 [打车主流程中的 `userPreferLabels` 定义](./takecar-workflow.md#userpreferlabels-参数说明) 保持一致。
5. 禁止将 `poi.name`、自然语言地址文本或经纬度直接写入本文件;本文件只保存地址别名引用和快捷偏好配置。
FILE:references/error_handling.md
<a id="error-handling-main"></a>
## 异常处理流程
### 1. 处理原则
- 异常流程用于处理脚本执行失败、参数缺失、环境不一致等非主流程场景。
- 出现异常时,先停止当前主流程,进入对应修复流程;修复完成后再返回主流程继续。
### 2. 错误码映射(必须)
脚本返回非 `0` 退出码时,必须按下表执行:
| 退出码 | 错误类型 | 处理动作 |
|------|--------|--------|
| `1` | token 缺失/未配置 | 直接执行本文件「Token 缺失错误」流程 |
| `2` | 接口基地址未配置 | 中断流程并提示配置缺失;不继续业务调用 |
| `3` | HTTP 错误 | 中断当前步骤,向用户返回接口错误并提示稍后重试 |
| `4` | 网络错误 | 中断当前步骤,提示网络异常并引导重试 |
### 3. Token 缺失错误(必须)
当任意脚本返回 token 缺失/未配置相关错误(例如 token not found、token missing、未找到 token)时,必须按以下步骤处理:
1. 立即中断当前流程,不再继续当前打车步骤。
2. 直接切换到 [Quick Start 流程](./quick-start-workflow.md#quick-start-main) 执行修复。
3. 在 Quick Start 内按顺序完成:
- `python3 ./scripts/tms_takecar.py preflight`
- 若 token 缺失,引导用户获取 TOKEN
- `python3 ./scripts/tms_takecar.py save-token xxxxxx`
- 再次执行 `python3 ./scripts/tms_takecar.py preflight` 验证生效
4. 修复步骤结束后,以 `preflight` 结果为准:
- 返回码 `0` 且 `next_actions == ["ready"]` 视为修复成功
- 返回码 `1` 视为仍未就绪,继续按 `next_actions` 补齐
### 4. 返回主流程
- 若修复成功(`preflight` 返回 `0`),返回用户原始意图对应流程继续执行。
- 若修复失败(`preflight` 返回 `1`),明确告知失败原因与下一步需要用户提供的信息。
### 5. 下单业务错误处理(必须)
当 `create-order` 接口返回成功(退出码 `0`)但业务结果异常时,必须按以下规则处理:
| 判定条件 | 含义 | 处理动作 |
|---------|------|--------|
| `data.orderId` 不为空 | 下单成功 | 更新 `~/.config/tms-takecar/state.json` 中的 `orderId`,告知用户下单成功 |
| `data.orderId` 为空 且 `code == 101` | 有未完成订单 | 告知用户存在未完成订单,需先完成或取消后再下单;中断下单流程 |
| `data.orderId` 为空 且 `code != 0` | 其他业务失败 | 展示 `message` 告知用户失败原因,引导用户稍后重试 |
| `data.orderId` 为空 且 `code == 0` | 异常:成功但无订单ID | 告知用户下单异常,建议稍后重试 |
好的,搜索结果返回了多个选项。由于出发地在北京,目的地应该是"北京华侨城"或者"华侨城·大剧院"这两个在北京的选项。根据技能流程的模版要求,我需要向用户展示第一页的选项让用户选择:
FILE:references/quick-start-workflow.md
<a id="quick-start-main"></a>
## ⚡ Quick Start(2 分钟)
### 1. 检查环境(每次入口都要执行)
先运行 `python3 ./scripts/tms_takecar.py preflight` 判断:
- 当前 Python 版本是否满足 `>= 3.6`
- token 是否存在(来源:`~/.config/tms-takecar/env.json` 的 `token`)
- 常住城市是否存在(来源:`~/.config/tms-takecar/env.json` 的 `resident_city`)
- 下一步应该是安装 Python、引导取 token、设置常住城市,还是可以直接继续
`preflight` 返回码规则:
- `0`:环境已就绪(`next_actions` 仅为 `ready`)
- `1`:环境未就绪(按 `next_actions` 补齐)
#### 1.1 安装 Python
如果本机没有合适的 Python:
- 说明缺少运行环境
- 在用户同意后帮助安装 Python。如果用户拒绝安装,告知用户将无法继续提供服务
- 安装后重新运行 `python3 ./scripts/tms_takecar.py preflight`
#### 1.2 获取 Token
如果 token 缺失,使用以下模版回复用户:
**回复模版**
```markdown
请使用微信扫描下方二维码,获取 TOKEN 后回复我:

如果图片无法正常展示
请前往「微信」-「我」-「服务」-「出行服务」-「我的」-「头像/昵称」-「Token信息」中获取token
```
**❌ 禁止回复模版以外的内容**
用户获取到 TOKEN 后,执行:
```bash
python3 ./scripts/tms_takecar.py save-token xxxxxx
```
#### 1.3 常住城市确认
如果 `preflight` 返回 `setup_resident_city`,询问用户并执行:
```bash
python3 ./scripts/tms_takecar.py set-resident-city 北京市
```
约束:
- 城市名称必须完整:`北京` -> `北京市`
- 注意口语化表达映射:`魔都` -> `上海市`,`长安` -> `西安市`,`金陵` -> `南京市`
#### 1.4 再次确认
执行 `python3 ./scripts/tms_takecar.py preflight`,直到返回码为 `0`。
---
<a id="token-management"></a>
## 🔐 Token 管理
### 注销
用户表达“注销”时:
1. 执行 `python3 ./scripts/tms_takecar.py delete-token` 清空 token。
2. 再执行 `python3 ./scripts/tms_takecar.py preflight`,确认结果包含 `setup_token`。
### 换 Token
用户表达“换 token”时:
1. 先执行 `python3 ./scripts/tms_takecar.py delete-token`
2. 再执行 `python3 ./scripts/tms_takecar.py save-token xxxxxx`
3. 再次运行 `python3 ./scripts/tms_takecar.py preflight` 确认 token 已生效
### 常住城市查询/更新
- 查询:`python3 ./scripts/tms_takecar.py get-resident-city`
- 更新:`python3 ./scripts/tms_takecar.py set-resident-city 上海市`
FILE:references/short-cut-setup-workflow.md
<a id="short-cut-setup-main"></a>
## 🧩 设置快捷场景流程(未命中场景)
适用于以下触发条件:
- `short-cut.json` 中不存在用户归一化场景(回家 / 去公司 / 接孩子)
- `addr.json` 中不存在场景终点地址别名,无法直接执行快捷下单
### 1. 全局约束
**⚠️必须先阅读以下文档**
- [异常处理流程](./references/error_handling.md)
- [接口契约](./references/api-contract.md)
- [常用地址 Schema](./addr-schema.md)
- [快捷叫车 Schema](./short-cut-schema.md)
- [订单状态模版](./references/state-schema.md)
- [设置快捷场景流程](./short-cut-setup-workflow.md)
1. **流程串行化**:禁止与快捷下单流程并行执行;进入本流程后,必须先完成场景设置再下单。同一时刻全局只允许一个用户在执行此流程(若涉及多用户,必须按序列化顺序执行)。
2. **回写事务性**:凡标注"立即写回"的步骤,必须满足以下三层验证才能进入下一步:
- **写前验证**:先通过 [`addr-keys`](./api-contract.md#cmd_addr_keys) / [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 或 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) / [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 检查 `addr.json` 和 / 或 `short-cut.json` 当前状态,确认 JSON 结构完整、可解析、无损坏。
- **写中确认**:实际调用 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 或 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 写入目标文件,确保每个字段按要求写入,且无部分写入。
- **写后验证**:写入完成后立即再次调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 或 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 回读目标对象,验证写入值与预期完全一致;若不一致立即回滚并转入异常处理。
3. **原子性保证**:
- 单次写回不得跨越多个编辑操作;如需多文件写入(如 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) + [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 同时更新),必须一次性原子完成,中途任何失败都得回滚所有已作改动。
- 禁止异步或后台写回;所有写回都必须在前台同步完成,并等待验证结果后才能继续。
- 禁止分段写入(今天写表1,明天补写表2),每个流程的所有写回必须在该流程结束前完成。
4. **地址相关请求**:必须先调用 `poi-search`,禁止在未检索前直接判断地点或使用缓存数据。
5. **下单与询价能力复用本文档**:
- 地址检索:见 [3. 地址检索](#short-cut-setup-poi-search)
- 价格预估:见 [4. 价格预估](#short-cut-setup-estimate)
- 下单:见 [5. 下单](#short-cut-setup-order)
### 2. 主流程
```
进入设置快捷场景流程
↓
调用 [`addr-keys`](./api-contract.md#cmd_addr_keys) / [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) 并归一化场景键(回家 / 去公司 / 接孩子)
↓
终点地址记忆是否存在?
├─ 否 → 终点地址检索与确认 → 立即调用 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value)
└─ 是 → 使用已有终点
↓
按地址检索确认起点
↓
询问用户是否固定该起点为当前场景默认起点
↓
若同意 → 立即调用 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 写入场景记录(必要时补调 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 写入起点地址对象)
↓
执行价格预估并让用户确认车型
↓
询问是否保存车型偏好(可选)
↓
若同意 → 立即写回该场景偏好车型
```
#### 2.1 Step 1:终点确认(地址别名不存在时)
1. 如果用户输入不是明确 POI 名称(如“家”“公司”“xx家”),先向用户确认可检索关键词。
2. 调用 [3. 地址检索](#short-cut-setup-poi-search) 搜索终点(使用 `poi-search --scene 2`,返回结果包含推荐下车点)。
3. 用户确认后,**必须立即执行以下完整写回流程**:见 [Step 1.1 写回](#short-cut-setup-writeback-step-11)
- **写回前检查**:调用 [`addr-keys`](./api-contract.md#cmd_addr_keys),确认文件可读、结构完整
- **执行写回**:按 7.2.1 规范调用 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 完整写入地址对象
- **写后验证**:调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 读取刚写入的对象,逐字段对比预期值,如不一致立即报错并停止后续操作
- 验证成功后才能进入 Step 2
#### 2.2 Step 2:起点确认与场景固化
1. 按 [3. 地址检索](#short-cut-setup-poi-search) 确认起点(使用 `poi-search --scene 1`,返回结果包含推荐上车点)。
2. 询问用户:“以后是否每次都以这个起点发起叫车?”
3. 若用户同意,**必须立即执行以下完整写回流程**:见 [Step 1.2.2 写回](#short-cut-setup-writeback-step-122)
- **写回前检查**:(1) 调用 [`addr-keys`](./api-contract.md#cmd_addr_keys) 和 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) 验证两个文件都完整、可解析、无损坏;(2) 检查终点地址别名是否已出现在 [`addr-keys`](./api-contract.md#cmd_addr_keys) 返回结果中
- **原子性准备**:若起点对应地址别名不存在,先在内存中预构建该地址对象完整数据,确保两个文件的写入能被视为一个原子操作
- **执行写回**:按 7.2.2 规范一次性完成所有写入(必要时调用 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 补齐地址 + 调用 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 写入场景记录 + 必要的 `memory.md` 更新时间),中途任何失败都立即回滚
- **写后验证**:(1) 调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 读取新增/更新的场景记录,验证 `from` 和 `to` 都能通过 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 找到对应对象;(2) 逐字段对比预期值,不一致立即报错
- 验证成功后才能进入 Step 3
5. 写回完成后,后续询价/下单必须按该场景记录中的 `from`、`to` 地址别名调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 回查对应对象。
#### 2.3 Step 3:询价、偏好保存与下单
1. 按本文档 [4. 价格预估](#short-cut-setup-estimate) 执行询价并展示车型。
2. 用户确认车型后由脚本自动写入 `~/.config/tms-takecar/state.json`(流程仅读取):
- 通过 `create-order --price-estimate-keys '<JSON数组>'` 一步完成 `userSelect` 标记与下单;如需传入偏好可追加 `--user-prefer-labels '<JSON数组>'`
3. 询问用户是否保存本次车型偏好。
4. 若用户同意,**必须立即执行第一次写回**(偏好车型):
- **写回前检查**:先调用 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys),再调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value),验证该场景记录存在
- **执行写回**:调用 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 更新该场景记录,不修改无关字段
- **写后验证**:再次调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 读取该记录,确认 `preferred_car_type` 已更新为用户选中的值,不一致立即报错
- 验证成功后才能进入下单
5. 询问用户现在是否要下单,用户确认后执行下一步。用户如果不下单则告知用户下次可以直接使用快捷下单
5. 按[快捷叫车流程](./references/short-cut-workflow.md#short-cut-main),以当前场景下单。
---
<a id="short-cut-setup-poi-search"></a>
### 3. 地址检索
**流程**:
1. 调用 `poi-search` 搜索地点(必须传 `scene` 参数):
- 起点检索(上车点)使用 `scene=1`。
- 终点检索(下车点)使用 `scene=2`。
- 仅做普通地点搜索且不需要推荐上下车点时使用 `scene=0`。
2. 判断是否需要用户选择:
**需要用户选择**:
- 搜索结果返回多个不同地点。
- 多个同名但位置不同的地点。
- 用户表述宽泛且结果差异大。
- **核心原则**:不能100%确定用户表述的地点。
**自动匹配**:
- 搜索结果只有1个地点。
**当需要用户选择时,用 `poi-search` 填充以下模版回复用户**
**⚠️ 模版中的条目数严格按照`poi-search`返回值**
**❌ 禁止自行替用户做出选择**
```markdown
### 请在以下地址中选择您的地址
📍 **1. {items[0].name}: {items[0].address}**
🅿️ **{items[0].point_name}**
---
📍 **2. {items[1].name}: {items[1].address}**
🅿️ **{items[1].point_name}**
---
📍 **{n}. {items[n].name}: {items[n].address}**
🅿️ **{items[n].point_name}**
---
### 你可以回复:”1“ 或者 "{items[0].name}"
### 你也可以回复:”更多“或者告诉我更确切的地址
```
- 如果用户输入新的keyword则重新调用[`poi-search --scene 1/2`](./api-contract.md#cmd_poi_search) 搜索地点
- 如果用户选择翻页或者说:"更多"、"下一页"则调用[`poi-search --scene 1/2 --page-index <当前page-index++>`](./api-contract.md#cmd_poi_search) 进行翻页
- 直到用户通过回复序号,回复地点名称等方式从返回值中做出选择再进行下一步
---
<a id="short-cut-setup-writeback"></a>
### 4. 记忆写入规范
所有写回 `~/.config/tms-takecar/addr.json`、`~/.config/tms-takecar/short-cut.json` 的操作,必须通过 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) / [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 执行。`memory.md` 不再保存地址与快捷叫车内容。**禁止省略写回步骤,禁止仅在对话中提到"已保存"而不实际调用对应接口写入文件。**
#### 4.1 写回格式定义
**常用地址格式**(通过 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 写入 `~/.config/tms-takecar/addr.json` 对应 key):
```json
"{地址别名}": {
"name": "{poi.name}",
"address": "{poi.address}",
"poiid": "{poi.poiid}",
"citycode": "{poi.citycode}",
"point_name": "{point.name}",
"point_longitude": "{point.longitude}",
"point_latitude": "{point.latitude}"
}
```
示例:
```json
"家": {
"name": "万科翡翠滨江",
"address": "深圳市南山区沙河西路3088号",
"poiid": "B0FFXXXX01",
"citycode": "440300",
"point_name": "万科翡翠滨江北门",
"point_longitude": 113.934528,
"point_latitude": 22.521867
}
```
**快捷叫车格式**(通过 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 写入 `~/.config/tms-takecar/short-cut.json`):
这里的 `from`、`to` 对应 `addr.json` 的 `{地址别名}`,不是 `{poi.name}`。
```json
"{归一化场景键}": {
"preferred_car_type": "{偏好车型,无则空字符串}",
"userPreferLabels": [],
"from": "{起点地址别名}",
"to": "{终点地址别名}"
}
```
示例:
```json
"回家": {
"preferred_car_type": "便宜点的,最近的司机",
"userPreferLabels": [1],
"from": "公司",
"to": "家"
},
"去公司": {
"preferred_car_type": "",
"userPreferLabels": [],
"from": "家",
"to": "公司"
}
```
#### 4.2 各步骤写回清单
##### <a id="short-cut-setup-writeback-step-11"></a> 7.2.1 Step 1.1 写回(终点地址记忆补齐)
1. 调用 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 更新场景对应地址别名:
- 回家 → 家
- 去公司 → 公司
- 接孩子 → 接孩子
2. 写入字段:`name`、`address`、`poiid`、`citycode`、`point_name`、`point_longitude`、`point_latitude`。
##### <a id="short-cut-setup-writeback-step-122"></a> 7.2.2 Step 1.2.2 写回(场景行新增与偏好保存)
1. 起点确认且用户同意固定起点后,调用 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 新增或覆盖场景记录:
- `"归一化场景键": {"preferred_car_type": "", "userPreferLabels": [], "from": "{起点地址别名}", "to": "{终点地址别名}"}`
- 其中 `{起点地址别名}`、`{终点地址别名}` 必须能在 `addr.json` 中找到对应对象
2. 若起点对应地址别名不存在,同步调用 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 补写对应地址对象。
3. 用户同意保存偏好后,将该场景记录的 `preferred_car_type` 更新为选中车型描述;若存在下单阶段偏好,同时更新 `userPreferLabels`。
#### 7.3 写回约束(严格模式)
1. **串行化执行**:同一时间只能有一个写回操作进行;禁止并发调用会修改 `addr.json` / `short-cut.json` 的 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) / [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value),每个写回必须原子性完成。
2. **来源唯一性**:地址字段(`poi.*`、`point.*`)仅允许来自用户最终确认的 `poi-search` + 推荐上下车点结果,禁止手填、猜测或复用旧候选数据。
3. **数据完整性**:
- 地址对象必须包含 7 个字段(`name`、`address`、`poiid`、`citycode`、`point_name`、`point_longitude`、`point_latitude`),缺一不可。
- 快捷叫车场景对象必须包含 4 个字段(`preferred_car_type`、`userPreferLabels`、`from`、`to`);其中 `from` 和 `to` 不得为空,必须在 `addr.json` 中现存。
- 禁止存储空值、`null` 或占位符,允许的唯一例外是 `preferred_car_type` 可为空字符串、`userPreferLabels` 可为空数组。
4. **键值一致性**:
- `short-cut.json` 的所有地址别名引用必须是 `addr.json` 的现存 key。
- 同一场景键在 `short-cut.json` 中全局唯一(不得重复出现)。
- 禁止在 `addr.json` 中使用 POI 名称作为 key,仅允许 "家"、"公司"、"接孩子" 等语义键。
5. **文件完整性**:每次写回后必须再次调用 [`addr-keys`](./api-contract.md#cmd_addr_keys) / [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 或 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) / [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 验证 JSON 格式完整、可解析、无多余残留字段。
#### 7.4 回写执行规则(原子性约束)
1. **写回必须完成后才能进入下一步**:标注"立即写回"的步骤必须确保文件落盘(disk sync),验证写入成功,再进入下一业务步骤(询价、下单、后续问询)。禁止异步写回或延迟写入。
2. **原子性保证**:单次写回操作必须是原子的:
- 如果写回包含 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 地址对象新增 + [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 场景记录新增,必须两步都成功,或都失败回滚。
- 如果任何步骤校验失败,立即停止该次写回,不得部分写入。
- 禁止跨流程的批量延迟写入;每个流程的写回必须在该流程内完成。
3. **时间戳一致性**:
- 单次流程内多个写回操作必须使用同一时间戳(精确到分钟)。
- 不得在不同时刻对同一行执行多次独立写回;必须先读取当前状态,整合所有变更,一次性写入。
- 时间戳必须按 YYYY-MM-DD HH:mm 格式,且不得早于上次更新时间。
4. **覆盖规则与版本管理**:
- 场景记录已存在:完整覆盖(不允许字段级部分更新),新值覆盖旧值,旧数据不可恢复。
- 场景记录不存在:新增一条记录,初始状态需符合 7.3 条 3 的字段完整性要求。
- 若检测到同一场景键出现重复来源或引用冲突,立即报错并转入异常处理,禁止自动清理。
5. **字段级写入规则**:
- 地址对象:仅允许整对象新增或整对象覆盖,禁止部分字段更新(如只更新 `name`)。
- 快捷叫车对象:允许只更新 `preferred_car_type` 或 `userPreferLabels`;其他字段变更必须进行整对象覆盖。
6. **回填规则与数据一致性**:写回完成立即读取并回填 `~/.config/tms-takecar/state.json`:
- 必须先调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 读取场景记录,再按其中的 `from` / `to` 调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 查询地址对象。
- 如果查询失败(key 不存在),立即转入异常处理流程,禁止使用 key 文本本身当参数。
- 回填后的 `state.json` 中 `pickup` / `dropoff` 的值必须与 `addr.json` 的对应对象完全一致。
7. **失败与回退**:
- 任一写回步骤的校验失败(key 不存在、字段缺失、JSON 损坏、时间戳冲突)时,立即停止后续调用,转入异常处理流程。
- 异常处理流程:(1) 记录故障原因和当前状态;(2) 通知用户流程中断,建议检查 [`addr-keys`](./api-contract.md#cmd_addr_keys) / [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) 是否还能正常返回;(3) 禁止自动修复或猜测式回滚,需明确用户确认后才能重试。
8. **禁止行为清单**(严禁):
- 禁止在对话中口头确认已保存而不实际编辑文件。
- 禁止跳过写回阶段直接进入下一业务步骤。
- 禁止复用旧流程的 `state.json` 拷贝,每次写回后必须重新读取最新的地址对象。
- 禁止猜测式补充缺失字段,缺失时必须向用户重新确认。
- 禁止并发调用修改同一文件的不同 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) / [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value)。
- 禁止保留历史版本或注释字段,JSON 必须保持清洁,仅包含当前有效数据。
FILE:references/api-contract.md
# 腾讯出行接口待确认项
当前 skill 的可执行入口已经统一为 `python3 ./scripts/tms_takecar.py`(路径相对于 SKILL.md 所在目录)。
## Python 脚本接口
### 命令入口
```bash
python3 ./scripts/tms_takecar.py <subcommand> [options]
```
### 默认请求参数(HTTP)
- 默认 API Base URL:`https://test.weixin.go.qq.com`
- 所有实际 HTTP 请求都会自动注入以下 body 固定字段:
- `seqId`: 动态生成(数字字符串,用于请求链路追踪)
- `timestamp`: 动态生成(当前毫秒时间戳)
- `wxAppId`: `wx65cc950f42e8fff1`
- `token`: 来自 `~/.config/tms-takecar/env.json` 的 `token` 字段(由 `save-token` 持久化)
### 鉴权错误码约定
- 当接口返回以下任一错误码时,统一按 token 缺失或无效处理,并返回退出码 `1`:
- `errCode = 10`
- `errCode = 35`
### 子命令
所有子命令出现非 `0` 退出码时,统一按 [异常处理流程](./error_handling.md#error-handling-main) 执行,不在调用点自行扩展处理分支。
<a id="preflight"></a>
1. `preflight` <a id="cmd_preflight"></a>
- 作用:检查 Python 运行环境、`TMS_SKILL_TOKEN` 与常住城市是否就绪
- 输入:无
- 输出:JSON,包含 `python_ok`、`python_version`、`platform`、`token_present`、`resident_city`、`resident_city_present`、`next_actions`
- 退出码:
- `0`:`next_actions` 仅包含 `ready`
- `1`:存在缺失项(如 `setup_token`、`setup_resident_city`)
2. `save-token <token>` <a id="cmd_save_token"></a>
- 作用:将 `TMS_SKILL_TOKEN` 持久化到 `~/.config/tms-takecar/env.json`
- 输入参数:
- 位置参数 `token`:要保存的 token
- 可选参数 `--config-file`:显式指定配置文件路径,默认 `~/.config/tms-takecar/env.json`
- 成功输出:JSON,包含 `saved`、`config_file`
- 退出码:`0`
3. `delete-token` <a id="cmd_delete_token"></a>
- 作用:从 `~/.config/tms-takecar/env.json` 清空本地保存的 `TMS_SKILL_TOKEN`
- 输入参数:
- 可选参数 `--config-file`:显式指定配置文件路径,默认 `~/.config/tms-takecar/env.json`
- 成功输出:JSON,包含 `deleted`、`config_file`
- 退出码:`0`
4. `set-resident-city <city_name>` <a id="cmd_set_resident_city"></a>
- 作用:写入或更新常住城市
- 输入参数:
- 位置参数 `city_name`:常住城市名称,推荐完整格式(如 `北京市`)
- 可选参数 `--env-file`:显式指定 env 配置文件路径,默认 `~/.config/tms-takecar/env.json`
- 成功输出:JSON,包含 `updated`、`resident_city`、`env_file`
- 退出码:
- `0`:成功
- `2`:参数错误(如空城市)
5. `get-resident-city` <a id="cmd_get_resident_city"></a>
- 作用:读取常住城市
- 输入参数:
- 可选参数 `--env-file`:显式指定 env 配置文件路径,默认 `~/.config/tms-takecar/env.json`
- 成功输出:JSON,包含 `resident_city`、`resident_city_present`、`env_file`
- 退出码:`0`
6. `delete-state` <a id="cmd_delete_state"></a>
- 作用:删除 `~/.config/tms-takecar/state.json`
- 输入参数:
- 可选参数 `--state-file`:显式指定状态文件路径,默认 `~/.config/tms-takecar/state.json`
- 成功输出:JSON,包含 `deleted`、`state_file`
- 退出码:`0`
7. `addr-keys` <a id="cmd_addr_keys"></a>
- 作用:返回 `~/.config/tms-takecar/addr.json` 中的全部 key
- 输入参数:
- 可选参数 `--addr-file`:显式指定地址文件路径,默认 `~/.config/tms-takecar/addr.json`
- 成功输出:JSON,包含 `keys`、`count`、`addr_file`
- 说明:这是 `addr.json` 的 `getAllKeys` / “返回全部 keys” 接口
- 退出码:`0`
8. `addr-get-value <key>` <a id="cmd_addr_get_value"></a>
- 作用:按 key 读取 `~/.config/tms-takecar/addr.json` 中的 value
- 输入参数:
- 位置参数 `key`:地址别名,例如 `家`、`公司`、`小宝学校`
- 可选参数 `--addr-file`:显式指定地址文件路径,默认 `~/.config/tms-takecar/addr.json`
- 成功输出:JSON,包含 `key`、`found`、`value`、`addr_file`
- 说明:这是 `addr.json` 的 `getValuesByKey` 接口;当 key 不存在时,返回 `found=false` 且 `value=null`
- 退出码:
- `0`:成功
- `2`:参数错误(如 key 为空)
9. `addr-upsert-value <key> <value_json>` <a id="cmd_addr_upsert_value"></a>
- 作用:按 key 新增或更新 `~/.config/tms-takecar/addr.json` 中的 value
- 输入参数:
- 位置参数 `key`:地址别名,例如 `家`、`公司`、`小宝学校`
- 位置参数 `value_json`:JSON object 字符串,推荐直接传 `poi-search` 单条 item 结果
- 可选参数 `--addr-file`:显式指定地址文件路径,默认 `~/.config/tms-takecar/addr.json`
- 成功输出:JSON,包含 `updated`、`created`、`key`、`value`、`file`、`addr_file`
- 说明:这是 `addr.json` 的 `upsertValue` 接口;`created=true` 表示新建,`created=false` 表示覆盖已有值
- 退出码:
- `0`:成功
- `2`:参数错误(如 `value_json` 不是 JSON object)
10. `addr-sync-to-state <key> --scene <1|2>` <a id="cmd_addr_sync_to_state"></a>
- 作用:将 `addr.json` 中指定 key 的地址对象同步到 `~/.config/tms-takecar/state.json` 的 `pickup` 或 `dropoff`
- 输入参数:
- 位置参数 `key`:地址别名,例如 `家`、`公司`、`小宝学校`
- 必选参数 `--scene`:`1` 表示写入 `pickup`,`2` 表示写入 `dropoff`
- 可选参数 `--addr-file`:显式指定地址文件路径,默认 `~/.config/tms-takecar/addr.json`
- 可选参数 `--state-file`:显式指定状态文件路径,默认 `~/.config/tms-takecar/state.json`
- 成功输出:JSON,包含 `updated`、`key`、`scene`、`scene_key`、`state_file`、`addr_file`
- 联动写入:
- 将目标地址对象规范化后写入 `state.json` 的 `pickup` 或 `dropoff`,格式与 `poi-search` 返回 item 一致
- 成功后会重置下游字段 `estimate=null`、`userPreferLabels=[]`、`orderId=null`
- 退出码:
- `0`:成功
- `2`:参数错误(如 key 不存在、scene 非法、地址对象缺少 `poiid`)
11. `short-cut-keys` <a id="cmd_short_cut_keys"></a>
- 作用:返回 `~/.config/tms-takecar/short-cut.json` 中的全部 key
- 输入参数:
- 可选参数 `--short-cut-file`:显式指定快捷场景文件路径,默认 `~/.config/tms-takecar/short-cut.json`
- 成功输出:JSON,包含 `keys`、`count`、`short_cut_file`
- 说明:这是 `short-cut.json` 的 `getAllKeys` / “返回全部 keys” 接口
- 退出码:`0`
12. `short-cut-get-value <key>` <a id="cmd_short_cut_get_value"></a>
- 作用:按 key 读取 `~/.config/tms-takecar/short-cut.json` 中的 value
- 输入参数:
- 位置参数 `key`:归一化场景键,例如 `回家`、`去公司`、`接孩子`
- 可选参数 `--short-cut-file`:显式指定快捷场景文件路径,默认 `~/.config/tms-takecar/short-cut.json`
- 成功输出:JSON,包含 `key`、`found`、`value`、`short_cut_file`
- 说明:这是 `short-cut.json` 的 `getValuesByKey` 接口;当 key 不存在时,返回 `found=false` 且 `value=null`
- 退出码:
- `0`:成功
- `2`:参数错误(如 key 为空)
13. `short-cut-upsert-value <key> <value_json>` <a id="cmd_short_cut_upsert_value"></a>
- 作用:按 key 新增或更新 `~/.config/tms-takecar/short-cut.json` 中的 value
- 输入参数:
- 位置参数 `key`:归一化场景键,例如 `回家`、`去公司`、`接孩子`
- 位置参数 `value_json`:JSON object 字符串,例如 `{"preferred_car_type":"","from":"公司","to":"家"}`
- 可选参数 `--short-cut-file`:显式指定快捷场景文件路径,默认 `~/.config/tms-takecar/short-cut.json`
- 成功输出:JSON,包含 `updated`、`created`、`key`、`value`、`file`、`short_cut_file`
- 说明:这是 `short-cut.json` 的 `upsertValue` 接口;`created=true` 表示新建,`created=false` 表示覆盖已有值
- 退出码:
- `0`:成功
- `2`:参数错误(如 `value_json` 不是 JSON object)
14. `poi-search` <a id="cmd_poi_search"></a>
- 作用:按关键字检索 POI,并按场景合并推荐上下车点(两步流程)
- 内部流程:
1. 调用基础检索接口 `/mcp/open/tms/lbs/suggestion` 获取 POI 列表
2. 当 `scene=1` 或 `scene=2` 时,对每个 POI 结果分别调用二级接口:
- `scene=1`(上车点):`/mcp/open/tms/recommend/taxi/getOnPoints`
- `scene=2`(下车点):`/mcp/open/tms/recommend/taxi/getDropOffPointV2`
3. 组合基础 POI 信息与推荐点信息,输出扁平化列表
- 输入参数:
- `--keyword`:检索关键字,必填
- `--city-name`:城市名,非必填,默认 `""`。映射为 API 字段 `region`。当该参数为空时,脚本回退读取 `~/.config/tms-takecar/env.json` 中的 `resident_city`。
- 城市解析约束:用户明确提到城市(如“北京的新街口”“上海人民广场”)时应优先由 agent 填充 `--city-name`;若表达歧义(如“四川饭店”“xx驻京办事处”)必须先追问所在城市,再调用 `poi-search`。
- `--page-size`:分页大小,非必填,默认 `3`。映射为 API 字段 `pageSize`
- `--page-index`:页码索引(1-based),非必填,默认 `1`。映射为 API 字段 `pageIndex`
- `--scene`:场景参数,非必填,默认 `0`。取值:`0`-普通检索,`1`-检索+推荐上车点,`2`-检索+推荐下车点;其他值报错。映射为 API 字段 `policy`(`0→0, 1→10, 2→11`)
- `--state-file`:显式指定状态文件路径,默认 `~/.config/tms-takecar/state.json`
- 基础检索 API(步骤 1):
- 路径:`/mcp/open/tms/lbs/suggestion`
- 请求 payload:`keyword`、`region`、`policy`、`pageIndex`、`pageSize`
- 响应解析:`data.data.dataList[]`,每个元素含 `id`、`title`、`address`、`adcode`、`location.lat`、`location.lng`
- 上车点 API(步骤 2,scene=1):
- 路径:`/mcp/open/tms/recommend/taxi/getOnPoints`
- 请求 payload:`lat`、`lng`(来自步骤 1)、`maxCount: 1`、`appId: "0"`、`appChannelId: "0"`、`geoPointAsFallback: true`、`geoPointWhenNoAbsorb: true`
- 响应解析:`data.data.points[0]` → `title`、`location.lat`、`location.lng`
- 下车点 API(步骤 2,scene=2):
- 路径:`/mcp/open/tms/recommend/taxi/getDropOffPointV2`
- 请求 payload:`poiId`(来自步骤 1 的 `id`)、`endLat`、`endLng`(来自步骤 1)、`appId: "0"`、`appChannelId: "0"`、`checkSubPoi: true`
- 响应解析优先级:
1. `data.data.parkingSuggestions.points` 非空时:展开 `subPoints`(`point_name = parent.title + "-" + sub.title`);无 `subPoints` 时直接使用 point(`point_name = point.title`)
2. `parkingSuggestions.points` 为空时:取 `data.data.dropOffPoints.pointList[0]`(注意:是 `data.data.dropOffPoints`,不是 `parkingSuggestions.dropOffPoints`)
- 成功输出:
- 实际调用成功时返回 `{"mode": "searched", "result": ...}`
- `scene=1/2` 时会自动写入 `~/.config/tms-takecar/state.json` 的 `pickup` 或 `dropoff` 数组:按 `poiid` 去重追加(已存在则覆盖)
- 返回数据约定:
- `result.body.items` 为扁平化地点列表,每个 item 包含:
- POI 字段:`name`、`address`、`longitude`、`latitude`、`poiid`、`citycode`
- 推荐点字段:`point_name`、`point_longitude`、`point_latitude`
- `scene=0` 时推荐点字段为空字符串
- `scene=1` 时推荐点字段为上车点信息(每个 POI 最多 1 条推荐点)
- `scene=2` 时推荐点字段为下车点信息(每个 POI 可能展开为多条,如机场的多个航站楼出入口)
- `result.body.page_index` 为当前返回页码索引
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
15. `build-static-map-url` <a id="cmd_build_static_map_url"></a>
- 作用:根据若干 marker 经纬度和 marker 枚举,生成腾讯静态地图 URL
- 输入参数:
- `--markers-json`:必填,JSON array;每个元素为 `{"latitude": ..., "longitude": ..., "marker": "起|终|P"}`
- 使用系统预设 markers,支持 3 种枚举:
- `起` → `color:red|label:起`
- `终` → `color:green|label:终`
- `P` → `color:blue|label:P`
- 成功输出:JSON,格式为 `{"url": "<static_map_url>"}`
- 退出码:
- `0`:成功
- `1`:缺少 token
- `2`:参数错误
16. `select-poi` <a id="cmd_select_poi"></a>
- 作用:根据用户选择的 `poiid` 收敛 `pickup`/`dropoff` 候选,仅保留 1 条记录
- 输入参数:
- `--poiid`:用户选择的 POI ID,必填
- `--scene`:对应 poi-search 场景,必填,取值 `1`(pickup)或 `2`(dropoff)
- `--state-file`:显式指定状态文件路径,默认 `~/.config/tms-takecar/state.json`
- 成功输出:JSON,包含 `updated`、`scene`、`poiid`、`state_file`
- 联动写入:成功后会重置下游字段 `estimate=null`、`userPreferLabels=[]`、`orderId=null`
- 退出码:
- 参数错误:`2`
- 成功:`0`
17. `estimate-price` <a id="cmd_estimate_price"></a>
- 作用:根据起终点进行打车价格预估
- 目标接口:`/mcp/open/tms/takecar/estimate/price`
- 输入参数:
- 可选参数 `--state-file`:显式指定状态文件路径,默认 `~/.config/tms-takecar/state.json`
- 参数来源:起终点、城市编码、POI 信息全部由脚本从 state 文件读取(`pickup` / `dropoff` 收敛后的唯一候选)
- 偏好来源:`state.userPreferLabels`(由上游流程写入),询价成功后回写到 `estimate.userPreferLabels`
- 成功输出:
- 实际调用成功时返回 `{"mode": "estimated", "result": ...}`
- 成功后自动写入 `~/.config/tms-takecar/state.json` 的 `estimate` 字段(含 `estimateKey`、`distance`、`estimateTime`、`products`、`userPreferLabels`)
- 返回数据约定:
- `result.body.code`:响应状态码,0 表示成功
- `result.body.message`:响应消息
- `result.body.data.cityStatus`:城市状态,0-已下线,1-上线,2-灰度中,3-暂停服务
- `result.body.data.cityMessage`:城市状态说明
- `result.body.data.distance`:预估里程,单位米
- `result.body.data.estimateTime`:预估时长,单位秒
- `result.body.data.estimateKey`:预估 key,下单时需要
- `result.body.data.product`:预估列表,每个元素包含:
- `riderInfo.rideType`:车型(1-普通经济型,2-超惠经济型,3-优选型,4-舒适型,5-商务型,6-豪华型,7-出租车)
- `riderInfo.riderClassify`:运力类目(1-经济型,2-优享型,3-舒适型,4-商务型,5-豪华型,6-出租车,7-超惠型)
- `riderInfo.riderDesc`:车型描述
- `priceEstimate`:运力预估列表,每个元素包含:
- `estimatePrice`:预估价,单位分
- `defaultChecked`:是否默认勾选(1 是 0 否)
- `discountAmount`:优惠金额,单位分
- `discountPercentage`:折扣力度(0-100)
- `discountType`:折扣类型(1-优惠券,2-折扣券)
- `priceEstimateKey`:运力预估 key
- `sp.code`:运力商 ID
- `sp.name`:运力商名称
- `sp.aliasName`:运力商别名
- 结果裁剪规则:仅保留上述字段;`riderInfo.rideType` 不在 `1~7` 枚举范围内的条目直接丢弃
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
17. `create-order` <a id="cmd_create_order"></a>
- 作用:创建打车订单(包含风控校验)
- 目标接口:`/mcp/open/tms/takecar/risk/verify/book/order`
- 输入参数:
- 可选参数 `--state-file`:显式指定状态文件路径,默认 `~/.config/tms-takecar/state.json`
- 可选参数 `--price-estimate-keys`:JSON 数组字符串,示例:`'["mock-price-key-eco-001"]'`。若提供,脚本先更新 `state.json` 中 `estimate.products[].userSelect`,再构造请求
- 可选参数 `--user-prefer-labels`:JSON 整数数组字符串,示例:`'[1, 3]'`。若提供,脚本先更新 `state.json` 中 `estimate.userPreferLabels`,再构造请求
- 参数来源:起终点、城市编码、POI 信息全部由脚本从 state 文件读取(`pickup` / `dropoff` 收敛后的唯一候选)
- `productStr`:脚本内部根据 `estimate.products` 中 `userSelect=1` 的条目自动构造
- 固定字段:请求中额外包含 `departureTime`(当前毫秒时间戳)、`orderServiceType=1`、`isNeedReEstimate=true`
- 位置字段:
- 坐标使用已收敛候选中的 `point_latitude` / `point_longitude`
- `toId` 使用终点 `poiid`
- `fromName` / `toName` 优先使用 `point_name`,为空时回退到 `name`
- 成功输出:
- 实际调用成功时返回 `{"mode": "ordered", "result": ...}`
- 返回数据约定:
- `result.body.code`:响应状态码,`0` 表示成功
- `result.body.message`:响应消息
- `result.body.data.orderId`:订单 ID;为空、空字符串或字符串 `null` 都表示下单失败
- `result.body.data.unfinishedOrder`:是否存在未完成订单
- 下单结果判定:
- `data.orderId` 不为空 → 下单成功
- `data.orderId` 为空 → 下单失败,需结合 `code` 和 `message` 判断原因
- `data.unfinishedOrder == true` → 有未完成订单,禁止重复下单
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
18. `query-ongoing-order` <a id="cmd_query_ongoing_order"></a>
- 作用:查询当前用户是否有进行中的订单
- 输入参数:无
- 成功输出:
- 实际调用成功时返回 `{"mode": "queried", "result": ...}`
- 返回数据约定(`result.body.data`):
- `hasOnGoingOrder`:是否有进行中的订单
- `orderId`:进行中订单 ID
- `status`:订单状态(`assigning`、`accepted`、`driver_arrived`、`in_trip`、`trip_finished`、`completed`、`cancelled`、`reassigning`)
- `orderDesc`:订单状态描述
- `vehicleColor`:车辆颜色
- `licensePlates`:车牌号
- `supplierName`:运力商名称
- `startName/startLng/startLat`:出发地名称与经纬度
- `endName/endLng/endLat`:目的地名称与经纬度
- `cancelFee`:取消费(元)
- `unpaidFee`:待支付费用(元)
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
19. `cancel-order` <a id="cmd_cancel_order"></a>
- 作用:取消进行中的订单
- 内部流程:
1. 先调用 `query-ongoing-order` 获取当前订单 ID
2. 如果没有进行中的订单,返回错误码 `5`
3. 默认先用 `confirm=false` 调用取消接口查询取消费
4. 当 `waiverFee=true` 时,脚本自动用相同参数并将 `confirm=true` 再次调用取消接口完成取消
5. 当 `waiverFee=false` 时,脚本返回取消费用并等待用户确认;agent 需在用户确认后调用 `cancel-order --confirm`
- 输入参数:
- `--confirm`:确认取消。仅在脚本已提示存在取消费用、且用户明确确认后使用
- `--reason`:取消原因,非必填
- 成功输出:
- 实际调用成功时返回 `{"mode": "cancelled", "result": ...}`
- 查询到取消费且需要用户确认时返回 `{"mode": "cancel_fee_required", "amount": <分>, "amount_yuan": <元>, "message": ... , "result": ...}`
- 返回数据约定(`result.body.data`):
- `amount`:取消费,单位分
- `waiverFee`:是否免取消费
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
- 没有进行中的订单:`5`
- 需要用户确认取消费:`6`
20. `query-order` <a id="cmd_query_order"></a>
- 作用:查询打车订单状态及司机、车辆信息
- 目标接口:`/mcp/open/tms/takecar/order/detail`
- 内部流程:
1. 先调用 `query-ongoing-order` 获取当前订单 ID
2. 如果有进行中的订单,使用获取到的订单 ID 查询详情
3. 如果没有进行中的订单,返回错误码 `5`
- 输入参数:无
- 成功输出:
- 实际调用成功时返回 `{"mode": "queried", "result": ...}`
- 返回数据约定(`result.body.data`):
- `orderId`:订单 ID
- `status`:订单状态枚举字符串
- `statusDesc`:订单状态可读描述(展示给用户)
- `acceptTime`:司机接单时间(字符串,格式 `YYYY-MM-DD HH:mm:ss`,未接单时为空)
- `cancelTime`:取消时间(字符串,未取消时为空)
- `driver.name`:司机姓名
- `driver.phone`:司机手机号(脱敏)
- `driver.avatar`:司机头像 URL
- `vehicle.brand`:车辆品牌
- `vehicle.color`:车辆颜色
- `vehicle.model`:车辆型号
- `vehicle.plate`:车牌号
- `vehicle.picture`:车辆图片 URL
- `position.startName`:出发地名称
- `position.startAddress`:出发地地址
- `position.startLat/startLng`:出发地经纬度
- `position.endName`:目的地名称
- `position.endAddress`:目的地地址
- `position.endLat/endLng`:目的地经纬度
- `estimateDistance`:预估路程(米)
- `estimateDuration`:预估时长(秒)
- `estimatePrice`:预估金额(元)
- `distance`:已行驶路程(米,行程结束前为 0)
- `cost.totalAmount`:实际费用(元,行程结束前为 0)
- `cost.refundAmount`:退款金额(元)
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
- 没有进行中的订单:`5`
21. `query-driver-location` <a id="cmd_query_driver_location"></a>
- 作用:在司机接单后、行程结束前,查询司机实时位置
- 目标接口:`/mcp/open/tms/takecar/passenger/order/driverPassengerDisplay`
- 内部流程:
1. 先调用 `query-ongoing-order` 获取当前订单 ID
2. 如果没有进行中的订单,返回错误码 `5`
3. 脚本从 `state.json` 读取 `routeId`、`trafficId` 参数(默认值均为 `0`)
4. 使用获取到的订单 ID 和参数查询司机位置
- 输入参数:无
- 成功输出:
- 实际调用成功时返回 `{"mode": "queried", "result": ...}`
- 返回数据约定(`result.body`,经过脚本转换后):
- `code`:响应状态码
- `message`:响应描述
- `data.orderStatus`:订单状态(详见下表)
- `data.eta`:预计到达时间,单位分钟(不可用时为 `null`)
- `data.eda`:预计到达距离,单位米(不可用时为 `null`)
- `data.driverLocation`:司机位置信息对象(不可用时为 `null`)
- `location`:司机当前坐标原始字符串,格式 `longitude,latitude`
- `latitude`:由 `location` 第 1 段解析得到(脚本当前实现)
- `longitude`:由 `location` 第 2 段解析得到(脚本当前实现)
- `direction`:司机行驶方向,单位度
- `locationTime`:位置上报时间戳,单位毫秒
- 订单状态值(orderStatus):
- `0`:预估中
- `1`:正在派单
- `2`:等待接驾(司机已接单)
- `3`:司机已到达
- `4`:行程开始(行程中)
- `5`:行程结束,计费结束
- `6`:订单待支付
- `7`:订单已支付
- `8`:司机已取消
- `9`:服务商取消
- `10`:系统取消
- `11`:乘客已取消
- `12`:取消费待支付
- `13`:取消费已支付
- `14`:订单待退款
- `15`:订单已退款
- `16`:预约成功(预约单)
- `21`:改派中
- 返回数据处理:
- 若 `data.resData.driverPassengerDisplay.driverLocationInfo` 中的 `eta` 和 `eda` 都不存在,脚本检查 `orderStatus`:
- 若 `orderStatus` 不属于 `2, 3, 4`,脚本返回错误结果并告知用户当前订单状态不支持查询司机位置
- 若属于,则返回空位置信息
- 退出码:
- 缺少 `TMS_SKILL_TOKEN`:`1`
- 脚本内未配置接口基地址:`2`
- HTTP 错误:`3`
- 网络错误:`4`
- 没有进行中的订单:`5`
### 环境变量
1. `TMS_SKILL_TOKEN`
- 用于请求鉴权
- 唯一数据来源:`~/.config/tms-takecar/env.json` 的 `token` 字段
## API 测试要求
- 每当新增或修改一个会发起 HTTP 请求的接口时,必须同步补充或更新对应的单元测试。
- 新增测试优先放在 [tests/test_tms_takecar_api.py](../tests/test_tms_takecar_api.py);如果是某个具体业务分支的补充,再按需补到对应的命令测试文件。
- 最低覆盖项必须包括:
- 缺少 token 时的退出路径。
- token 错误或接口返回 401/403 等鉴权失败时的错误路径。
- 业务成功路径。
- 可选参数、默认值和边界值场景。
- 响应为空、非 JSON、超时或网络错误等解析与传输边界。
- 所有 HTTP 测试都必须使用 mock,不得依赖真实网络请求。
FILE:references/short-cut-workflow.md
<a id="short-cut-main"></a>
## ⚡ 快捷叫车流程(总入口 + 快捷下单)
适用于以下用户诉求:
- "回家"
- "去公司"、"上班"
- "接孩子"
- 其他可映射为固定场景终点的快捷叫车表达
### 1. 全局约束
**⚠️必须先阅读以下文档**
- [异常处理流程](./references/error_handling.md)
- [接口契约](./references/api-contract.md)
- [常用地址 Schema](./addr-schema.md)
- [快捷叫车 Schema](./short-cut-schema.md)
- [订单状态模版](./references/state-schema.md)
- [设置快捷场景流程](./short-cut-setup-workflow.md)
#### 1.1 决策流程(整体强约束)
```markdown
用户表达进入快捷叫车
↓
场景是否可归一化为「回家 / 去公司 / 接孩子」?
├─ 否 → 回退到 [打车主流程](./references/takecar-workflow.md#takecar-main-flow)
└─ 是 → 调用 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) / [`addr-keys`](./api-contract.md#cmd_addr_keys)
↓
先调用 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) 再语义匹配,是否命中该场景记录?
├─ 否 → 进入 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main)
└─ 是 → 进入 [快捷下单主流程](#short-cut-flow)
↓
先调用 [`addr-keys`](./api-contract.md#cmd_addr_keys) 再语义匹配,是否命中终点地址别名?
├─ 否 → 进入 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main)
└─ 是 → 再调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) / [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 读取该场景记录的起点与偏好车型后执行快捷下单
```
强约束:
- ⚠️ 禁止跳步、禁止并行分支、禁止合并执行多个步骤。**命中已保存场景时走本流程;未命中时必须切换到设置快捷场景流程,且需要用户确认的部分不得跳过**
- 命中某一判断分支后,只允许执行该分支对应动作,不得同轮切换分支。
- ⚠️ 凡是文档标注“立即写回”的步骤,必须先通过 [`addr-upsert-value`](./api-contract.md#cmd_addr_upsert_value) 或 [`short-cut-upsert-value`](./api-contract.md#cmd_short_cut_upsert_value) 实际写入 `addr.json` / `short-cut.json`,再进入下一步。
- 任一步骤失败、字段缺失或契约校验不通过,立即回退到 [异常处理流程](./references/error_handling.md)。
1. 必须通过 [`addr-keys`](./api-contract.md#cmd_addr_keys)、[`addr-get-value`](./api-contract.md#cmd_addr_get_value)、[`short-cut-keys`](./api-contract.md#cmd_short_cut_keys)、[`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 读取 `addr.json` 与 `short-cut.json`,并优先使用其中的常用地址和快捷叫车配置。
2. `short-cut.json` 中的 `from`、`to` 字段表示 `addr.json` 中的地址别名(如 `家`、`公司`、`接孩子`),不是 `poi.name` 文本。
3. 进入询价或下单前,必须先调用 [`addr-keys`](./api-contract.md#cmd_addr_keys) 获取全部 key,再做语义匹配命中目标地址别名,最后调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 读取对应对象,并将该对象的地址字段填充到 `~/.config/tms-takecar/state.json`(`pickup`/`dropoff`)。
4. 地址匹配采用语义对齐,但读取流程必须严格遵守“先 [`*-keys`](./api-contract.md#cmd_addr_keys) 、后匹配、再 [`*-get-value`](./api-contract.md#cmd_addr_get_value)”;禁止跳过 keys 接口直接猜测 key。
5. 语义对齐示例(不限于):
- "公司" = "单位" = "上班"
- "爸妈家" = "父母家"
- "回家" = "家"
6. 匹配方法必须严谨:优先做语义归一化和同义词映射,禁止仅凭字符串包含、模糊正则或任意子串命中直接判定目标 key。
7. 若多个 key 都可能匹配,或无法稳定唯一命中,必须使用以下模版回复用户并等待用户选择后继续。
**❌ 禁止自行替用户做出选择,❌ 禁止回复模版以外的内容**
```markdown
1 - {short-cut.key[1]}
📍 {short-cut.key[1].from} -> {short-cut.key[1].to}
---
{n} - {short-cut.key[n]}
📍 {short-cut.key[n].from} -> {short-cut.key[n].to}
**请告诉我你具体是想用以上哪条路线。或者给我更精准的起点和终点。**
```
8. **状态文件初始化**:开启叫车流程前,—若 `~/.config/tms-takecar/state.json` 已存在则调用`delete-state`删除。
- 删除命令:
```bash
python3 ./scripts/tms_takecar.py delete-state
```
9. 地址相关请求(含上车点、下车点、模糊地名)必须先调用 `poi-search`,**未调用前禁止输出地点判断、禁止直接要求用户补充更具体地址**。
10. 地址相关信息若未命中记忆,必须切换到 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main),并复用「3. 地址检索」让用户确认。
11. 下单相关话术与字段,必须使用「4. 价格预估」与「5. 下单」的模板和约束。
### 2. 场景归一化规则
将用户输入先归一化为场景键,再执行后续流程:
| 用户表达 | 归一化场景键 | 默认终点记忆键 |
| --- | --- | --- |
| 回家、回去、回住处 | 回家 | 家 |
| 去公司、上班、去单位 | 去公司 | 公司 |
| 接孩子、去接娃、接小孩 | 接孩子 | 接孩子 |
若无法归一化到明确场景,回退到 [打车主流程](./references/takecar-workflow.md#takecar-main-flow)。
<a id="short-cut-poi-search"></a>
### 3. 地址检索
**流程**:
1. 调用 `poi-search` 搜索地点(必须传 `scene` 参数):
- 起点检索(上车点)使用 `scene=1`
- 终点检索(下车点)使用 `scene=2`
- 仅做普通地点搜索且不需要推荐上下车点时使用 `scene=0`
- 用户明确提到城市(如“北京的新街口”“上海人民广场”)时,优先填充 `--city-name`
- 表达歧义(如“四川饭店”“xx驻京办事处”)时,先追问城市,再调用 `poi-search`
- 用户未提及城市时,不传 `--city-name`,由脚本从 `~/.config/tms-takecar/env.json` 的 `resident_city` 回退
2. 判断是否需要用户选择:
**需要用户选择**:
- 搜索结果返回多个不同地点
- 多个同名但位置不同的地点
- 用户表述宽泛且结果差异大
- **核心原则**:不能100%确定用户表述的地点
**自动匹配**:
- 搜索结果只有1个地点
**当需要用户选择时,用`poi-search`填充以下模版回复用户**
**⚠️ 模版中的条目数严格按照`poi-search`返回值**
**❌ 禁止自行替用户做出选择**
```markdown
### 请在以下地址中选择您的地址
📍 **1. {items[0].name}: {items[0].address}**
🅿️ **{items[0].point_name}**
---
📍 **2. {items[1].name}: {items[1].address}**
🅿️ **{items[1].point_name}**
---
📍 **{n}. {items[n].name}: {items[n].address}**
🅿️ **{items[n].point_name}**
---
### 你可以回复:”1“ 或者 "{items[0].name}"
### 你也可以回复:”更多“或者告诉我更确切的地址
```
- 如果用户输入新的keyword则重新调用[`poi-search --scene 1`](./api-contract.md#cmd_poi_search) 搜索地点
- 如果用户选择翻页或者说:"更多"、"下一页"则调用[`poi-search --scene 1 --page-index <当前page-index++>`](./api-contract.md#cmd_poi_search) 进行翻页
- 直到用户通过回复序号,回复地点名称等方式从返回值中做出选择再进行下一步
- 用户确认后调用 [`select-poi`](./api-contract.md#cmd_select_poi) 收敛上车点:
```bash
python3 ./scripts/tms_takecar.py select-poi --scene 1 --poiid <用户选择的poiid>
```
<a id="short-cut-estimate"></a>
### 4. 价格预估
#### 4.1 车型筛选规则
从 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 返回的场景记录中提取 `preferred_car_type`,分为 **询价阶段可筛选** 和 **下单阶段参数** 两类:
**询价阶段可筛选的诉求**:
| 诉求类型 | 示例 | 筛选字段 | 筛选逻辑 |
|----------|------|----------|----------|
| 价格 | "30元以内" | `estimatePrice` | `estimatePrice / 100 <= 用户指定金额` |
| 车型 | "舒服一点的"、"后排宽敞一点的" | `rideType` | 根据语义匹配对应车型(舒适→rideType 4,豪华→rideType 6,商务→rideType 5 等) |
| 座位数 | "我们有5个人" | `riderDesc` | 从 `riderDesc` 中提取座位数,筛选 `可坐人数 >= 用户人数` 的车型。`riderDesc`未提及座位数时默认5座车 |
**下单阶段参数(询价阶段不做筛选)**:
| 诉求类型 | 示例 | 处理方式 |
|----------|------|----------|
| 接驾时间 | "快一点的" | 确认下单后作为 `userPreferLabels` 参数提供 |
| 接驾距离 | "找个最近的司机" | 确认下单后作为 `userPreferLabels` 参数提供 |
| 新能源/油车 | "想要个电车" | 确认下单后作为 `userPreferLabels` 参数提供 |
##### 4.2.1 筛选决策流程
```
询价结果返回 product 列表
↓
[`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 返回的当前场景记录中,`preferred_car_type` 是否包含可筛选偏好?
├─ 否 → 【默认列表】使用 defaultChecked=1 的车型的`priceEstimateKey`
└─ 是 → 对 product 列表按偏好条件过滤
↓
筛选结果是否为空?
├─ 否 → 保存筛选后的车型的 `priceEstimateKey`
└─ 是 → 保存defaultChecked=1 `priceEstimateKey`
```
**组合诉求示例**:
- "叫个30元以内的" → 按价格筛选,state.json保存筛选后列表
- "我想要个女司机" → 不可筛选,state.json保存默认列表
- "叫个30元以内的,要女司机" → state.json保存按价格筛选后列表
- "我们5个人,舒服一点的" → state.json保存按座位数(≥5人→需6座车)+ 车型(舒适/商务)筛选后列表
- "快一点来,30以内" → state.json保存按价格筛选后列表,"快一点"记录到下单阶段参数
#### 4.3 流程:
1. 通过快捷场景与常用地址接口读取数据:
- 先调用 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) 获取全部场景 key
- 对场景 key 做语义归一化匹配,唯一命中后调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 读取当前场景记录
- 从当前场景记录读取 `from`、`to`
- 对 `from`、`to` 分别调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 读取对应地址对象
2. 如果`from`调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value)返回空,执行[3. 地址检索](#short-cut-poi-search),由用户确认起点。
否则调用[`addr-sync-to-state {from} --scene 1`](./api-contract.md#cmd_addr_sync_to_state)同步起点数据。**⚠️这里的key是`from`**
3. 如果`to`调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value)返回空,进入 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main)。
否则调用[`addr-sync-to-state {to} --scene 2`](./api-contract.md#cmd_addr_sync_to_state)同步终点数据。**⚠️这里的key是`to`**
4. 调用 [`estimate-price`](./api-contract.md#cmd_estimate_price) 询价:
```bash
python3 ./scripts/tms_takecar.py estimate-price
```
5. 检查返回结果:
- 如果 `cityStatus` 不为 `1`(上线),告知用户该城市当前不支持叫车服务,并展示 `cityMessage`,结束流程
- 如果 `code` 不为 `0`,告知用户询价失败并展示 `message`,结束流程
6. 调用 [`estimate-price`](./api-contract.md#cmd_estimate_price) 成功后,读取 `~/.config/tms-takecar/state.json` 的 `estimate` 字段:
- `estimateKey`:询价返回的预估 key
- `distance`:预估里程
- `estimateTime`:预估时长
7. 根据用户诉求筛选车型(见上方 **车型筛选规则**),确定最终展示列表
8. 向用户展示车型和价格(见上方 **展示模版**),**❌ 禁止自行替用户做出选择,❌ 禁止回复模版以外的内容**
9. 用户确认选择后(包括重新筛选后再次确认),提取命中的 `priceEstimateKey` 列表,进入「5. 下单」(将 `--price-estimate-keys` 传给 [`create-order`](./api-contract.md#cmd_create_order),详见「5. 下单」)
<a id="short-cut-order"></a>
### 5. 下单
```
用户确认下单
↓
🔧 create-order(创建订单)
↓
下单成功?(data.orderId 不为空)
├─ 是 → 更新 state.json,告知用户下单成功
└─ 否 → 根据 code 和 message 告知用户失败原因
```
<a id="short-cut-place-order"></a>
#### 5.1 创建订单
1. 调用 [`create-order`](./api-contract.md#cmd_create_order) 创建订单:
```bash
python3 ./scripts/tms_takecar.py create-order --price-estimate-keys '<JSON数组>' [--user-prefer-labels '<JSON数组>']
```
2. 判断下单结果:
- `data.orderId` **不为空** → 下单成功
- `data.orderId` **为空**(包含空字符串或字符串 `null`)→ 下单失败
- `data.unfinishedOrder == true` → 有未完成订单,提示用户先完成或取消现有订单
#### 5.2 下单成功处理
1. 使用以下模版告知用户下单成功
**❌ 禁止回复模版以外的内容**
**回复模版**:
```markdown
订单已创建,正在为你安排车辆。
🧾 订单号:{orderId}
📍 {fromName} → {toName}
🚗 车型:{riderDesc}
💰 预估费用:约 {price} 元
⏱️ 行程预计:约 {estimateTime} 分钟
💬 发送「查询订单」可查看当前订单状态
```
#### 5.3 下单失败处理
- `data.unfinishedOrder == true`(有未完成订单)→ 告知用户存在未完成订单,需先完成或取消后再下单
- 其他失败 → 展示 `message` 告知用户失败原因,引导重试
#### 5.4 `userPreferLabels` 参数说明
从用户的原始打车诉求中自动解析并填充,**无需询问用户**:
| 枚举值 | 含义 | 触发关键词示例 |
|-----|----|---------|
| `1` | 接驾距离更近(地理位置上离起点更近) | "附近的"、"近一点"、"最近的" |
| `2` | 接驾最快(最快到达接驾点) | "最快的"、"快点来"、"尽快"、"5分钟内接驾" |
| `3` | 电车 | "电车"、"新能源"、"纯电" |
| `4` | 油车 | "油车"、"燃油车" |
**规则**:
- **必填字段**:每次调用都必须传入该字段
- 可多选,无偏好时传空数组 `[]`
- 禁止给用户透出 `userPreferLabels` 标签编号信息,只需透出文字信息即可
- **话术规范**:
- 包含偏好时,在下单过渡语中将用户原始需求文字透出。例如:用户说"要一辆附近的电车" → 过渡语:"正在帮你叫车,会尽可能为你安排一辆附近的电车"
- 不包含偏好时,过渡语直接说:"正在为你创建订单"
---
<a id="short-cut-flow"></a>
### 6. 快捷下单主流程(仅命中已保存场景)
#### 6.1 Step 0:读取记忆并做流程分流
1. 通过 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) / [`addr-keys`](./api-contract.md#cmd_addr_keys) 读取快捷场景与常用地址
2. 将用户表达归一化为场景键(回家 / 去公司 / 接孩子)。
3. 调用 [`short-cut-keys`](./api-contract.md#cmd_short_cut_keys) 获取全部场景 key,并做语义匹配判断是否存在对应场景记录:
- 不存在:立即切换到 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main)
- 存在:继续执行本章 6.2
4. 对命中的场景 key 调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value),再对其中终点地址别名调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 查找终点地址对象:
- 未命中:立即切换到 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main)
- 命中:继续执行本章 6.2
---
#### 6.2 Step 1:场景行存在,执行快捷下单
1. 调用 [`short-cut-get-value`](./api-contract.md#cmd_short_cut_get_value) 读取当前场景记录。
2. 读取该记录的 `from`、`to`(两者均为 `addr.json` 的地址别名)。
3. 分别调用 [`addr-get-value`](./api-contract.md#cmd_addr_get_value) 读取上述两个地址别名对应的对象,并调用[`addr-sync-to-state {from/to} --scene 1/2`](./api-contract.md#cmd_addr_sync_to_state)同步起点/终点数据。
4. 若`to`无对应地址对象或字段缺失,立即切换到 [设置快捷场景流程](./short-cut-setup-workflow.md#short-cut-setup-main)。
##### 6.2.1 Step 1.1:起点存在
1. 使用当前场景的 `起点` 与已确认终点进入叫车。
2. 判断该行 `偏好车型`:
- 有 `偏好车型`:
1. 先执行「4. 价格预估」,按场景表格中的车型偏好规则筛选可下单车型,如筛选后车型列表为空,则回退为 `defaultChecked=1` 的默认可下单车型。过程中无需询问用户。
2. 再执行「5. 下单」(#short-cut-order)下单。
- 无 `偏好车型`:
1. 调用 `estimate-price` 后,按 `defaultChecked=1` 的默认可下单车型。
2. 静默执行「5. 订单」(#short-cut-order) **⚠️不额外让用户再选车型**。
3. 下单结果仍按「5. 下单」(#short-cut-order) 成功/失败模板回复。
##### 6.2.2 Step 1.2:起点不存在
1. ⚠️ 如果用户的地址不是明确的poi名称,比如:“家”、“公司”、“xx家”,禁止直接用于keyword。需要向用户确认具体的地址作为下一步「3. 地址检索」(#short-cut-poi-search)的keyword
2. 先按「3. 地址检索」确认起点(调用 `poi-search --scene 1`,返回结果包含推荐上车点)。
3. 后续流程与 6.2.1 相同。
---
### 7. 订单相关处理
查询、取消、询问司机位置等订单处理流程,参考: [订单处理流程](./references/order-workflow.md)
FILE:references/addr-schema.md
# 常用地址文件 `~/.config/tms-takecar/addr.json`
用于保存快捷叫车和地址记忆流程依赖的常用地址数据。
## 1. 文件结构
顶层必须是 JSON object:
```json
{
"家": {
"name": "万科翡翠滨江",
"address": "深圳市南山区沙河西路3088号",
"poiid": "B0FFXXXX01",
"citycode": "440300",
"point_name": "万科翡翠滨江北门",
"point_longitude": 113.934528,
"point_latitude": 22.521867
},
"公司": {
"name": "腾讯滨海大厦",
"address": "深圳市南山区滨海大道33号",
"poiid": "B0FFXXXX02",
"citycode": "440300",
"point_name": "腾讯滨海大厦北门",
"point_longitude": 113.941245,
"point_latitude": 22.520031
}
}
```
## 2. 字段约束
- key:地址别名,只允许使用语义键,例如 `家`、`公司`、`小宝学校`。
- value:必须是用户最终确认后的 `poi_search` 单条返回结果对象。
- 禁止手填、猜测、裁剪字段,必须原样保留 `poi_search` 返回中用于下游流程的地址与推荐点字段。
## 3. 必需字段
每个地址对象至少包含以下字段:
| 字段 | 含义 |
| --- | --- |
| `name` | POI 名称 |
| `address` | POI 地址 |
| `poiid` | POI 唯一标识 |
| `citycode` | 城市编码 |
| `point_name` | 推荐上下车点名称 |
| `point_longitude` | 推荐点经度 |
| `point_latitude` | 推荐点纬度 |
## 4. 读写规则
1. 读取快捷场景前,必须先加载本文件并按别名取值。
2. 若别名不存在、字段缺失或对象损坏,必须回退到地址检索流程,重新调用 `poi_search`。
3. 写入时仅允许整条地址对象新增或整条覆盖,禁止字段级部分更新。
4. 下游写入 `state.json` 时,`pickup` / `dropoff` 的字段值必须直接来自本文件对应对象。
FILE:references/order-workflow.md
<a id="order-workflow-main"></a>
## 订单流程
适用于以下用户诉求:
- 查询订单
- 取消订单
- 查询司机位置
### 路由规则
- 用户要查看订单状态、订单进度、订单详情:进入 [订单查询流程](#order-query-flow)
- 用户要取消当前订单:进入 [取消订单流程](#order-cancel-flow)
- 用户要查看司机位置、司机实时位置、司机到哪了:进入 [司机位置查询流程](#driver-location-flow)
<a id="order-query-flow"></a>
### 订单查询流程
**步骤:**
1. 先告知用户将为其查询当前订单状态。
2. 调用工具查询订单详情:
```
python3 ./scripts/tms_takecar.py query-order
```
(脚本内部会自动查询进行中的订单 ID)
3. 解析 `query-order` 返回的 `result.body`:
- 退出码为 `5` 或 `code != 0` 或 HTTP 错误 → 按 [异常处理流程](./error_handling.md#error-handling-main) 处理
- `code == 0` → 提取 `data` 字段,按下方模板回复用户
4. **禁止编造**订单信息,所有字段均来自工具返回。
5. **❌ 禁止回复模版以外的内容**
**回复模板:**
```markdown
📋 订单状态:{statusDesc}
📍 {startName} → {endName}
🧾 订单号:{orderId}
🚗 车辆信息
{brand} {model}({color})
车牌:{plate}
👤 司机信息
{driver.name} {driver.phone}
💰 费用
预估:约 {estimatePrice} 元
```
**字段说明:**
- `statusDesc`:直接来自 `data.statusDesc`,是后端返回的可读描述。
- 司机和车辆字段来自 `data.driver` 和 `data.vehicle`;若这两组字段均为空(订单还未被接受),则省略「🚗 车辆信息」和「👤 司机信息」两行。
- `estimatePrice` 来自 `data.estimatePrice`;行程已结束时用 `data.cost.totalAmount` 替代,并改为「实际费用:{totalAmount} 元」。
<a id="order-cancel-flow"></a>
### 取消订单流程
1. 先告知用户将为其查询是否存在进行中的订单并进行取消。
2. 默认先调用取消订单工具查询取消费:
```
python3 ./scripts/tms_takecar.py cancel-order [--reason <reason>]
```
(脚本内部会自动查询进行中的订单 ID;若无订单则返回错误码 `5`)
3. 解析取消结果:
- 退出码为 `5` 或 HTTP 错误 → 按 [异常处理流程](./error_handling.md#error-handling-main) 处理。
- CLI 返回 `mode == cancel_fee_required` → 按「待确认模板」告知用户取消费用,并等待用户确认。
- CLI 返回 `mode == cancelled` → 按「免取消费自动取消成功模板」回复用户。
- 其他情况视为取消失败,按「取消失败模板」回复用户,并按 [异常处理流程](./error_handling.md#error-handling-main) 处理。
4. 用户明确确认承担取消费用后,再调用:
```
python3 ./scripts/tms_takecar.py cancel-order --confirm [--reason <reason>]
```
5. 解析第二次取消结果:
- `code != 0` 或 HTTP 错误 → 按 [异常处理流程](./error_handling.md#error-handling-main) 处理。
- `code == 0` → 按「确认后取消成功模板」回复用户。
6. **❌ 禁止回复模版以外的内容**
**待确认模板:**
```markdown
🚕 已查询到进行中的订单,请确认是否取消:
- 💰 取消费用:{amount_yuan} 元
如需继续取消,我将按你的确认执行。
```
**免取消费自动取消成功模板:**
```markdown
✅ 订单已取消
- 📣 结果:本次取消无需支付取消费,已为你直接取消
```
**确认后取消成功模板:**
```markdown
✅ 订单取消成功
- 📣 结果:已按你的确认完成取消
- 💰 取消费用:{amount_yuan} 元
```
**取消失败模板:**
```markdown
❌ 订单取消失败,你可以到**腾讯出行服务小程序**进行进一步操作
- 📣 原因:{message}
```
说明:
- 待确认模板中的 `amount_yuan` 来自第一次 `cancel-order` 返回的 `amount / 100`。
- 默认第一次 `cancel-order` 直接返回 `mode == cancelled` 时,表示无需支付取消费,agent 不需要再次向用户确认。
- 成功模板不再依赖旧字段 `successMessage`,以 `cancel-order` 或 `cancel-order --confirm` 返回成功为准。
- **❌ 禁止回复模版以外的内容**
<a id="driver-location-flow"></a>
### 司机位置查询流程
1. 先告知用户将为其查询司机位置。
2. 调用司机位置接口:
```bash
python3 ./scripts/tms_takecar.py query-driver-location
```
(脚本内部会自动查询进行中的订单 ID;若无订单则返回错误码 `5`)
3. 解析 [`query-driver-location`](./api-contract.md#cmd_query_driver_location) 的 `result.body`:
- 退出码为 `5` 或 `code != 0` 或 HTTP 错误 → 按 [异常处理流程](./error_handling.md#error-handling-main) 处理。
- `code == 0` 且 `data.driverLocation` 非空 → 已成功获取司机位置,按下方模板回复用户
- `code == 0` 且 `data.driverLocation` 为空 → 检查 `data.orderStatus`:
- 若 `orderStatus` 属于 `2`(等待接驾)、`3`(司机已到达)、`4`(行程开始)→ 返回相应状态说明
- 若 `orderStatus` 不属于以上状态 → 按订单状态告知用户无法查询司机位置(如订单已完成、已取消等)
4. **❌ 禁止回复模版以外的内容**
**司机位置回复模板:**
```markdown
🚕 司机接驾中
- 📋 订单状态:{orderStatus描述}
- 📏 司机距离:{eda} 米
- ⏱️ 预计到达时间:约 {eta} 分钟
```
**orderStatus 状态描述对照:**
| orderStatus | 描述 | 是否可查询司机位置 |
|---|---|---|
| 2 | 等待接驾(司机已接单) | ✅ 可查询 |
| 3 | 司机已到达 | ✅ 可查询 |
| 4 | 行程开始(行程中) | ✅ 可查询 |
| 其他 | 订单未派单/已完成/已取消等 | ❌ 无法查询 |
**字段说明:**
- `orderStatus`:来自 [`query-driver-location`](./api-contract.md#cmd_query_driver_location) 返回的 `data.orderStatus`
- `eda`、`eta`:来自 `query-driver-location` 的 `data.eda`(单位米)、`data.eta`(单位分钟)
- `location`:来自 `query-driver-location` 的 `data.driverLocation.location`,格式 `longitude,latitude`;模板中的 `{location_longitude}` 和 `{location_latitude}` 由该字段拆分得到
- `locationTime`、`direction` 可用于补充说明,但不要求在模板中强制展示
FILE:references/takecar-workflow.md
<a id="takecar-main-flow"></a>
## 🚗 打车流程(必须严格按顺序执行)
### 全局执行约束
**的工具调用规则优先级高于通用对话习惯**
❌ 禁止在 workflow 约束的场景外自由回答地址、价格、订单等信息
✅ 只能在工具调用后根据结果进行正常对话
**⚠️必须先阅读以下文档**
- [订单状态模版](./references/state-schema.md)
- [异常处理流程](./references/error_handling.md)
- [接口契约](./references/api-contract.md)
1. 每次调用工具前,必须先对照 [api_contract.md](./api_contract.md) 校验参数名和字段。
2. 若用户拒绝提供当前位置,统一回复:不提供当前位置信息则无法满足你的需求。
3. **状态文件初始化**:开启叫车流程前,若 `~/.config/tms-takecar/state.json` 已存在则调用[`delete-state`](./api-contract.md#cmd_delete_state)删除。
- 删除命令:
```bash
python3 ./scripts/tms_takecar.py delete-state
```
4. 地址相关请求(含上车点、下车点、模糊地名)必须先调用 [`poi-search`](./api-contract.md#cmd_poi_search),**未调用前禁止输出地点判断、禁止直接要求用户补充更具体地址**。
5. [`poi-search`](./api-contract.md#cmd_poi_search) 的 `region` 填充规则:
- 用户明确提到城市(如“北京的新街口”“上海人民广场”)时,优先把该城市填入 `--city-name`
- 表达歧义(如“四川饭店”“xx驻京办事处”)时,先追问用户目的城市,再调用 `poi-search`
- 用户未提及城市时,不传 `--city-name`,由脚本从 `~/.config/tms-takecar/env.json` 的 `resident_city` 回退
6. 禁止合并处理上下车点,必须按`### ⚠️ PLAN 计划与流程顺序强制要求`顺序逐步完成
7. 输出前必须自检:若本轮处于地址确认流程且未完成工具调用,只允许输出过渡语并立刻调用工具,不得输出自由解释。
8. 若违反本节规则,本轮回复视为无效,需要回到"先工具后回复"的流程重新执行。
9. 下游接口参数必须从 `~/.config/tms-takecar/state.json` 中读取上游结果填充,参数映射见 [state-schema.md — 下游接口参数取值映射](./state-schema.md#下游接口参数取值映射)。
10. **经纬度取值规则**:`estimate-price` 和 `create-order` 的经纬度参数必须取自候选项中的 `point_longitude` / `point_latitude`(即 `poi-search --scene 1/2` 返回的推荐点坐标)。
### ⚠️ PLAN 计划与流程顺序强制要求
**启动新的叫车流程,必须按以下顺序逐步制定计划并完成, 不能跳过或者合并**:
```
第一步:确定上车点(完成后才能进入第二步)
↓
第二步:确定下车点(完成后才能进入第三步)
↓
第三步:询价与车型确认
↓
第四步:下单
```
**错误示例**:
- ❌ "从武汉大学去机场" → 同时搜索"武汉大学"和"机场"
- ❌ "去机场" → 同时获取位置和搜索"机场"
- ❌ 未调用 [`poi-search`](./api-contract.md#cmd_poi_search) 直接回复:
"上车点'华侨城'有点泛,请你先补充更具体位置(小区/门牌/地铁站)"
**正确做法**:
- ✅ "从武汉大学去机场" → 先搜索"武汉大学" → 再搜索"机场"
- ✅ "去机场" → 先获取位置 → 再搜索"机场"
- ✅ "华侨城"(上车点模糊)→ 先调用 [`poi-search(keyword=华侨城)`](./api-contract.md#cmd_poi_search) 返回候选列表,再按模版让用户选择
---
<a id="takecar-step-1-pickup"></a>
### 第一步:确定上车点
**流程**:
1. 调用 [`poi-search --scene 1`](./api-contract.md#cmd_poi_search) 搜索地点并返回推荐上车点
2. 判断是否需要用户选择:
**需要用户选择**:
- 搜索结果返回多个不同地点
- 多个同名但位置不同的地点
- 用户表述宽泛且结果差异大
- **核心原则**:不能100%确定用户表述的地点
**自动匹配**:
- 搜索结果只有1个地点
**当需要用户选择时,用[`poi-search --scene 1`](./api-contract.md#cmd_poi_search)填充以下模版回复用户**
**⚠️ 模版中的条目数严格按照[`poi-search --scene 1`](./api-contract.md#cmd_poi_search)返回值**
**❌ 禁止自行替用户做出选择**
```markdown
### 请在以下地址中选择您的上车点
📍 **1. {items[0].name}: {items[0].address}**
🅿️ **{items[0].point_name}**
---
📍 **2. {items[1].name}: {items[1].address}**
🅿️ **{items[1].point_name}**
---
📍 **{n}. {items[n].name}: {items[n].address}**
🅿️ **{items[n].point_name}**
---
### 你可以回复:”1“ 或者 "{items[0].name}"
### 你也可以回复:”更多“或者告诉我更确切的地址
```
- 如果用户输入新的keyword则重新调用[`poi-search --scene 1`](./api-contract.md#cmd_poi_search) 搜索地点
- 如果用户选择翻页或者说:"更多"、"下一页"则调用[`poi-search --scene 1 --page-index <当前page-index++>`](./api-contract.md#cmd_poi_search) 进行翻页
- 直到用户通过回复序号,回复地点名称等方式从返回值中做出选择再进行下一步
3. 用户确认后调用 [`select-poi`](./api-contract.md#cmd_select_poi) 收敛上车点:
```bash
python3 ./scripts/tms_takecar.py select-poi --scene 1 --poiid <用户选择的poiid>
```
4. `pickup` 在 state 中仅保留 1 条候选(字段结构与 `poi-search` 返回 item 一致)。
---
<a id="takecar-step-2-dropoff"></a>
### 第二步:确定下车点
**⚠️ 前置条件**:必须在第一步上车点完全确定后才能开始
如果有缺失回到`### 第一步:确定上车点`
**流程**:
1. 调用 [`poi-search --scene 2`](./api-contract.md#cmd_poi_search) 搜索地点并返回推荐下车点
2. 判断是否需要用户选择:
**需要用户选择**:
- 搜索结果返回多个不同地点
**自动匹配**:
- 搜索结果只有1个地点
**当需要用户选择时,用[`poi-search --scene 2`](./api-contract.md#cmd_poi_search)填充以下模版回复用户**
**⚠️ 模版中的条目数严格按照[`poi-search --scene 2`](./api-contract.md#cmd_poi_search)返回值**
**❌ 禁止自行替用户做出选择**
```markdown
### 请在以下地址中选择您的下车点
📍 **1. {items[0].name}: {items[0].address}**
🅿️ **{items[0].point_name}**
---
📍 **2. {items[1].name}: {items[1].address}**
🅿️ **{items[1].point_name}**
---
📍 **{n}. {items[n].name}: {items[n].address}**
🅿️ **{items[n].point_name}**
---
### 你可以回复:”1“ 或者 "{items[0].name}"
### 你也可以回复:”更多“或者告诉我更确切的地址
```
- 如果用户输入新的keyword则重新调用[`poi-search --scene 2`](./api-contract.md#cmd_poi_search) 搜索地点
- 如果用户选择翻页或者说:"更多"、"下一页"则调用[`poi-search --scene 2 --page-index <当前page-index++>`](./api-contract.md#cmd_poi_search) 进行翻页
- 直到用户通过回复序号,回复地点名称等方式从返回值中做出选择再进行下一步
直到用户从返回值中做出选择再进行下一步
3. 用户确认后调用 [`select-poi`](./api-contract.md#cmd_select_poi) 收敛下车点:
```bash
python3 ./scripts/tms_takecar.py select-poi --scene 2 --poiid <用户选择的poiid>
```
---
<a id="takecar-step-3-estimate"></a>
### 第三步:询价与车型确认
**⚠️ 前置条件**:必须在第二步下车点完全确定后才能开始
#### 回复模版
**展示模版**:
```markdown
### 📋 行程预估
- 预估里程:{distance/1000:.1f} 公里
- 预估时长:{estimateTime/60} 分钟
### 🚗 可选车型
| 序号 | 车型 | 运力商 | 预估价格 | 优惠 |
|------|------|--------|----------|------|
| 1 | {riderInfo.riderDesc} | {sp.name} | ¥{estimatePrice/100:.2f} | {优惠信息} |
| 2 | ... | ... | ... | ... |
### 请回复"确认叫车"呼叫以上全部车型,或回复序号只呼叫部分车型(如"1,3")
```
**优惠信息展示规则**:
- 有 `discountAmount > 0` 时展示:`减¥{discountAmount/100:.2f}`
- 有 `discountPercentage > 0` 且 `discountPercentage < 100` 时展示:`{discountPercentage/10}折`
- 无优惠时展示:`-`
#### 车型筛选规则
从用户原始叫车诉求中提取偏好,分为 **询价阶段可筛选** 和 **下单阶段参数** 两类:
**询价阶段可筛选的诉求**:
| 诉求类型 | 示例 | 筛选字段 | 筛选逻辑 |
|----------|------|----------|----------|
| 价格 | "30元以内" | `estimatePrice` | `estimatePrice / 100 <= 用户指定金额` |
| 车型 | "舒服一点的"、"后排宽敞一点的" | `rideType` | 根据语义匹配对应车型(舒适→rideType 4,豪华→rideType 6,商务→rideType 5 等) |
| 座位数 | "我们有5个人" | `riderDesc` | 从 `riderDesc` 中提取座位数,筛选 `可坐人数 >= 用户人数` 的车型。`riderDesc`未提及座位数时默认5座车 |
**下单阶段参数(询价阶段不做筛选)**:
| 诉求类型 | 示例 | 处理方式 |
|----------|------|----------|
| 接驾时间 | "快一点的" | 确认下单后作为 `userPreferLabels` 参数提供 |
| 接驾距离 | "找个最近的司机" | 确认下单后作为 `userPreferLabels` 参数提供 |
| 新能源/油车 | "想要个电车" | 确认下单后作为 `userPreferLabels` 参数提供 |
**车型选择规则**:
- 列表只展示本轮可下单车型:筛选模式仅展示筛选后的车型;默认模式仅展示 `defaultChecked=1` 的车型
- 不展示全量车型,也不需要在列表中使用 `✅` 标记
- 用户回复"确认叫车"、"叫车"、"确认"等 → 使用当前展示列表中的全部车型下单
- 用户回复序号(如"1,3") → 仅使用对应序号的车型下单
- 用户表达新的筛选诉求(如"太贵了,有没有30以内的"、"换个大一点的车") → **不重新调用 [`estimate-price`](./api-contract.md#cmd_estimate_price)**,从本次询价的原始 `product` 列表中按新诉求重新筛选并展示
- 用户修改车型诉求后的每次回复,必须先完整输出一次上方「展示模版」的重筛车型列表,再补充说明文字;禁止只输出文字说明而不展示列表
- 若重筛结果为空,也必须先按「展示模版」展示默认车型列表(`defaultChecked=1`),再说明“未找到完全符合条件的车型”并引导调整条件
##### 筛选决策流程
```
询价结果返回 product 列表
↓
用户诉求中是否包含可筛选偏好?
├─ 否 → 【默认列表】展示 defaultChecked=1 的车型
└─ 是 → 对 product 列表按偏好条件过滤
↓
筛选结果是否为空?
├─ 否 → 【筛选列表】展示筛选后的车型
└─ 是 → 【兜底】礼貌告知用户未找到符合条件的车型,
展示 defaultChecked=1 的默认车型列表,
引导用户调整条件(如调高预算或更换车型)
↓
用户诉求中是否包含不可筛选/不支持的偏好?(如"女司机"、"指定品牌"等)
├─ 否 → 正常展示
└─ 是 → 在展示车型列表同时,礼貌告知"部分诉求暂时无法满足"
```
**组合诉求示例**:
- "叫个30元以内的" → 按价格筛选,展示筛选后列表
- "我想要个女司机" → 不可筛选,展示默认列表 + 告知暂不支持
- "叫个30元以内的,要女司机" → 按价格筛选展示 + 告知"女司机"暂不支持
- "我们5个人,舒服一点的" → 按座位数(≥5人→需6座车)+ 车型(舒适/商务)筛选
- "快一点来,30以内" → 按价格筛选展示,"快一点"记录到下单阶段参数
#### 流程:
1. 调用 [`estimate-price`](./api-contract.md#cmd_estimate_price) 询价:
```bash
python3 ./scripts/tms_takecar.py estimate-price
```
2. 检查返回结果:
- 如果 `cityStatus` 不为 `1`(上线),告知用户该城市当前不支持叫车服务,并展示 `cityMessage`,结束流程
- 如果 `code` 不为 `0`,告知用户询价失败并展示 `message`,结束流程
3. 调用 [`estimate-price`](./api-contract.md#cmd_estimate_price) 成功后,读取 `~/.config/tms-takecar/state.json` 的 `estimate` 字段:
- `estimateKey`:询价返回的预估 key
- `distance`:预估里程
- `estimateTime`:预估时长
4. 根据用户诉求筛选车型(见上方 **车型筛选规则**),确定最终展示列表
5. 向用户展示车型和价格(见上方 **展示模版**),**❌ 禁止自行替用户做出选择,❌ 禁止回复模版以外的内容**
6. 用户确认选择后(包括重新筛选后再次确认),提取命中的 `priceEstimateKey` 列表,进入第四步下单(将 `--price-estimate-keys` 传给 [`create-order`](./api-contract.md#cmd_create_order),详见「第四步:下单」)
---
<a id="takecar-step-4-order"></a>
### 第四步:下单
**⚠️ 前置条件**:必须在第三步询价与车型确认完成后才能开始。
#### 决策流程图
```
用户确认下单
↓
🔧 [`create-order`](./api-contract.md#cmd_create_order)(创建订单)
↓
下单成功?(data.orderId 不为空)
├─ 是 → 更新 state.json,告知用户下单成功
└─ 否 → 根据 unfinishedOrder、code 和 message 告知用户失败原因
```
#### 4.1 创建订单
1. 调用 [`create-order`](./api-contract.md#cmd_create_order) 创建订单:
```bash
python3 ./scripts/tms_takecar.py create-order --price-estimate-keys '<JSON数组>' [--user-prefer-labels '<JSON数组>']
```
2. 判断下单结果:
- `data.orderId` **不为空** → 下单成功
- `data.orderId` **为空**(包含空字符串或字符串 `null`)→ 下单失败
- `data.unfinishedOrder == true` → 有未完成订单,提示用户先完成或取消现有订单
#### 4.2 下单成功处理
1. 使用以下模版告知用户下单成功
**❌ 禁止回复模版以外的内容**
**回复模版**:
```markdown
订单已创建,正在为你安排车辆。
🧾 订单号:{orderId}
📍 {fromName} → {toName}
🚗 车型:{riderDesc}
💰 预估费用:约 {price} 元
⏱️ 行程预计:约 {estimateTime} 分钟
💬 发送「查询订单」可查看当前订单状态
```
#### 4.3 下单失败处理
- `data.unfinishedOrder == true`(有未完成订单)→ 告知用户存在未完成订单,需先完成或取消后再下单
- 其他失败 → 展示 `message` 告知用户失败原因,引导重试
#### `userPreferLabels` 参数说明
从用户的原始打车诉求中自动解析并填充,**无需询问用户**:
| 枚举值 | 含义 | 触发关键词示例 |
|-----|----|---------|
| `1` | 接驾距离更近(地理位置上离起点更近) | "附近的"、"近一点"、"最近的" |
| `2` | 接驾最快(最快到达接驾点) | "最快的"、"快点来"、"尽快"、"5分钟内接驾" |
| `3` | 电车 | "电车"、"新能源"、"纯电" |
| `4` | 油车 | "油车"、"燃油车" |
**规则**:
- **必填字段**:每次调用都必须传入该字段
- 可多选,无偏好时传空数组 `[]`
- 禁止给用户透出 `userPreferLabels` 标签编号信息,只需透出文字信息即可
- **话术规范**:
- 包含偏好时,在下单过渡语中将用户原始需求文字透出。例如:用户说"要一辆附近的电车" → 过渡语:"正在帮你叫车,会尽可能为你安排一辆附近的电车"
- 不包含偏好时,过渡语直接说:"正在为你创建订单"
### 订单相关处理
查询、取消、询问司机位置等订单处理流程,参考: [订单处理流程](./references/order-workflow.md)
### 模版回复规则
**使用markdown模版回复用户时必须遵守以下规则**
- 当要求输出特定模版时,必须严格按照模版格式输出,禁止添加额外信息或修改模版结构。
- 模版中的字段必须保留,禁止删除或改名。
- 模版中的换行和标点符号必须保留,禁止修改为其他格式。
- 多条数据展示时,禁止合并成一句话或列表,必须保持模版的层级结构。
FILE:references/state-schema.md
# 订单状态文件 `~/.config/tms-takecar/state.json`
> 每次进入叫车流程时创建,用于临时存储当前订单状态。
## JSON 模版
```json
{
"pickup": [],
"dropoff": [],
"estimate": null,
"userPreferLabels": [],
"orderId": null
}
```
## 字段说明
### `pickup` / `dropoff`
- 类型:数组
- 元素结构:与 `poi-search` 的 `result.body.items[]` 单项字段保持一致
- 元素字段:
- `name`
- `address`
- `longitude`
- `latitude`
- `poiid`
- `citycode`
- `point_name`
- `point_longitude`
- `point_latitude`
写入规则:
1. 每次执行 `poi-search --scene 1/2`,脚本会向对应数组追加候选。
2. 追加时按 `poiid` 去重:已存在则覆盖,不存在则新增。
3. `scene=0` 不写 state。
收敛规则:
1. 用户确定具体地点后,必须执行 `select-poi --poiid <id> --scene <1|2>`。
2. `select-poi` 成功后,对应数组只保留 1 条(被选中的 `poiid`),其余删除。
### `estimate`
- 来源:`estimate-price` 成功后写入
- 关键字段:
- `estimate.estimateKey`
- `estimate.distance`
- `estimate.estimateTime`
- `estimate.products[]`
- `estimate.userPreferLabels`
### `orderId`
- 来源:`create-order` 成功后写入
## 下游请求体取值映射(脚本自动构造)
### `estimate-price`
前置条件:
1. `pickup` 数组长度必须为 1(已完成 `select-poi --scene 1`)。
2. `dropoff` 数组长度必须为 1(已完成 `select-poi --scene 2`)。
取值映射:
- `cityCode` <- `pickup[0].citycode`
- `destCityCode` <- `dropoff[0].citycode`
- `fromLat` <- `pickup[0].point_latitude`
- `fromLng` <- `pickup[0].point_longitude`
- `fromName` <- `pickup[0].name`
- `fromAddress` <- `pickup[0].address`
- `toId` <- `dropoff[0].poiid`
- `toLat` <- `dropoff[0].point_latitude`
- `toLng` <- `dropoff[0].point_longitude`
- `toName` <- `dropoff[0].name`
- `toAddress` <- `dropoff[0].address`
### `create-order`
前置条件:
1. `pickup` 和 `dropoff` 均已收敛为单条候选。
2. `estimate.estimateKey` 非空。
3. `estimate.products` 中至少一项 `userSelect=1`。
取值映射:
- 地址相关字段同 `estimate-price`
- `estimateKey` <- `estimate.estimateKey`
- `productStr` <- 由 `estimate.products` 中 `userSelect=1` 自动构造
- `userPreferLabels` <- `estimate.userPreferLabels`
## 联动清理规则
- `select-poi` 成功后统一清空:
- `estimate = null`
- `userPreferLabels = []`
- `orderId = null`
- `create-order --price-estimate-keys` 若提供该参数,会在发起请求前更新 `estimate.products[].userSelect`;若同时提供 `--user-prefer-labels`,也会更新 `estimate.userPreferLabels`。两者均为可选,不传时保留 state 原值。
FILE:scripts/tms_takecar_api.py
import json
import random
import time
import urllib.error
import urllib.request
DEFAULT_API_BASE = "https://test.weixin.go.qq.com"
DEFAULT_POI_SUGGESTION_PATH = "/mcp/open/tms/lbs/suggestion"
DEFAULT_GET_ON_POINTS_PATH = "/mcp/open/tms/recommend/taxi/getOnPoints"
DEFAULT_GET_DROP_OFF_POINT_PATH = "/mcp/open/tms/recommend/taxi/getDropOffPointV2"
DEFAULT_ESTIMATE_PRICE_PATH = "/mcp/open/tms/takecar/estimate/price"
DEFAULT_CREATE_ORDER_PATH = "/mcp/open/tms/takecar/risk/verify/book/order"
DEFAULT_QUERY_ONGOING_ORDER_PATH = "/api/v1/query-ongoing-order"
DEFAULT_CANCEL_ORDER_PATH = "/mcp/open/tms/takecar/cancel/order"
DEFAULT_QUERY_ORDER_PATH = "/mcp/open/tms/takecar/order/detail"
DEFAULT_QUERY_DRIVER_LOCATION_PATH = "/api/v1/query-driver-location"
DEFAULT_WX_APP_ID = "wx65cc950f42e8fff1"
DEFAULT_HTTP_TIMEOUT_SECONDS = 60.0
INVALID_TOKEN_ERR_CODES = {10, 35}
def generate_timestamp_ms() -> int:
return int(time.time() * 1000)
def generate_seq_id(timestamp_ms: int) -> str:
# Keep seqId numeric and high-entropy for request tracing.
random_suffix = random.randint(100000, 999999)
return f"{timestamp_ms}{random_suffix}"
def build_request_payload(payload: dict, token: str) -> dict:
timestamp_ms = generate_timestamp_ms()
request_payload = {
"seqId": generate_seq_id(timestamp_ms),
"timestamp": timestamp_ms,
"wxAppId": DEFAULT_WX_APP_ID,
"token": token,
}
request_payload.update(payload)
return request_payload
def is_invalid_token_error(body) -> bool:
if not isinstance(body, dict):
return False
err_code = body.get("errCode")
if isinstance(err_code, str) and err_code.isdigit():
err_code = int(err_code)
return err_code in INVALID_TOKEN_ERR_CODES
def post_request(
base_url: str,
trips_path: str,
headers: dict,
payload: dict,
token: str = "",
urlopen_func=None,
timeout_seconds: float = DEFAULT_HTTP_TIMEOUT_SECONDS,
) -> dict:
if urlopen_func is None:
urlopen_func = urllib.request.urlopen
url = base_url.rstrip("/") + "/" + trips_path.strip("/")
request_payload = build_request_payload(payload, token)
request = urllib.request.Request(
url=url,
data=json.dumps(request_payload, ensure_ascii=False).encode("utf-8"),
headers=headers,
method="POST",
)
with urlopen_func(request, timeout=timeout_seconds) as response:
body = response.read().decode("utf-8")
if not body.strip():
return {"status": response.status, "body": None}
try:
return {"status": response.status, "body": json.loads(body)}
except json.JSONDecodeError:
return {"status": response.status, "body": body}
def run_json_api_command(
environ: dict,
stdout,
stderr,
payload: dict,
api_path: str,
success_mode: str,
load_token_func,
build_headers_func,
write_json_func,
default_api_base: str,
result_transform=None,
urlopen_func=None,
) -> int:
if urlopen_func is None:
urlopen_func = urllib.request.urlopen
token = load_token_func(environ)
if not token:
stderr.write("Token is not configured. Run: save-token <token>\n")
return 1
base_url = default_api_base.strip()
if not base_url:
write_json_func(
stdout,
{
"mode": "blocked",
"reason": "DEFAULT_API_BASE is not configured",
"payload": payload,
},
)
return 2
headers = build_headers_func(token)
try:
result = post_request(
base_url,
api_path,
headers,
payload,
token=token,
urlopen_func=urlopen_func,
timeout_seconds=DEFAULT_HTTP_TIMEOUT_SECONDS,
)
except urllib.error.HTTPError as exc:
error_body = exc.read().decode("utf-8", errors="replace")
try:
error_body_json = json.loads(error_body)
except json.JSONDecodeError:
error_body_json = None
if is_invalid_token_error(error_body_json):
write_json_func(
stderr,
{
"mode": "error",
"status": exc.code,
"body": error_body,
},
)
return 1
write_json_func(
stderr,
{
"mode": "error",
"status": exc.code,
"body": error_body,
},
)
return 3
except urllib.error.URLError as exc:
write_json_func(
stderr,
{
"mode": "error",
"reason": str(exc),
"hint": "Request timed out after 60s",
},
)
return 4
if result_transform is not None:
result = result_transform(result)
result_body = result.get("body") if isinstance(result, dict) else None
if is_invalid_token_error(result_body):
write_json_func(
stderr,
{
"mode": "error",
"reason": "token is missing or invalid",
"body": result_body,
},
)
return 1
write_json_func(stdout, {"mode": success_mode, "result": result})
return 0
FILE:scripts/tms_takecar.py
#!/usr/bin/env python3
import argparse
import json
import math
import os
import platform
import re
import sys
from typing import List, Optional, Tuple
import urllib.parse
import urllib.request
from tms_takecar_api import (
DEFAULT_HTTP_TIMEOUT_SECONDS,
DEFAULT_WX_APP_ID,
generate_seq_id,
generate_timestamp_ms,
is_invalid_token_error,
post_request,
run_json_api_command,
)
DEFAULT_API_BASE = "https://weixin.go.qq.com"
DEFAULT_POI_SUGGESTION_PATH = "/mcp/open/tms/lbs/suggestion"
DEFAULT_GET_ON_POINTS_PATH = "/mcp/open/tms/recommend/taxi/getOnPoints"
DEFAULT_GET_DROP_OFF_POINT_PATH = "/mcp/open/tms/recommend/taxi/getDropOffPointV2"
DEFAULT_ESTIMATE_PRICE_PATH = "/mcp/open/tms/takecar/estimate/price"
DEFAULT_CREATE_ORDER_PATH = "/mcp/open/tms/takecar/risk/verify/book/order"
DEFAULT_QUERY_ONGOING_ORDER_PATH = "/mcp/open/tms/takecar/order/ongoing"
DEFAULT_CANCEL_ORDER_PATH = "/mcp/open/tms/takecar/cancel/order"
DEFAULT_QUERY_ORDER_PATH = "/mcp/open/tms/takecar/order/detail"
DEFAULT_QUERY_DRIVER_LOCATION_PATH = "/mcp/open/tms/takecar/passenger/order/driverPassengerDisplay"
DEFAULT_STATIC_MAP_PATH = "/mcp/open/tms/lbs/staticMap"
DEFAULT_STATIC_MAP_SIZE = "800*600"
DEFAULT_STATIC_MAP_SCALE = 2
DEFAULT_STATIC_MAP_MAPTYPE = "roadmap"
SCENE_TO_POLICY = {0: 0, 1: 10, 2: 11}
ALLOWED_RIDE_TYPES = {1, 2, 3, 4, 5, 6, 7}
STATIC_MAP_MARKER_PRESETS = {
"起": {"color": "red", "label": "起"},
"终": {"color": "green", "label": "终"},
"P": {"color": "blue", "label": "P"},
}
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ASSETS_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), "assets")
TOKEN_CONFIG_DIR = os.path.join(os.path.expanduser("~"), ".config", "tms-takecar")
TOKEN_CONFIG_FILE = os.path.join(TOKEN_CONFIG_DIR, "token")
ENV_CONFIG_FILE = os.path.join(TOKEN_CONFIG_DIR, "env.json")
PREFERENCE_FILE_PATH = os.path.join(ASSETS_DIR, "preference.md")
STATE_FILE_PATH = os.path.join(TOKEN_CONFIG_DIR, "state.json")
ADDR_FILE_PATH = os.path.join(TOKEN_CONFIG_DIR, "addr.json")
SHORT_CUT_FILE_PATH = os.path.join(TOKEN_CONFIG_DIR, "short-cut.json")
def build_default_env_config() -> dict:
return {
"env": {
"python": False,
},
"resident_city": "",
"token": "",
"updated_at": "",
}
def build_default_state() -> dict:
return {
"pickup": [],
"dropoff": [],
"estimate": None,
"userPreferLabels": [],
"orderId": None,
}
def _ensure_state_parent_dir(state_file: str) -> None:
parent = os.path.dirname(state_file)
if parent:
os.makedirs(parent, exist_ok=True)
def _ensure_env_parent_dir(env_file: str) -> None:
parent = os.path.dirname(env_file)
if parent:
os.makedirs(parent, exist_ok=True)
def _ensure_json_parent_dir(file_path: str) -> None:
parent = os.path.dirname(file_path)
if parent:
os.makedirs(parent, exist_ok=True)
def _resolve_env_file_path(env_file: Optional[str] = None) -> str:
if env_file:
return env_file
token_dir = os.path.dirname(TOKEN_CONFIG_FILE)
if token_dir:
return os.path.join(token_dir, "env.json")
return ENV_CONFIG_FILE
def _normalize_env_config(data: dict) -> dict:
env_config = build_default_env_config()
env = data.get("env")
if isinstance(env, dict):
python_ok = env.get("python")
env_config["env"]["python"] = bool(python_ok) if isinstance(python_ok, bool) else False
resident_city = data.get("resident_city")
if isinstance(resident_city, str):
env_config["resident_city"] = resident_city.strip()
token = data.get("token")
if isinstance(token, str):
env_config["token"] = token.strip()
updated_at = data.get("updated_at")
if isinstance(updated_at, str):
env_config["updated_at"] = updated_at.strip()
return env_config
def load_env_config(env_file: Optional[str] = None) -> dict:
env_file = _resolve_env_file_path(env_file)
try:
with open(env_file, "r", encoding="utf-8") as handle:
data = json.load(handle)
if isinstance(data, dict):
return _normalize_env_config(data)
except (OSError, json.JSONDecodeError):
pass
return build_default_env_config()
def save_env_config(env_config: dict, env_file: Optional[str] = None) -> str:
env_file = _resolve_env_file_path(env_file)
_ensure_env_parent_dir(env_file)
normalized = _normalize_env_config(env_config)
with open(env_file, "w", encoding="utf-8") as handle:
json.dump(normalized, handle, ensure_ascii=False, indent=2)
handle.write("\n")
return env_file
def _load_legacy_token(config_file: Optional[str] = None) -> str:
config_file = config_file or TOKEN_CONFIG_FILE
try:
with open(config_file, "r", encoding="utf-8") as f:
return f.read().strip()
except OSError:
return ""
def load_state_file(state_file: Optional[str] = None) -> dict:
state_file = state_file or STATE_FILE_PATH
try:
with open(state_file, "r", encoding="utf-8") as handle:
data = json.load(handle)
if isinstance(data, dict):
state = build_default_state()
pickup = data.get("pickup")
dropoff = data.get("dropoff")
state["pickup"] = pickup if isinstance(pickup, list) else []
state["dropoff"] = dropoff if isinstance(dropoff, list) else []
state["estimate"] = data.get("estimate")
user_prefer_labels = data.get("userPreferLabels")
state["userPreferLabels"] = user_prefer_labels if isinstance(user_prefer_labels, list) else []
state["orderId"] = data.get("orderId")
return state
except (OSError, json.JSONDecodeError):
pass
return build_default_state()
def save_state_file(state: dict, state_file: Optional[str] = None) -> str:
state_file = state_file or STATE_FILE_PATH
_ensure_state_parent_dir(state_file)
with open(state_file, "w", encoding="utf-8") as handle:
json.dump(state, handle, ensure_ascii=False, indent=2)
handle.write("\n")
return state_file
def _load_json_key_value_store(file_path: str) -> dict:
try:
with open(file_path, "r", encoding="utf-8") as handle:
data = json.load(handle)
if isinstance(data, dict):
return data
except (OSError, json.JSONDecodeError):
pass
return {}
def _save_json_key_value_store(file_path: str, data: dict) -> str:
_ensure_json_parent_dir(file_path)
with open(file_path, "w", encoding="utf-8") as handle:
json.dump(data, handle, ensure_ascii=False, indent=2)
handle.write("\n")
return file_path
def _resolve_addr_file_path(addr_file: Optional[str] = None) -> str:
return addr_file or ADDR_FILE_PATH
def _resolve_short_cut_file_path(short_cut_file: Optional[str] = None) -> str:
return short_cut_file or SHORT_CUT_FILE_PATH
def _parse_json_object_arg(raw_text: str, field_name: str) -> dict:
try:
parsed = json.loads(raw_text)
except json.JSONDecodeError as exc:
raise ValueError(f"invalid {field_name} json: {exc}")
if not isinstance(parsed, dict):
raise ValueError(f"{field_name} must be a JSON object")
return parsed
def _list_store_keys(file_path: str) -> dict:
store = _load_json_key_value_store(file_path)
return {
"keys": list(store.keys()),
"count": len(store),
}
def _get_store_value_by_key(file_path: str, key: str) -> dict:
store = _load_json_key_value_store(file_path)
lookup_key = key.strip()
if not lookup_key:
raise ValueError("key is required")
return {
"key": lookup_key,
"found": lookup_key in store,
"value": store.get(lookup_key),
}
def _upsert_store_value(file_path: str, key: str, value: dict) -> dict:
store = _load_json_key_value_store(file_path)
lookup_key = key.strip()
if not lookup_key:
raise ValueError("key is required")
existed = lookup_key in store
store[lookup_key] = value
written_file = _save_json_key_value_store(file_path, store)
return {
"updated": True,
"created": not existed,
"key": lookup_key,
"value": value,
"file": written_file,
}
def get_addr_keys(addr_file: Optional[str] = None) -> dict:
addr_file = _resolve_addr_file_path(addr_file)
payload = _list_store_keys(addr_file)
payload["addr_file"] = addr_file
return payload
def get_addr_value_by_key(key: str, addr_file: Optional[str] = None) -> dict:
addr_file = _resolve_addr_file_path(addr_file)
payload = _get_store_value_by_key(addr_file, key)
payload["addr_file"] = addr_file
return payload
def upsert_addr_value(key: str, value: dict, addr_file: Optional[str] = None) -> dict:
addr_file = _resolve_addr_file_path(addr_file)
payload = _upsert_store_value(addr_file, key, value)
payload["addr_file"] = addr_file
return payload
def sync_addr_value_to_state(key: str, scene: int, addr_file: Optional[str] = None, state_file: Optional[str] = None) -> dict:
addr_file = _resolve_addr_file_path(addr_file)
lookup_key = key.strip()
if not lookup_key:
raise ValueError("key is required")
scene_key = _get_scene_key(scene)
addr_payload = get_addr_value_by_key(lookup_key, addr_file=addr_file)
if not addr_payload.get("found"):
raise ValueError(f"key not found in addr store: {lookup_key}")
value = addr_payload.get("value")
if not isinstance(value, dict):
raise ValueError(f"invalid addr value for key: {lookup_key}")
normalized = _normalize_poi_item(value)
if not str(normalized.get("poiid", "")).strip():
raise ValueError(f"missing required field: {lookup_key}.poiid")
state = load_state_file(state_file=state_file)
state[scene_key] = [normalized]
state["estimate"] = None
state["userPreferLabels"] = []
state["orderId"] = None
written_state_file = save_state_file(state, state_file=state_file)
return {
"updated": True,
"key": lookup_key,
"scene": scene,
"scene_key": scene_key,
"state_file": written_state_file,
"addr_file": addr_file,
}
def get_short_cut_keys(short_cut_file: Optional[str] = None) -> dict:
short_cut_file = _resolve_short_cut_file_path(short_cut_file)
payload = _list_store_keys(short_cut_file)
payload["short_cut_file"] = short_cut_file
return payload
def get_short_cut_value_by_key(key: str, short_cut_file: Optional[str] = None) -> dict:
short_cut_file = _resolve_short_cut_file_path(short_cut_file)
payload = _get_store_value_by_key(short_cut_file, key)
payload["short_cut_file"] = short_cut_file
return payload
def upsert_short_cut_value(key: str, value: dict, short_cut_file: Optional[str] = None) -> dict:
short_cut_file = _resolve_short_cut_file_path(short_cut_file)
payload = _upsert_store_value(short_cut_file, key, value)
payload["short_cut_file"] = short_cut_file
return payload
def _parse_prefer_labels_json(raw_text: str) -> List[int]:
try:
parsed = json.loads(raw_text)
except json.JSONDecodeError as exc:
raise ValueError(f"invalid user prefer labels json: {exc}")
if not isinstance(parsed, list):
raise ValueError("user prefer labels must be a JSON array")
result: List[int] = []
for item in parsed:
if isinstance(item, bool):
raise ValueError("user prefer labels must contain integers")
if not isinstance(item, int):
raise ValueError("user prefer labels must contain integers")
result.append(item)
return result
def _parse_price_estimate_keys_json(raw_text: str) -> List[str]:
try:
parsed = json.loads(raw_text)
except json.JSONDecodeError as exc:
raise ValueError(f"invalid price estimate keys json: {exc}")
if not isinstance(parsed, list):
raise ValueError("priceEstimateKeys must be a JSON array")
result: List[str] = []
for item in parsed:
if not isinstance(item, str):
raise ValueError("priceEstimateKeys must contain strings")
result.append(item)
return result
def _require_dict(parent: dict, key: str, path: str) -> dict:
value = parent.get(key)
if not isinstance(value, dict):
raise ValueError(f"missing required object: {path}")
return value
def _require_non_empty(value, path: str):
if value in (None, ""):
raise ValueError(f"missing required field: {path}")
return value
def _require_number(value, path: str) -> float:
if isinstance(value, bool):
raise ValueError(f"invalid number field: {path}")
if not isinstance(value, (int, float)):
raise ValueError(f"invalid number field: {path}")
return float(value)
def _get_scene_key(scene: int) -> str:
if scene == 1:
return "pickup"
if scene == 2:
return "dropoff"
raise ValueError("scene must be one of 1, 2")
def _normalize_poi_item(item: dict) -> dict:
return {
"name": item.get("name", ""),
"address": item.get("address", ""),
"longitude": item.get("longitude", ""),
"latitude": item.get("latitude", ""),
"poiid": item.get("poiid", ""),
"citycode": item.get("citycode", ""),
"point_name": item.get("point_name", ""),
"point_longitude": item.get("point_longitude", ""),
"point_latitude": item.get("point_latitude", ""),
}
def _parse_static_map_markers_json(raw_text: str) -> List[dict]:
try:
parsed = json.loads(raw_text)
except json.JSONDecodeError as exc:
raise ValueError(f"invalid markers json: {exc}")
if not isinstance(parsed, list) or not parsed:
raise ValueError("markers must be a non-empty JSON array")
result: List[dict] = []
for index, item in enumerate(parsed):
if not isinstance(item, dict):
raise ValueError(f"markers[{index}] must be a JSON object")
result.append(item)
return result
def _coerce_static_map_coordinate(value, field_name: str, index: int) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float, str)):
raise ValueError(f"markers[{index}].{field_name} must be a number")
try:
number = float(value)
except (TypeError, ValueError):
raise ValueError(f"markers[{index}].{field_name} must be a number")
return number
def _normalize_static_map_marker_type(value, index: int) -> str:
text = str(value).strip() if value is not None else ""
if text not in STATIC_MAP_MARKER_PRESETS:
raise ValueError("markers[%d].marker must be one of: %s" % (index, ", ".join(STATIC_MAP_MARKER_PRESETS.keys())))
return text
def normalize_static_map_markers(markers: List[dict]) -> List[dict]:
normalized: List[dict] = []
for index, item in enumerate(markers):
latitude = _coerce_static_map_coordinate(item.get("latitude"), "latitude", index)
longitude = _coerce_static_map_coordinate(item.get("longitude"), "longitude", index)
if latitude < -90 or latitude > 90:
raise ValueError(f"markers[{index}].latitude out of range: {latitude}")
if longitude < -180 or longitude > 180:
raise ValueError(f"markers[{index}].longitude out of range: {longitude}")
marker_type = _normalize_static_map_marker_type(item.get("marker"), index)
normalized.append({
"latitude": latitude,
"longitude": longitude,
"marker": marker_type,
})
return normalized
def _parse_static_map_size(size_text: str) -> Tuple[int, int]:
match = re.fullmatch(r"\s*(\d+)\s*\*\s*(\d+)\s*", str(size_text))
if not match:
raise ValueError("size must be in WIDTH*HEIGHT format")
width = int(match.group(1))
height = int(match.group(2))
if width < 50 or width > 1680:
raise ValueError("size width must be between 50 and 1680")
if height < 50 or height > 1200:
raise ValueError("size height must be between 50 and 1200")
return width, height
def _lat_to_mercator_rad(latitude: float) -> float:
sin_value = math.sin(math.radians(latitude))
rad_x2 = math.log((1 + sin_value) / (1 - sin_value)) / 2
return max(min(rad_x2, math.pi), -math.pi) / 2
def _zoom_for_fraction(map_pixels: float, world_pixels: float, fraction: float) -> float:
if map_pixels <= 0 or world_pixels <= 0:
return 4.0
if fraction <= 0:
return 18.0
return math.log(map_pixels / world_pixels / fraction, 2)
def calculate_static_map_center(markers: List[dict]) -> Tuple[float, float]:
latitudes = [item["latitude"] for item in markers]
longitudes = [item["longitude"] for item in markers]
return (min(latitudes) + max(latitudes)) / 2.0, (min(longitudes) + max(longitudes)) / 2.0
def calculate_static_map_zoom(markers: List[dict], width: int, height: int, scale: int = 2, padding: int = 40) -> int:
if len(markers) == 1:
return 17 if scale == 2 else 18
usable_width = max(width - padding * 2, 1)
usable_height = max(height - padding * 2, 1)
if scale == 2:
usable_width *= 2
usable_height *= 2
latitudes = [item["latitude"] for item in markers]
longitudes = [item["longitude"] for item in markers]
max_lat = max(latitudes)
min_lat = min(latitudes)
max_lng = max(longitudes)
min_lng = min(longitudes)
lat_fraction = (_lat_to_mercator_rad(max_lat) - _lat_to_mercator_rad(min_lat)) / math.pi
lng_diff = max_lng - min_lng
if lng_diff < 0:
lng_diff += 360
lng_fraction = lng_diff / 360.0
lat_zoom = _zoom_for_fraction(usable_height, 256.0, lat_fraction)
lng_zoom = _zoom_for_fraction(usable_width, 256.0, lng_fraction)
max_zoom = 17 if scale == 2 else 18
return max(4, min(int(math.floor(min(lat_zoom, lng_zoom))), max_zoom))
def build_static_map_marker_params(markers: List[dict]) -> List[str]:
grouped = {}
for item in markers:
preset = STATIC_MAP_MARKER_PRESETS[item["marker"]]
group_key = (preset["color"], preset["label"])
grouped.setdefault(group_key, []).append(f'{item["latitude"]:.6f},{item["longitude"]:.6f}')
params = []
for (color, label), locations in grouped.items():
style_parts = [f"size:large", f"color:{color}", f"label:{label}"]
params.append("|".join(style_parts + locations))
return params
def build_static_map_url(markers: List[dict], token: str = "") -> str:
size = DEFAULT_STATIC_MAP_SIZE
scale = DEFAULT_STATIC_MAP_SCALE
maptype = DEFAULT_STATIC_MAP_MAPTYPE
if scale not in (1, 2):
raise ValueError("scale must be 1 or 2")
if maptype not in ("roadmap", "satellite", "hybrid"):
raise ValueError("maptype must be one of: roadmap, satellite, hybrid")
normalized_markers = normalize_static_map_markers(markers)
width, height = _parse_static_map_size(size)
center_lat, center_lng = calculate_static_map_center(normalized_markers)
zoom = calculate_static_map_zoom(normalized_markers, width=width, height=height, scale=scale)
timestamp_ms = generate_timestamp_ms()
seq_id = generate_seq_id(timestamp_ms)
query_params = [
("seqId", seq_id),
("wxAppId", DEFAULT_WX_APP_ID),
("token", token),
("timestamp", str(timestamp_ms)),
("size", f"{width}*{height}"),
("center", f"{center_lat:.6f},{center_lng:.6f}"),
("zoom", str(zoom)),
("scale", str(scale)),
("maptype", maptype),
]
for marker_param in build_static_map_marker_params(normalized_markers):
query_params.append(("markers", marker_param))
url = "%s%s?%s" % (
DEFAULT_API_BASE.rstrip("/"),
DEFAULT_STATIC_MAP_PATH,
urllib.parse.urlencode(query_params, doseq=True),
)
return url
def _append_poi_candidates(existing: list, items: list) -> list:
poiid_to_index = {}
for idx, entry in enumerate(existing):
if not isinstance(entry, dict):
continue
poiid = str(entry.get("poiid", "")).strip()
if poiid:
poiid_to_index[poiid] = idx
for item in items:
if not isinstance(item, dict):
continue
normalized = _normalize_poi_item(item)
poiid = str(normalized.get("poiid", "")).strip()
if not poiid:
continue
if poiid in poiid_to_index:
existing[poiid_to_index[poiid]] = normalized
else:
poiid_to_index[poiid] = len(existing)
existing.append(normalized)
return existing
def update_state_from_poi_search(scene: int, items: list, state_file: Optional[str] = None) -> Optional[str]:
if scene not in (1, 2) or not items:
return None
state = load_state_file(state_file=state_file)
scene_key = _get_scene_key(scene)
candidates = state.get(scene_key)
if not isinstance(candidates, list):
candidates = []
state[scene_key] = _append_poi_candidates(candidates, items)
return save_state_file(state, state_file=state_file)
def apply_select_poi(poiid: str, scene: int, state_file: Optional[str] = None) -> dict:
selected_poiid = poiid.strip()
if not selected_poiid:
raise ValueError("poiid is required")
scene_key = _get_scene_key(scene)
state = load_state_file(state_file=state_file)
candidates = state.get(scene_key)
if not isinstance(candidates, list) or not candidates:
raise ValueError(f"missing candidates for scene={scene}")
selected = None
for item in candidates:
if not isinstance(item, dict):
continue
if str(item.get("poiid", "")).strip() == selected_poiid:
selected = _normalize_poi_item(item)
break
if selected is None:
raise ValueError(f"poiid not found in scene={scene}: {selected_poiid}")
state[scene_key] = [selected]
state["estimate"] = None
state["userPreferLabels"] = []
state["orderId"] = None
written_state_file = save_state_file(state, state_file=state_file)
return {
"updated": True,
"scene": scene,
"poiid": selected_poiid,
"state_file": written_state_file,
}
def _require_selected_scene_item(state: dict, scene: int) -> dict:
scene_key = _get_scene_key(scene)
candidates = state.get(scene_key)
if not isinstance(candidates, list) or len(candidates) != 1:
raise ValueError(f"missing selected candidate: {scene_key}")
selected = candidates[0]
if not isinstance(selected, dict):
raise ValueError(f"missing selected candidate: {scene_key}")
return selected
def build_estimate_state_products(data: dict) -> list:
products = []
product_list = data.get("product") if isinstance(data, dict) else []
default_estimate_time = data.get("estimateTime") if isinstance(data, dict) else None
default_estimate_distance = data.get("distance") if isinstance(data, dict) else None
if not isinstance(product_list, list):
return products
for product in product_list:
if not isinstance(product, dict):
continue
rider_info = product.get("riderInfo")
if not isinstance(rider_info, dict):
continue
estimate_list = product.get("priceEstimate")
if not isinstance(estimate_list, list):
continue
for estimate_item in estimate_list:
if not isinstance(estimate_item, dict):
continue
sp = estimate_item.get("sp")
if not isinstance(sp, dict):
sp = {}
default_checked = estimate_item.get("defaultChecked")
products.append({
"isTop": product.get("isTop", 0),
"topStyle": product.get("topStyle", 1),
"riderInfo": {
"rideType": rider_info.get("rideType"),
"riderClassify": rider_info.get("riderClassify"),
"riderDesc": rider_info.get("riderDesc"),
},
"id": estimate_item.get("id"),
"priceEstimateKey": estimate_item.get("priceEstimateKey"),
"defaultChecked": default_checked,
"discountType": estimate_item.get("discountType"),
"discountAmount": estimate_item.get("discountAmount"),
"discountPercentage": estimate_item.get("discountPercentage"),
"estimatePrice": estimate_item.get("estimatePrice"),
"estimateTime": estimate_item.get("estimateTime", default_estimate_time),
"estimateDistance": estimate_item.get("estimateDistance", default_estimate_distance),
"estimateDuration": estimate_item.get("estimateDuration", default_estimate_time),
"isPriceChange": estimate_item.get("isPriceChange"),
"platEstimatePriceType": estimate_item.get("platEstimatePriceType"),
"platEstimatePrice": estimate_item.get("platEstimatePrice"),
"userSelect": 1 if default_checked == 1 else 0,
"sp": {
"code": sp.get("code"),
"name": sp.get("name"),
"avatar": sp.get("avatar", ""),
},
})
return products
def update_state_from_estimate_result(result: dict, user_prefer_labels: List[int], state_file: Optional[str] = None) -> Optional[str]:
if not isinstance(result, dict):
return None
body = result.get("body")
if not isinstance(body, dict):
return None
if body.get("code") != 0:
return None
data = body.get("data")
if not isinstance(data, dict):
return None
if data.get("estimateKey") in (None, ""):
return None
state = load_state_file(state_file=state_file)
state["estimate"] = {
"estimateKey": data.get("estimateKey"),
"distance": data.get("distance"),
"estimateTime": data.get("estimateTime"),
"products": build_estimate_state_products(data),
"userPreferLabels": user_prefer_labels,
}
state["userPreferLabels"] = user_prefer_labels
state["orderId"] = None
return save_state_file(state, state_file=state_file)
def update_state_from_create_order_result(result: dict, state_file: Optional[str] = None) -> Optional[str]:
if not isinstance(result, dict):
return None
body = result.get("body")
if not isinstance(body, dict):
return None
code, _message, data = extract_create_order_business_fields(body)
if code != 0:
return None
order_id = normalize_order_id(data.get("orderId"))
if not order_id:
return None
state = load_state_file(state_file=state_file)
state["orderId"] = order_id
return save_state_file(state, state_file=state_file)
def build_estimate_price_payload_from_state(state: dict) -> dict:
pickup = _require_selected_scene_item(state, scene=1)
dropoff = _require_selected_scene_item(state, scene=2)
city_code = normalize_city_code(str(_require_non_empty(pickup.get("citycode"), "pickup[0].citycode")))
dest_city_code = normalize_city_code(str(_require_non_empty(dropoff.get("citycode"), "dropoff[0].citycode")))
from_lat = _require_number(pickup.get("point_latitude"), "pickup[0].point_latitude")
from_lng = _require_number(pickup.get("point_longitude"), "pickup[0].point_longitude")
to_lat = _require_number(dropoff.get("point_latitude"), "dropoff[0].point_latitude")
to_lng = _require_number(dropoff.get("point_longitude"), "dropoff[0].point_longitude")
timestamp_ms = generate_timestamp_ms()
payload = {
"seqId": generate_seq_id(timestamp_ms),
"timestamp": timestamp_ms,
"departureTime": timestamp_ms,
"orderServiceType": 1,
"cityCode": city_code,
"destCityCode": dest_city_code,
"fromLat": from_lat,
"fromLng": from_lng,
"fromName": _require_non_empty(pickup.get("name"), "pickup[0].name"),
"fromAddress": _require_non_empty(pickup.get("address"), "pickup[0].address"),
"toId": _require_non_empty(dropoff.get("poiid"), "dropoff[0].poiid"),
"toLat": to_lat,
"toLng": to_lng,
"toName": _require_non_empty(dropoff.get("name"), "dropoff[0].name"),
"toAddress": _require_non_empty(dropoff.get("address"), "dropoff[0].address"),
}
payload["currentLat"] = from_lat
payload["currentLng"] = from_lng
return payload
def _validate_user_prefer_labels(value) -> List[int]:
if not isinstance(value, list):
raise ValueError("missing required field: estimate.userPreferLabels")
result: List[int] = []
for item in value:
if isinstance(item, bool) or not isinstance(item, int):
raise ValueError("invalid user prefer labels in estimate.userPreferLabels")
result.append(item)
return result
def normalize_order_id(value) -> str:
if value is None:
return ""
text = str(value).strip()
if not text or text.lower() == "null":
return ""
return text
def extract_create_order_business_fields(body: dict) -> Tuple[object, object, dict]:
if not isinstance(body, dict):
return None, "", {}
business_code = body.get("code")
business_message = body.get("message")
data = body.get("data")
if not isinstance(data, dict):
return business_code, business_message, {}
nested_data = data.get("data")
nested_code = data.get("code")
nested_message = data.get("message")
if isinstance(nested_data, dict):
if nested_code is not None:
business_code = nested_code
if nested_message is not None:
business_message = nested_message
return business_code, business_message, nested_data
return business_code, business_message, data
def build_create_order_result_data(body: dict) -> dict:
code, message, data = extract_create_order_business_fields(body)
order_id = normalize_order_id(data.get("orderId"))
unfinished_order = data.get("unfinishedOrder")
if not isinstance(unfinished_order, bool):
unfinished_order = code == 101 or (not order_id and isinstance(message, str) and ("未完成" in message or "进行中" in message))
return {
"orderId": order_id,
"unfinishedOrder": unfinished_order,
}
def transform_create_order_result(result: dict) -> dict:
if not isinstance(result, dict):
return result
body = result.get("body")
if not isinstance(body, dict):
return result
result["body"] = {
"code": body.get("code"),
"message": body.get("message"),
"data": build_create_order_result_data(body),
}
return result
def _get_point_value(item: dict, point_key: str, fallback_key: str):
point_value = item.get(point_key)
if point_value not in (None, ""):
return point_value
return item.get(fallback_key)
def _build_estimate_price_entry(item: dict, idx: int) -> dict:
rider_info = _require_dict(item, "riderInfo", f"estimate.products[{idx}].riderInfo")
ride_type = _require_non_empty(rider_info.get("rideType"), f"estimate.products[{idx}].riderInfo.rideType")
rider_classify = _require_non_empty(rider_info.get("riderClassify"), f"estimate.products[{idx}].riderInfo.riderClassify")
sp = _require_dict(item, "sp", f"estimate.products[{idx}].sp")
sp_code = _require_non_empty(sp.get("code"), f"estimate.products[{idx}].sp.code")
price_estimate_key = _require_non_empty(item.get("priceEstimateKey"), f"estimate.products[{idx}].priceEstimateKey")
entry_id = item.get("id")
if entry_id in (None, ""):
entry_id = f"{rider_classify}-{ride_type}-{sp_code}"
return {
"id": entry_id,
"choosed": True,
"spName": sp.get("name", ""),
"spCode": sp_code,
"spAvatar": sp.get("avatar", ""),
"isPriceChange": item.get("isPriceChange"),
"estimateTime": item.get("estimateTime"),
"discountType": item.get("discountType"),
"estimatePrice": item.get("estimatePrice"),
"discountAmount": item.get("discountAmount"),
"priceEstimateKey": price_estimate_key,
"platEstimatePriceType": item.get("platEstimatePriceType"),
"platEstimatePrice": item.get("platEstimatePrice"),
"estimateDistance": item.get("estimateDistance"),
"estimateDuration": item.get("estimateDuration"),
}
def build_product_str_from_estimate_products(products: list) -> str:
if not isinstance(products, list):
raise ValueError("missing required field: estimate.products")
built = []
for idx, item in enumerate(products):
if not isinstance(item, dict):
continue
if item.get("userSelect") != 1:
continue
rider_info = _require_dict(item, "riderInfo", f"estimate.products[{idx}].riderInfo")
ride_type = _require_non_empty(rider_info.get("rideType"), f"estimate.products[{idx}].riderInfo.rideType")
rider_classify = _require_non_empty(rider_info.get("riderClassify"), f"estimate.products[{idx}].riderInfo.riderClassify")
built.append({
"isTop": item.get("isTop", 0),
"topStyle": item.get("topStyle", 1),
"riderInfo": {
"rideType": ride_type,
"riderClassify": rider_classify,
},
"estimatePrices": [_build_estimate_price_entry(item, idx)],
})
if not built:
raise ValueError("missing selected products: estimate.products[].userSelect=1")
return json.dumps(built, ensure_ascii=False)
def build_create_order_payload_from_state(state: dict) -> dict:
pickup = _require_selected_scene_item(state, scene=1)
dropoff = _require_selected_scene_item(state, scene=2)
estimate = _require_dict(state, "estimate", "estimate")
timestamp_ms = generate_timestamp_ms()
return {
"seqId": generate_seq_id(timestamp_ms),
"timestamp": timestamp_ms,
"estimateKey": _require_non_empty(estimate.get("estimateKey"), "estimate.estimateKey"),
"departureTime": timestamp_ms,
"orderServiceType": 1,
"fromLat": _require_number(pickup.get("point_latitude"), "pickup[0].point_latitude"),
"fromLng": _require_number(pickup.get("point_longitude"), "pickup[0].point_longitude"),
"fromName": _require_non_empty(_get_point_value(pickup, "point_name", "name"), "pickup[0].point_name"),
"fromAddress": _require_non_empty(_get_point_value(pickup, "address", "point_name"), "pickup[0].address"),
"productStr": build_product_str_from_estimate_products(estimate.get("products")),
"toId": _require_non_empty(dropoff.get("poiid"), "dropoff[0].poiid"),
"toLat": _require_number(dropoff.get("point_latitude"), "dropoff[0].point_latitude"),
"toLng": _require_number(dropoff.get("point_longitude"), "dropoff[0].point_longitude"),
"toName": _require_non_empty(_get_point_value(dropoff, "point_name", "name"), "dropoff[0].point_name"),
"toAddress": _require_non_empty(_get_point_value(dropoff, "address", "point_name"), "dropoff[0].address"),
"isNeedReEstimate": True,
}
def sign(sk: str, params: dict) -> str:
import hashlib
parts = []
for key in sorted(params.keys()):
if key == "sign":
continue
val = params[key]
if isinstance(val, bool):
val = "true" if val else "false"
elif val is None:
val = "null"
else:
val = json.dumps(val, ensure_ascii=False).strip('"')
parts.append(f"{key}{val}")
meta = "".join(parts) + sk
md5 = hashlib.md5()
md5.update(meta.encode("UTF-8"))
return md5.hexdigest()
def load_token(environ: Optional[dict] = None) -> str:
del environ
env_config = load_env_config()
token = env_config.get("token", "")
if isinstance(token, str) and token.strip():
return token.strip()
legacy_token = _load_legacy_token()
if legacy_token:
env_config["token"] = legacy_token
save_env_config(env_config)
return legacy_token
def build_headers(token: str) -> dict:
return {"Content-Type": "application/json"}
def build_preflight_result(environ: Optional[dict] = None) -> dict:
environ = os.environ if environ is None else environ
python_ok = sys.version_info >= (3, 6)
token = load_token(environ)
env_config = load_env_config()
resident_city = str(env_config.get("resident_city", "")).strip()
resident_city_present = bool(resident_city)
env_config["env"]["python"] = python_ok
env_config["token"] = token
try:
save_env_config(env_config)
except OSError:
pass
result = {
"python_ok": python_ok,
"python_version": sys.version.split()[0],
"platform": platform.platform(),
"token_present": bool(token),
"resident_city": resident_city,
"resident_city_present": resident_city_present,
"next_actions": [],
}
if not python_ok:
result["next_actions"].append("install_python")
if not token:
result["next_actions"].append("setup_token")
if not resident_city_present:
result["next_actions"].append("setup_resident_city")
if python_ok and token and resident_city_present:
result["next_actions"].append("ready")
return result
def persist_token(token: str, config_file: Optional[str] = None) -> dict:
env_file = _resolve_env_file_path(config_file)
env_config = load_env_config(env_file=env_file)
env_config["token"] = token.strip()
written_file = save_env_config(env_config, env_file=env_file)
return {"saved": True, "config_file": written_file}
def delete_token(config_file: Optional[str] = None) -> dict:
env_file = _resolve_env_file_path(config_file)
env_config = load_env_config(env_file=env_file)
had_token = bool(str(env_config.get("token", "")).strip())
env_config["token"] = ""
written_file = save_env_config(env_config, env_file=env_file)
legacy_removed = False
if os.path.isfile(TOKEN_CONFIG_FILE):
os.remove(TOKEN_CONFIG_FILE)
legacy_removed = True
return {"deleted": had_token or legacy_removed, "config_file": written_file}
def _normalize_city_name(city_name: str) -> str:
city = city_name.strip()
if not city:
return ""
if city.endswith("市"):
return city
return city + "市"
def set_resident_city(city_name: str, env_file: Optional[str] = None) -> dict:
normalized_city = _normalize_city_name(city_name)
if not normalized_city:
raise ValueError("resident city is required")
env_config = load_env_config(env_file=env_file)
env_config["resident_city"] = normalized_city
written_file = save_env_config(env_config, env_file=env_file)
return {
"updated": True,
"resident_city": normalized_city,
"env_file": written_file,
}
def get_resident_city(env_file: Optional[str] = None) -> dict:
env_config = load_env_config(env_file=env_file)
resident_city = str(env_config.get("resident_city", "")).strip()
return {
"resident_city": resident_city,
"resident_city_present": bool(resident_city),
"env_file": _resolve_env_file_path(env_file),
}
def delete_state_file(state_file: Optional[str] = None) -> dict:
state_file = state_file or STATE_FILE_PATH
removed = False
if os.path.isfile(state_file):
os.remove(state_file)
removed = True
return {"deleted": removed, "state_file": state_file}
def read_text_file(path: str) -> str:
try:
with open(path, "r", encoding="utf-8") as handle:
return handle.read()
except OSError:
return ""
def parse_markdown_table_value(markdown_text: str, row_name: str) -> str:
for line in markdown_text.splitlines():
stripped = line.strip()
if not stripped.startswith("|"):
continue
cells = [cell.strip() for cell in stripped.strip("|").split("|")]
if len(cells) >= 2 and cells[0] == row_name:
return cells[1]
return ""
def resolve_city_name(city_name: str) -> str:
if city_name.strip():
return city_name.strip()
resident_city_result = get_resident_city()
resident_city = resident_city_result.get("resident_city", "")
if isinstance(resident_city, str):
return resident_city
return ""
def build_poi_search_payload(args: argparse.Namespace) -> dict:
if args.scene not in (0, 1, 2):
raise ValueError("scene must be one of 0, 1, 2")
return {
"keyword": args.keyword,
"region": resolve_city_name(args.city_name),
"policy": SCENE_TO_POLICY[args.scene],
"pageIndex": args.page_index,
"pageSize": args.page_size,
}
def build_get_on_points_payload(lat: float, lng: float) -> dict:
return {
"lat": lat,
"lng": lng,
"maxCount": 1,
"appId": "0",
"appChannelId": "0",
"geoPointAsFallback": True,
"geoPointWhenNoAbsorb": True,
}
def build_get_drop_off_point_payload(poi_id: str, lat: float, lng: float) -> dict:
return {
"appId": "0",
"appChannelId": "0",
"poiId": poi_id,
"endLat": lat,
"endLng": lng,
"checkSubPoi": True,
}
def parse_suggestion_data_list(body) -> list:
if not isinstance(body, dict):
return []
data = body.get("data", {})
if not isinstance(data, dict):
return []
inner_data = data.get("data", {})
if not isinstance(inner_data, dict):
return []
data_list = inner_data.get("dataList", [])
return data_list if isinstance(data_list, list) else []
def extract_on_points_from_response(body) -> list:
if not isinstance(body, dict):
return []
data = body.get("data", {})
if not isinstance(data, dict):
return []
inner_data = data.get("data", {})
if not isinstance(inner_data, dict):
return []
points = inner_data.get("points", [])
if not isinstance(points, list) or not points:
return []
first = points[0]
if not isinstance(first, dict):
return []
loc = first.get("location", {})
if not isinstance(loc, dict):
return []
return [{
"point_name": first.get("title", ""),
"point_latitude": loc.get("lat", ""),
"point_longitude": loc.get("lng", ""),
}]
def extract_drop_off_points_from_response(body) -> list:
if not isinstance(body, dict):
return []
data = body.get("data", {})
if not isinstance(data, dict):
return []
inner_data = data.get("data", {})
if not isinstance(inner_data, dict):
return []
parking = inner_data.get("parkingSuggestions", {})
if not isinstance(parking, dict):
parking = {}
points = parking.get("points", [])
if isinstance(points, list) and points:
result = []
for pt in points:
if not isinstance(pt, dict):
continue
sub_points = pt.get("subPoints", [])
if isinstance(sub_points, list) and sub_points:
for sp in sub_points:
if not isinstance(sp, dict):
continue
sp_loc = sp.get("location", {})
result.append({
"point_name": pt.get("title", "") + "-" + sp.get("title", ""),
"point_latitude": sp_loc.get("lat", "") if isinstance(sp_loc, dict) else "",
"point_longitude": sp_loc.get("lng", "") if isinstance(sp_loc, dict) else "",
})
else:
loc = pt.get("location", {})
result.append({
"point_name": pt.get("title", ""),
"point_latitude": loc.get("lat", "") if isinstance(loc, dict) else "",
"point_longitude": loc.get("lng", "") if isinstance(loc, dict) else "",
})
if result:
return result
# Fallback: data.dropOffPoints (NOT parkingSuggestions.dropOffPoints)
drop_off = inner_data.get("dropOffPoints", {})
if not isinstance(drop_off, dict):
return []
point_list = drop_off.get("pointList", [])
if isinstance(point_list, list) and point_list:
first = point_list[0]
if isinstance(first, dict):
return [{
"point_name": first.get("title", ""),
"point_latitude": first.get("lat", ""),
"point_longitude": first.get("lng", ""),
}]
return []
def flatten_poi_search_result(result: dict, page_index: int) -> dict:
body = result.get("body")
if not isinstance(body, dict):
return result
data_list = parse_suggestion_data_list(body)
items = []
for item in data_list:
if not isinstance(item, dict):
continue
loc = item.get("location", {})
items.append({
"name": item.get("title", ""),
"address": item.get("address", ""),
"longitude": loc.get("lng", "") if isinstance(loc, dict) else "",
"latitude": loc.get("lat", "") if isinstance(loc, dict) else "",
"poiid": item.get("id", ""),
"citycode": str(item.get("adcode", "")),
"point_name": "",
"point_longitude": "",
"point_latitude": "",
})
body["items"] = items
body["page_index"] = page_index
return result
def normalize_city_code(city_code) -> str:
text = str(city_code).strip()
if len(text) == 6 and text.isdigit():
return text[:4] + "00"
return text
def transform_estimate_price_result(result: dict) -> dict:
if not isinstance(result, dict):
return result
body = result.get("body")
if not isinstance(body, dict):
return result
raw_data = body.get("data")
if not isinstance(raw_data, dict):
raw_data = {}
# Surface inner business errors (e.g. code 21003 "当前城市未开通打车功能")
inner_code = raw_data.get("code")
if isinstance(inner_code, int) and inner_code != 0:
inner_msg = raw_data.get("message") or raw_data.get("msg") or "unknown error"
result_copy = dict(result)
result_copy["body"] = dict(body)
result_copy["body"]["error"] = {"code": inner_code, "message": inner_msg}
return result_copy
data = raw_data
nested_data = raw_data.get("data")
if isinstance(nested_data, dict) and not any(key in raw_data for key in ("estimateKey", "product", "cityStatus", "cityMessage", "distance", "estimateTime")):
data = nested_data
filtered_product = []
product_list = data.get("product")
if isinstance(product_list, list):
for item in product_list:
if not isinstance(item, dict):
continue
rider_info = item.get("riderInfo")
if not isinstance(rider_info, dict):
continue
ride_type = rider_info.get("rideType")
if ride_type not in ALLOWED_RIDE_TYPES:
continue
price_estimate_list = []
raw_estimates = item.get("priceEstimate")
if isinstance(raw_estimates, list):
for raw_estimate in raw_estimates:
if not isinstance(raw_estimate, dict):
continue
sp = raw_estimate.get("sp")
if not isinstance(sp, dict):
sp = {}
price_estimate_list.append({
"defaultChecked": raw_estimate.get("defaultChecked"),
"discountAmount": raw_estimate.get("discountAmount"),
"discountPercentage": raw_estimate.get("discountPercentage"),
"discountType": raw_estimate.get("discountType"),
"estimatePrice": raw_estimate.get("estimatePrice"),
"priceEstimateKey": raw_estimate.get("priceEstimateKey"),
"sp": {
"aliasName": sp.get("aliasName"),
"code": sp.get("code"),
"name": sp.get("name"),
},
})
filtered_product.append({
"riderInfo": {
"rideType": rider_info.get("rideType"),
"riderClassify": rider_info.get("riderClassify"),
"riderDesc": rider_info.get("riderDesc"),
},
"priceEstimate": price_estimate_list,
})
result["body"] = {
"code": body.get("code"),
"message": body.get("message"),
"data": {
"cityMessage": data.get("cityMessage"),
"cityStatus": data.get("cityStatus"),
"distance": data.get("distance"),
"estimateKey": data.get("estimateKey"),
"estimateTime": data.get("estimateTime"),
"product": filtered_product,
},
}
return result
def transform_query_ongoing_result(result: dict) -> dict:
if not isinstance(result, dict):
return result
body = result.get("body")
if not isinstance(body, dict):
return result
data = extract_query_ongoing_data(body)
if not isinstance(data, dict):
return result
# fields we care about
fields = [
"hasOnGoingOrder",
"cancelFee",
"departureTime",
"endAddress",
"endLat",
"endLng",
"isTaxi",
"licensePlates",
"mainTips",
"minorTips",
"orderId",
"orderDesc",
"orderSerialInfo",
"orderServiceType",
"serialFlag",
"startName",
"startAddress",
"status",
"statusStr",
"supplierId",
"supplierLogo",
"supplierName",
"tencentOrderStatus",
"tips",
"unpaidFee",
"vehicleColor",
"endName",
]
filtered = {}
for k in fields:
if k in data:
filtered[k] = data.get(k)
# Place filtered fields directly under body.data for compatibility with existing callers/tests
result["body"] = {
"code": body.get("code"),
"message": body.get("message"),
"data": filtered,
"orderId": filtered.get("orderId", ""),
}
return result
def extract_query_ongoing_data(body: dict) -> Optional[dict]:
if not isinstance(body, dict):
return None
raw_data = body.get("data")
if not isinstance(raw_data, dict):
return None
inner_data = raw_data.get("data")
if isinstance(inner_data, dict):
return inner_data
return raw_data
def _safe_extract_nested_detail(body: dict) -> Optional[dict]:
if not isinstance(body, dict):
return None
data = body.get("data")
if not isinstance(data, dict):
return None
inner_data = data.get("data")
if isinstance(inner_data, dict):
detail = inner_data.get("detail")
if isinstance(detail, dict):
return detail
return inner_data
return data
def _to_datetime_text(value) -> str:
if isinstance(value, str):
return value
if isinstance(value, bool) or not isinstance(value, (int, float)):
return ""
if value <= 0:
return ""
import datetime
try:
return datetime.datetime.fromtimestamp(float(value) / 1000.0).strftime("%Y-%m-%d %H:%M:%S")
except (OverflowError, OSError, ValueError):
return ""
def _to_number_or_default(value, default=0):
if isinstance(value, bool):
return default
if isinstance(value, (int, float)):
return value
return default
def transform_query_order_result(result: dict) -> dict:
if not isinstance(result, dict):
return result
body = result.get("body")
if not isinstance(body, dict):
return result
data = _safe_extract_nested_detail(body)
if not isinstance(data, dict):
return result
# Keep compatibility with the existing output contract used by the workflow docs.
if all(key in data for key in ("orderId", "status", "statusDesc", "driver", "vehicle", "position")):
return result
from_address = data.get("fromAddress") if isinstance(data.get("fromAddress"), dict) else {}
to_address = data.get("toAddress") if isinstance(data.get("toAddress"), dict) else {}
from_location = from_address.get("location") if isinstance(from_address.get("location"), dict) else {}
to_location = to_address.get("location") if isinstance(to_address.get("location"), dict) else {}
driver = data.get("driver") if isinstance(data.get("driver"), dict) else {}
vehicle_info = driver.get("vehicleInfo") if isinstance(driver.get("vehicleInfo"), dict) else {}
mapped = {
"orderId": normalize_order_id(data.get("orderId")),
"status": data.get("status"),
"statusDesc": data.get("statusDesc") or data.get("orderDesc") or "",
"acceptTime": _to_datetime_text(data.get("acceptTime") or data.get("arrivedTime")),
"cancelTime": _to_datetime_text(data.get("canceledTime")),
"driver": {
"name": driver.get("name", ""),
"phone": driver.get("phone", ""),
"avatar": driver.get("avatar", ""),
},
"vehicle": {
"brand": vehicle_info.get("brand", ""),
"color": vehicle_info.get("color", ""),
"model": vehicle_info.get("model", ""),
"plate": vehicle_info.get("plate", ""),
"picture": vehicle_info.get("picture", ""),
},
"position": {
"startName": from_address.get("name", ""),
"startAddress": from_address.get("address", ""),
"startLat": from_location.get("lat", ""),
"startLng": from_location.get("lng", ""),
"endName": to_address.get("name", ""),
"endAddress": to_address.get("address", ""),
"endLat": to_location.get("lat", ""),
"endLng": to_location.get("lng", ""),
},
"estimateDistance": _to_number_or_default(data.get("estimateDistance"), _to_number_or_default(data.get("journeyDistance"), _to_number_or_default(data.get("totalDistance"), 0))),
"estimateDuration": _to_number_or_default(data.get("estimateDuration"), _to_number_or_default(data.get("journeyDuration"), _to_number_or_default(data.get("totalTime"), 0))),
"estimatePrice": _to_number_or_default(data.get("estimatePrice"), 0),
"distance": _to_number_or_default(data.get("distance"), 0),
"cost": {
"totalAmount": _to_number_or_default(data.get("totalAmount"), _to_number_or_default(data.get("payAmount"), 0)),
"refundAmount": _to_number_or_default(data.get("refundAmount"), _to_number_or_default(data.get("actualRefundAmount"), 0)),
},
}
result["body"] = {
"code": body.get("code"),
"message": body.get("message"),
"data": mapped,
}
return result
def build_query_order_payload(order_id: str) -> dict:
return {"orderId": order_id}
def build_query_driver_location_payload(order_id: str, state_file: Optional[str] = None) -> dict:
timestamp_ms = generate_timestamp_ms()
state = load_state_file(state_file=state_file)
route_id = state.get("routeId", 0)
traffic_id = state.get("trafficId", 0)
if not isinstance(route_id, int):
route_id = 0
if not isinstance(traffic_id, int):
traffic_id = 0
return {
"seqId": generate_seq_id(timestamp_ms),
"timestamp": timestamp_ms,
"wxAppId": "wx65cc950f42e8fff1",
"orderId": order_id,
"routeId": route_id,
"trafficId": traffic_id,
}
def build_query_ongoing_order_payload(args: argparse.Namespace) -> dict:
del args
return {}
def transform_query_driver_location_result(result: dict) -> dict:
if not isinstance(result, dict):
return result
body = result.get("body")
if not isinstance(body, dict):
return result
data_wrapper = body.get("data", {})
if not isinstance(data_wrapper, dict):
data_wrapper = {}
res_data = data_wrapper.get("resData", {})
if not isinstance(res_data, dict):
res_data = {}
order_info = res_data.get("orderInfo", {})
if not isinstance(order_info, dict):
order_info = {}
driver_display = res_data.get("driverPassengerDisplay", {})
if not isinstance(driver_display, dict):
driver_display = {}
location_info = driver_display.get("driverLocationInfo", {})
if not isinstance(location_info, dict):
location_info = {}
order_status = order_info.get("orderStatus")
eta = location_info.get("eta")
eda = location_info.get("eda")
# Check if eta and eda are available
if eta in (None, "") or eda in (None, ""):
# If not available, check orderStatus
if order_status not in (2, 3, 4):
# Not in a state that supports driver location query
result["body"] = {
"code": body.get("code", -1),
"message": body.get("message", "无法查询司机位置"),
"data": {
"orderStatus": order_status,
"eta": None,
"eda": None,
"driverLocation": None,
},
}
return result
# If status is 2, 3, or 4 but no location info, return empty location
location = ""
result["body"] = {
"code": body.get("code", 0),
"message": body.get("message", "司机位置暂无"),
"data": {
"orderStatus": order_status,
"eta": None,
"eda": None,
"driverLocation": None,
},
}
return result
# Extract location components
location = location_info.get("location", "")
loc_parts = location.split(",") if location else []
try:
location_lat = float(loc_parts[0]) if len(loc_parts) > 0 and loc_parts[0] else None
location_lng = float(loc_parts[1]) if len(loc_parts) > 1 and loc_parts[1] else None
except (ValueError, IndexError):
location_lat = None
location_lng = None
result["body"] = {
"code": body.get("code", 0),
"message": body.get("message", "success"),
"data": {
"orderStatus": order_status,
"eta": eta,
"eda": eda,
"driverLocation": {
"location": location,
"latitude": location_lat,
"longitude": location_lng,
"direction": location_info.get("direction"),
"locationTime": location_info.get("locationTime"),
},
},
}
return result
def build_cancel_order_payload(order_id: str, confirm: bool, reason: str = "") -> dict:
payload = {"orderId": order_id, "confirm": confirm}
if reason.strip():
payload["reason"] = reason.strip()
return payload
def check_token(environ: dict, stderr) -> str:
token = load_token(environ)
if not token:
stderr.write("Token is not configured. Run: save-token <token>\n")
return token
def get_ongoing_order_id(environ: dict, stdout, stderr) -> Optional[str]:
"""
Internal helper: call query-ongoing-order to get the current order ID.
Returns order ID if exists, None otherwise.
"""
token = load_token(environ)
if not token:
stderr.write("Token is not configured. Run: save-token <token>\n")
return None
payload = {}
base_url = DEFAULT_API_BASE.strip()
if not base_url:
stderr.write("API base URL is not configured\n")
return None
headers = build_headers(token)
try:
result = post_request(
base_url,
DEFAULT_QUERY_ONGOING_ORDER_PATH,
headers,
payload,
token=token,
timeout_seconds=DEFAULT_HTTP_TIMEOUT_SECONDS,
)
except (urllib.error.HTTPError, urllib.error.URLError) as exc:
stderr.write(f"Failed to query ongoing order: {exc}\n")
return None
body = result.get("body")
if is_invalid_token_error(body):
stderr.write("Token is invalid or expired\n")
return None
data = extract_query_ongoing_data(body)
if not isinstance(data, dict):
stderr.write("No ongoing order found\n")
return None
order_id = data.get("orderId")
has_ongoing_order = data.get("hasOnGoingOrder")
if has_ongoing_order is False:
stderr.write("No ongoing order found\n")
return None
if order_id and isinstance(order_id, str) and order_id != "null" and order_id.strip() != "":
return order_id.strip()
stderr.write("No ongoing order found\n")
return None
def post_cancel_order_payload(environ: dict, payload: dict, stderr) -> Tuple[Optional[dict], Optional[int]]:
token = load_token(environ)
if not token:
stderr.write("Token is not configured. Run: save-token <token>\n")
return None, 1
base_url = DEFAULT_API_BASE.strip()
if not base_url:
stderr.write("API base URL is not configured\n")
return None, 2
headers = build_headers(token)
try:
result = post_request(
base_url,
DEFAULT_CANCEL_ORDER_PATH,
headers,
payload,
token=token,
timeout_seconds=DEFAULT_HTTP_TIMEOUT_SECONDS,
)
except urllib.error.HTTPError as exc:
error_body = exc.read().decode("utf-8", errors="replace")
try:
error_body_json = json.loads(error_body)
except json.JSONDecodeError:
error_body_json = None
if is_invalid_token_error(error_body_json):
write_json(
stderr,
{
"mode": "error",
"status": exc.code,
"body": error_body,
},
)
return None, 1
write_json(
stderr,
{
"mode": "error",
"status": exc.code,
"body": error_body,
},
)
return None, 3
except urllib.error.URLError as exc:
write_json(
stderr,
{
"mode": "error",
"reason": str(exc),
"hint": "Request timed out after 60s",
},
)
return None, 4
body = result.get("body") if isinstance(result, dict) else None
if is_invalid_token_error(body):
write_json(
stderr,
{
"mode": "error",
"reason": "token is missing or invalid",
"body": body,
},
)
return None, 1
return result, None
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Tencent travel ride-hailing skill CLI")
subparsers = parser.add_subparsers(dest="command")
preflight_parser = subparsers.add_parser("preflight", help="Check Python runtime and token availability")
preflight_parser.set_defaults(handler=handle_preflight)
save_token_parser = subparsers.add_parser("save-token", help="Persist token into config file")
save_token_parser.add_argument("token", help="Token value to persist")
save_token_parser.add_argument("--config-file", default=None, help="Explicit config file path (default: ~/.config/tms-takecar/env.json)")
save_token_parser.set_defaults(handler=handle_save_token)
delete_token_parser = subparsers.add_parser("delete-token", help="Remove saved token from env config")
delete_token_parser.add_argument("--config-file", default=None, help="Explicit config file path")
delete_token_parser.set_defaults(handler=handle_delete_token)
set_resident_city_parser = subparsers.add_parser("set-resident-city", help="Set or update resident city in env config")
set_resident_city_parser.add_argument("city_name", help="Resident city full name")
set_resident_city_parser.add_argument("--env-file", dest="env_file", default=None, help="Explicit env file path (default: ~/.config/tms-takecar/env.json)")
set_resident_city_parser.add_argument("--memory-file", dest="env_file", default=None, help=argparse.SUPPRESS)
set_resident_city_parser.set_defaults(handler=handle_set_resident_city)
get_resident_city_parser = subparsers.add_parser("get-resident-city", help="Get resident city from env config")
get_resident_city_parser.add_argument("--env-file", dest="env_file", default=None, help="Explicit env file path (default: ~/.config/tms-takecar/env.json)")
get_resident_city_parser.add_argument("--memory-file", dest="env_file", default=None, help=argparse.SUPPRESS)
get_resident_city_parser.set_defaults(handler=handle_get_resident_city)
delete_state_parser = subparsers.add_parser("delete-state", help="Remove state.json from config directory")
delete_state_parser.add_argument("--state-file", default=None, help="Explicit state file path (default: ~/.config/tms-takecar/state.json)")
delete_state_parser.set_defaults(handler=handle_delete_state)
addr_keys_parser = subparsers.add_parser("addr-keys", help="Return all keys from addr.json")
addr_keys_parser.add_argument("--addr-file", default=None, help="Explicit addr file path (default: ~/.config/tms-takecar/addr.json)")
addr_keys_parser.set_defaults(handler=handle_addr_keys)
addr_get_value_parser = subparsers.add_parser("addr-get-value", help="Get one value from addr.json by key")
addr_get_value_parser.add_argument("key", help="Address alias key")
addr_get_value_parser.add_argument("--addr-file", default=None, help="Explicit addr file path (default: ~/.config/tms-takecar/addr.json)")
addr_get_value_parser.set_defaults(handler=handle_addr_get_value)
addr_upsert_value_parser = subparsers.add_parser("addr-upsert-value", help="Create or update one value in addr.json by key")
addr_upsert_value_parser.add_argument("key", help="Address alias key")
addr_upsert_value_parser.add_argument("value_json", help="JSON object to store for the given key")
addr_upsert_value_parser.add_argument("--addr-file", default=None, help="Explicit addr file path (default: ~/.config/tms-takecar/addr.json)")
addr_upsert_value_parser.set_defaults(handler=handle_addr_upsert_value)
addr_sync_state_parser = subparsers.add_parser("addr-sync-to-state", help="Sync one addr.json value into pickup/dropoff in state.json by key and scene")
addr_sync_state_parser.add_argument("key", help="Address alias key")
addr_sync_state_parser.add_argument("--scene", type=int, choices=[1, 2], required=True, help="1:pickup, 2:dropoff")
addr_sync_state_parser.add_argument("--addr-file", default=None, help="Explicit addr file path (default: ~/.config/tms-takecar/addr.json)")
addr_sync_state_parser.add_argument("--state-file", default=None, help="Explicit state file path (default: ~/.config/tms-takecar/state.json)")
addr_sync_state_parser.set_defaults(handler=handle_addr_sync_to_state)
short_cut_keys_parser = subparsers.add_parser("short-cut-keys", help="Return all keys from short-cut.json")
short_cut_keys_parser.add_argument("--short-cut-file", default=None, help="Explicit short-cut file path (default: ~/.config/tms-takecar/short-cut.json)")
short_cut_keys_parser.set_defaults(handler=handle_short_cut_keys)
short_cut_get_value_parser = subparsers.add_parser("short-cut-get-value", help="Get one value from short-cut.json by key")
short_cut_get_value_parser.add_argument("key", help="Normalized shortcut scene key")
short_cut_get_value_parser.add_argument("--short-cut-file", default=None, help="Explicit short-cut file path (default: ~/.config/tms-takecar/short-cut.json)")
short_cut_get_value_parser.set_defaults(handler=handle_short_cut_get_value)
short_cut_upsert_value_parser = subparsers.add_parser("short-cut-upsert-value", help="Create or update one value in short-cut.json by key")
short_cut_upsert_value_parser.add_argument("key", help="Normalized shortcut scene key")
short_cut_upsert_value_parser.add_argument("value_json", help="JSON object to store for the given key")
short_cut_upsert_value_parser.add_argument("--short-cut-file", default=None, help="Explicit short-cut file path (default: ~/.config/tms-takecar/short-cut.json)")
short_cut_upsert_value_parser.set_defaults(handler=handle_short_cut_upsert_value)
poi_search_parser = subparsers.add_parser("poi-search", help="Search POIs and optional on/off points by keyword")
poi_search_parser.add_argument("--keyword", required=True, help="POI search keyword")
poi_search_parser.add_argument("--city-name", default="", help="City name")
poi_search_parser.add_argument("--page-size", type=int, default=3, help="Page size")
poi_search_parser.add_argument("--page-index", type=int, default=1, help="Page index")
poi_search_parser.add_argument("--scene", type=int, choices=[0, 1, 2], default=0, help="0:poi, 1:pickup, 2:dropoff")
poi_search_parser.add_argument("--state-file", default=None, help="Explicit state file path (default: ~/.config/tms-takecar/state.json)")
poi_search_parser.set_defaults(handler=handle_poi_search)
static_map_url_parser = subparsers.add_parser("build-static-map-url", help="Build a Tencent static map URL from marker coordinates")
static_map_url_parser.add_argument("--markers-json", required=True, help='JSON array, e.g. [{"latitude":39.9,"longitude":116.4,"marker":"起"}]')
static_map_url_parser.set_defaults(handler=handle_build_static_map_url)
estimate_price_parser = subparsers.add_parser("estimate-price", help="Estimate ride price from state.json")
estimate_price_parser.add_argument("--state-file", default=None, help="Explicit state file path (default: ~/.config/tms-takecar/state.json)")
estimate_price_parser.set_defaults(handler=handle_estimate_price)
select_poi_parser = subparsers.add_parser("select-poi", help="Select one POI candidate by scene and poiid")
select_poi_parser.add_argument("--poiid", required=True, help="POI ID selected by user")
select_poi_parser.add_argument("--scene", type=int, choices=[1, 2], required=True, help="1:pickup, 2:dropoff")
select_poi_parser.add_argument("--state-file", default=None, help="Explicit state file path (default: ~/.config/tms-takecar/state.json)")
select_poi_parser.set_defaults(handler=handle_select_poi)
create_order_parser = subparsers.add_parser("create-order", help="Create a ride order with risk verification from state.json")
create_order_parser.add_argument("--state-file", default=None, help="Explicit state file path (default: ~/.config/tms-takecar/state.json)")
create_order_parser.add_argument("--price-estimate-keys", default=None, help="JSON array of selected priceEstimateKeys; updates estimate.products[].userSelect before ordering")
create_order_parser.add_argument("--user-prefer-labels", default=None, help="JSON array of userPreferLabel ints; updates estimate.userPreferLabels before ordering")
create_order_parser.set_defaults(handler=handle_create_order)
query_ongoing_order_parser = subparsers.add_parser("query-ongoing-order", help="Query whether current user has an ongoing order")
query_ongoing_order_parser.set_defaults(handler=handle_query_ongoing_order)
cancel_order_parser = subparsers.add_parser("cancel-order", help="Cancel an ongoing ride order")
cancel_order_parser.add_argument("--confirm", action="store_true", default=False, help="Confirm cancellation after fee check")
cancel_order_parser.add_argument("--reason", default="", help="Cancellation reason")
cancel_order_parser.set_defaults(handler=handle_cancel_order)
query_order_parser = subparsers.add_parser("query-order", help="Query ride order status, driver and vehicle info")
query_order_parser.set_defaults(handler=handle_query_order)
query_driver_location_parser = subparsers.add_parser("query-driver-location", help="Query real-time driver location for an order")
query_driver_location_parser.set_defaults(handler=handle_query_driver_location)
return parser
def write_json(stream, payload: dict) -> None:
stream.write(json.dumps(payload, ensure_ascii=False, indent=2))
stream.write("\n")
def handle_preflight(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del args, stderr
result = build_preflight_result(environ)
write_json(stdout, result)
if result.get("next_actions") == ["ready"]:
return 0
return 1
def handle_save_token(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ, stderr
write_json(stdout, persist_token(args.token, config_file=args.config_file))
return 0
def handle_delete_token(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ, stderr
write_json(stdout, delete_token(config_file=args.config_file))
return 0
def handle_set_resident_city(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
payload = set_resident_city(args.city_name, env_file=args.env_file)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, payload)
return 0
def handle_get_resident_city(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ, stderr
write_json(stdout, get_resident_city(env_file=args.env_file))
return 0
def handle_delete_state(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ, stderr
write_json(stdout, delete_state_file(state_file=args.state_file))
return 0
def handle_addr_keys(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ, stderr
write_json(stdout, get_addr_keys(addr_file=args.addr_file))
return 0
def handle_addr_get_value(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
payload = get_addr_value_by_key(args.key, addr_file=args.addr_file)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, payload)
return 0
def handle_addr_upsert_value(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
value = _parse_json_object_arg(args.value_json, "addr value")
payload = upsert_addr_value(args.key, value, addr_file=args.addr_file)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, payload)
return 0
def handle_addr_sync_to_state(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
payload = sync_addr_value_to_state(
args.key,
scene=args.scene,
addr_file=args.addr_file,
state_file=args.state_file,
)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, payload)
return 0
def handle_short_cut_keys(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ, stderr
write_json(stdout, get_short_cut_keys(short_cut_file=args.short_cut_file))
return 0
def handle_short_cut_get_value(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
payload = get_short_cut_value_by_key(args.key, short_cut_file=args.short_cut_file)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, payload)
return 0
def handle_short_cut_upsert_value(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
value = _parse_json_object_arg(args.value_json, "short-cut value")
payload = upsert_short_cut_value(args.key, value, short_cut_file=args.short_cut_file)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, payload)
return 0
def run_poi_search_api(environ, stdout, stderr, payload, scene, page_index, state_file=None, urlopen_func=urllib.request.urlopen):
token = load_token(environ)
base_url = DEFAULT_API_BASE.strip()
if not base_url:
write_json(stdout, {"mode": "blocked", "reason": "DEFAULT_API_BASE is not configured", "payload": payload})
return 2
headers = build_headers(token)
# Step 1: Base suggestion search
try:
base_result = post_request(
base_url,
DEFAULT_POI_SUGGESTION_PATH,
headers,
payload,
token=token,
urlopen_func=urlopen_func,
timeout_seconds=DEFAULT_HTTP_TIMEOUT_SECONDS,
)
except urllib.error.HTTPError as exc:
error_body = exc.read().decode("utf-8", errors="replace")
write_json(stderr, {"mode": "error", "status": exc.code, "body": error_body})
return 3
except urllib.error.URLError as exc:
write_json(
stderr,
{
"mode": "error",
"reason": str(exc),
"hint": "Request timed out after 60s",
},
)
return 4
base_body = base_result.get("body")
if is_invalid_token_error(base_body):
write_json(stderr, {"mode": "error", "reason": "token is missing or invalid", "body": base_body})
return 1
data_list = parse_suggestion_data_list(base_body)
if scene == 0:
result = flatten_poi_search_result(base_result, page_index)
write_json(stdout, {"mode": "searched", "result": result})
return 0
# Step 2: For each base item, call secondary API
all_items = []
for data_item in data_list:
if not isinstance(data_item, dict):
continue
loc = data_item.get("location", {})
lat = loc.get("lat", 0) if isinstance(loc, dict) else 0
lng = loc.get("lng", 0) if isinstance(loc, dict) else 0
poi_id = str(data_item.get("id", ""))
base_info = {
"name": data_item.get("title", ""),
"address": data_item.get("address", ""),
"longitude": lng,
"latitude": lat,
"poiid": poi_id,
"citycode": str(data_item.get("adcode", "")),
}
if scene == 1:
secondary_payload = build_get_on_points_payload(lat, lng)
secondary_path = DEFAULT_GET_ON_POINTS_PATH
else:
secondary_payload = build_get_drop_off_point_payload(poi_id, lat, lng)
secondary_path = DEFAULT_GET_DROP_OFF_POINT_PATH
try:
secondary_result = post_request(
base_url,
secondary_path,
headers,
secondary_payload,
token=token,
urlopen_func=urlopen_func,
timeout_seconds=DEFAULT_HTTP_TIMEOUT_SECONDS,
)
except (urllib.error.HTTPError, urllib.error.URLError):
all_items.append({**base_info, "point_name": "", "point_longitude": "", "point_latitude": ""})
continue
secondary_body = secondary_result.get("body")
if scene == 1:
points = extract_on_points_from_response(secondary_body)
else:
points = extract_drop_off_points_from_response(secondary_body)
if points:
for pt in points:
all_items.append({**base_info, **pt})
else:
all_items.append({**base_info, "point_name": "", "point_longitude": "", "point_latitude": ""})
output_payload = {
"mode": "searched",
"result": {
"status": base_result.get("status", 200),
"body": {"items": all_items, "page_index": page_index},
},
}
written_state_file = update_state_from_poi_search(scene=scene, items=all_items, state_file=state_file)
if written_state_file:
output_payload["state"] = {"state_file": written_state_file, "updated": True}
write_json(stdout, output_payload)
return 0
def handle_poi_search(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
try:
payload = build_poi_search_payload(args)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
return run_poi_search_api(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
scene=args.scene,
page_index=args.page_index,
state_file=args.state_file,
urlopen_func=urllib.request.urlopen,
)
def handle_build_static_map_url(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
try:
markers = _parse_static_map_markers_json(args.markers_json)
token = load_token(environ)
if not token:
stderr.write("Token is not configured. Run: save-token <token>\n")
return 1
url = build_static_map_url(
markers=markers,
token=token,
)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, {"url": url})
return 0
def handle_select_poi(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
del environ
try:
result = apply_select_poi(poiid=args.poiid, scene=args.scene, state_file=args.state_file)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
write_json(stdout, result)
return 0
def handle_estimate_price(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
state = load_state_file(state_file=args.state_file)
try:
payload = build_estimate_price_payload_from_state(state)
prefer_labels = _validate_user_prefer_labels(state.get("userPreferLabels", []))
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
def _write_json_with_state(stream, api_payload: dict) -> None:
if stream is stdout and isinstance(api_payload, dict) and api_payload.get("mode") == "estimated":
result_obj = api_payload.get("result")
state_file = update_state_from_estimate_result(result_obj, user_prefer_labels=prefer_labels, state_file=args.state_file)
if state_file:
api_payload = dict(api_payload)
api_payload["state"] = {"state_file": state_file, "updated": True}
write_json(stream, api_payload)
return run_json_api_command(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
api_path=DEFAULT_ESTIMATE_PRICE_PATH,
success_mode="estimated",
load_token_func=load_token,
build_headers_func=build_headers,
write_json_func=_write_json_with_state,
default_api_base=DEFAULT_API_BASE,
result_transform=transform_estimate_price_result,
urlopen_func=urllib.request.urlopen,
)
def handle_create_order(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
state = load_state_file(state_file=args.state_file)
state_modified = False
if getattr(args, "price_estimate_keys", None):
try:
price_estimate_keys = _parse_price_estimate_keys_json(args.price_estimate_keys)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
estimate = state.get("estimate")
if isinstance(estimate, dict):
products = estimate.get("products")
if isinstance(products, list):
key_set = set(price_estimate_keys)
for item in products:
if not isinstance(item, dict):
continue
key = item.get("priceEstimateKey")
item["userSelect"] = 1 if (isinstance(key, str) and key in key_set) else 0
state_modified = True
if getattr(args, "user_prefer_labels", None):
try:
prefer_labels = _parse_prefer_labels_json(args.user_prefer_labels)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
estimate = state.get("estimate")
if isinstance(estimate, dict):
estimate["userPreferLabels"] = prefer_labels
state_modified = True
if state_modified:
save_state_file(state, state_file=args.state_file)
try:
payload = build_create_order_payload_from_state(state)
except ValueError as exc:
stderr.write(f"{exc}\n")
return 2
def _write_json_with_state(stream, api_payload: dict) -> None:
if stream is stdout and isinstance(api_payload, dict) and api_payload.get("mode") == "ordered":
result_obj = api_payload.get("result")
state_file = update_state_from_create_order_result(result_obj, state_file=args.state_file)
if state_file:
api_payload = dict(api_payload)
api_payload["state"] = {"state_file": state_file, "updated": True}
write_json(stream, api_payload)
return run_json_api_command(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
api_path=DEFAULT_CREATE_ORDER_PATH,
success_mode="ordered",
load_token_func=load_token,
build_headers_func=build_headers,
write_json_func=_write_json_with_state,
default_api_base=DEFAULT_API_BASE,
result_transform=transform_create_order_result,
urlopen_func=urllib.request.urlopen,
)
def handle_query_ongoing_order(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
payload = build_query_ongoing_order_payload(args)
return run_json_api_command(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
api_path=DEFAULT_QUERY_ONGOING_ORDER_PATH,
success_mode="queried",
load_token_func=load_token,
build_headers_func=build_headers,
write_json_func=write_json,
result_transform=transform_query_ongoing_result,
default_api_base=DEFAULT_API_BASE,
urlopen_func=urllib.request.urlopen,
)
def handle_cancel_order(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
order_id = get_ongoing_order_id(environ, stdout, stderr)
if not order_id:
return 5
if not args.confirm:
fee_payload = build_cancel_order_payload(order_id, False, args.reason)
fee_result, error_code = post_cancel_order_payload(environ, fee_payload, stderr)
if error_code is not None:
return error_code
fee_body = fee_result.get("body") if isinstance(fee_result, dict) else None
fee_data = fee_body.get("data") if isinstance(fee_body, dict) else None
if not isinstance(fee_data, dict):
write_json(
stderr,
{
"mode": "error",
"reason": "cancel fee response missing data",
"body": fee_body,
},
)
return 3
waiver_fee = fee_data.get("waiverFee")
amount = fee_data.get("amount", 0)
if waiver_fee is False:
amount_yuan = round((amount or 0) / 100, 2)
write_json(
stdout,
{
"mode": "cancel_fee_required",
"amount": amount,
"amount_yuan": amount_yuan,
"message": f"取消订单需支付 {amount_yuan:.2f} 元取消费,确认取消请传入 --confirm",
"result": fee_result,
},
)
return 6
payload = build_cancel_order_payload(order_id, True, args.reason)
return run_json_api_command(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
api_path=DEFAULT_CANCEL_ORDER_PATH,
success_mode="cancelled",
load_token_func=load_token,
build_headers_func=build_headers,
write_json_func=write_json,
default_api_base=DEFAULT_API_BASE,
urlopen_func=urllib.request.urlopen,
)
def handle_query_order(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
order_id = get_ongoing_order_id(environ, stdout, stderr)
if not order_id:
return 5
payload = build_query_order_payload(order_id)
return run_json_api_command(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
api_path=DEFAULT_QUERY_ORDER_PATH,
success_mode="queried",
load_token_func=load_token,
build_headers_func=build_headers,
write_json_func=write_json,
result_transform=transform_query_order_result,
default_api_base=DEFAULT_API_BASE,
urlopen_func=urllib.request.urlopen,
)
def handle_query_driver_location(args: argparse.Namespace, environ: dict, stdout, stderr) -> int:
if not check_token(environ, stderr):
return 1
order_id = get_ongoing_order_id(environ, stdout, stderr)
if not order_id:
return 5
payload = build_query_driver_location_payload(order_id)
return run_json_api_command(
environ=environ,
stdout=stdout,
stderr=stderr,
payload=payload,
api_path=DEFAULT_QUERY_DRIVER_LOCATION_PATH,
success_mode="queried",
load_token_func=load_token,
build_headers_func=build_headers,
write_json_func=write_json,
result_transform=transform_query_driver_location_result,
default_api_base=DEFAULT_API_BASE,
urlopen_func=urllib.request.urlopen,
)
def main(argv: Optional[List[str]] = None, environ: Optional[dict] = None, stdout=None, stderr=None) -> int:
environ = os.environ if environ is None else environ
stdout = sys.stdout if stdout is None else stdout
stderr = sys.stderr if stderr is None else stderr
parser = build_parser()
args = parser.parse_args(argv)
if not hasattr(args, "handler"):
parser.print_usage(stderr)
stderr.write("tms_takecar.py: error: the following arguments are required: command\n")
return 2
return args.handler(args, environ, stdout, stderr)
if __name__ == "__main__":
raise SystemExit(main())
FILE:LICENSE.txt
Tencent is pleased to support the open source community by making Tencent Ride Skill available.
Copyright (C) 2026 Tencent. All rights reserved.
Tencent Ride Skill is licensed under the MIT-0.
Terms of the MIT-0:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.腾讯云 TKE 容器服务全栈运维专家,支持集群管理、K8s 资源操作、Pod 排障、Helm 部署、TCR 镜像仓库管理
---
name: tke-skill
description: 腾讯云 TKE 容器服务全栈运维专家,支持集群管理、K8s 资源操作、Pod 排障、Helm 部署、TCR 镜像仓库管理
allowed-tools: Read, Bash, Write
---
# TKE 全栈运维专家
你是腾讯云容器服务 (TKE) 全栈运维专家。通过两个 CLI 工具管理 TKE 集群和 Kubernetes 资源:
- `tke_cli.py` — 腾讯云 API 操作(集群管理、TCR 镜像仓库)
- `k8s_cli.py` — Kubernetes 集群内操作(资源管理、Pod 操作、Helm 部署)
## 凭证配置
### 腾讯云凭证(tke_cli.py 使用)
支持两种方式(命令行参数优先):
1. **环境变量**:`TENCENTCLOUD_SECRET_ID` / `TENCENTCLOUD_SECRET_KEY`
2. **命令行参数**:`--secret-id` / `--secret-key`
### Kubeconfig(k8s_cli.py 使用)
支持四级优先级(自动解析):
1. `--kubeconfig` 参数指定文件路径
2. `--cluster-id` + `--region` 自动从 TKE API 获取(显式指定集群时优先)
3. `KUBECONFIG` 环境变量
4. `~/.kube/config` 默认路径
## 前置依赖
| 工具 | 用途 | 安装 |
|------|------|------|
| Python 3 | 运行脚本 | 系统自带 |
| tencentcloud-sdk-python-tke | TKE 云 API | `pip install tencentcloud-sdk-python-tke` |
| tencentcloud-sdk-python-tcr | TCR 云 API | `pip install tencentcloud-sdk-python-tcr` |
| kubectl | K8s 资源操作 | [安装指南](https://kubernetes.io/docs/tasks/tools/) |
| helm | Helm 包管理 | [安装指南](https://helm.sh/docs/intro/install/) |
---
## 第一部分:集群管理(tke_cli.py)
所有命令通过 Bash 工具执行,基础格式:
```bash
python {baseDirectory}/tke_cli.py <command> --region <region> [参数]
```
### 1. clusters - 查询集群列表
```bash
python {baseDirectory}/tke_cli.py clusters --region ap-guangzhou
python {baseDirectory}/tke_cli.py clusters --region ap-guangzhou --cluster-ids cls-xxx cls-yyy
python {baseDirectory}/tke_cli.py clusters --region ap-guangzhou --cluster-type MANAGED_CLUSTER --limit 10
```
### 2. cluster-status - 查询集群状态
```bash
python {baseDirectory}/tke_cli.py cluster-status --region ap-guangzhou
python {baseDirectory}/tke_cli.py cluster-status --region ap-guangzhou --cluster-ids cls-xxx
```
### 3. cluster-level - 查询集群规格
```bash
python {baseDirectory}/tke_cli.py cluster-level --region ap-guangzhou
python {baseDirectory}/tke_cli.py cluster-level --region ap-guangzhou --cluster-id cls-xxx
```
### 4. endpoints - 查询集群访问地址
```bash
python {baseDirectory}/tke_cli.py endpoints --region ap-guangzhou --cluster-id cls-xxx
```
### 5. endpoint-status - 查询集群端点状态
```bash
python {baseDirectory}/tke_cli.py endpoint-status --region ap-guangzhou --cluster-id cls-xxx
python {baseDirectory}/tke_cli.py endpoint-status --region ap-guangzhou --cluster-id cls-xxx --is-extranet
```
### 6. kubeconfig - 获取集群 kubeconfig
```bash
python {baseDirectory}/tke_cli.py kubeconfig --region ap-guangzhou --cluster-id cls-xxx
python {baseDirectory}/tke_cli.py kubeconfig --region ap-guangzhou --cluster-id cls-xxx --is-extranet
```
### 7. node-pools - 查询节点池
```bash
python {baseDirectory}/tke_cli.py node-pools --region ap-guangzhou --cluster-id cls-xxx
```
### 8. create-endpoint - 开启集群访问端点
参数说明:
- `--cluster-id`(必填):集群ID
- `--is-extranet`:开启外网访问,不指定则默认开启内网
- `--subnet-id`:子网ID,开启内网时必填,必须为集群所在 VPC 内的子网
- `--security-group`:安全组ID,开启外网且不使用已有 CLB 时必填
- `--existed-lb-id`:使用已有 CLB 开启访问(内网/外网均可用)
- `--domain`:自定义域名
- `--extensive-parameters`:创建 LB 的扩展参数(JSON 字符串),仅外网访问时使用
```bash
# 开启内网访问
python {baseDirectory}/tke_cli.py create-endpoint --region ap-guangzhou --cluster-id cls-xxx --subnet-id subnet-xxx
# 开启外网访问
python {baseDirectory}/tke_cli.py create-endpoint --region ap-guangzhou --cluster-id cls-xxx --is-extranet --security-group sg-xxx
# 使用已有CLB
python {baseDirectory}/tke_cli.py create-endpoint --region ap-guangzhou --cluster-id cls-xxx --existed-lb-id lb-xxx
```
### 9. delete-endpoint - 关闭集群访问端点
```bash
python {baseDirectory}/tke_cli.py delete-endpoint --region ap-guangzhou --cluster-id cls-xxx
python {baseDirectory}/tke_cli.py delete-endpoint --region ap-guangzhou --cluster-id cls-xxx --is-extranet
```
### 10. tcr-instances - 查询 TCR 实例列表
```bash
python {baseDirectory}/tke_cli.py tcr-instances --region ap-guangzhou
python {baseDirectory}/tke_cli.py tcr-instances --region ap-guangzhou --instance-name my-tcr
python {baseDirectory}/tke_cli.py tcr-instances --region ap-guangzhou --all-instances
```
### 11. tcr-repos - 查询镜像仓库列表
```bash
python {baseDirectory}/tke_cli.py tcr-repos --region ap-guangzhou --registry-id tcr-xxx
python {baseDirectory}/tke_cli.py tcr-repos --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns
python {baseDirectory}/tke_cli.py tcr-repos --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app
```
### 12. tcr-create-repo - 创建镜像仓库
```bash
python {baseDirectory}/tke_cli.py tcr-create-repo --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app
python {baseDirectory}/tke_cli.py tcr-create-repo --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app --brief-description "我的应用"
```
### 13. tcr-delete-repo - 删除镜像仓库
```bash
python {baseDirectory}/tke_cli.py tcr-delete-repo --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app
```
### 14. tcr-images - 查询镜像版本列表
```bash
python {baseDirectory}/tke_cli.py tcr-images --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app
python {baseDirectory}/tke_cli.py tcr-images --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app --image-version v1.0
```
### 15. tcr-create-instance - 创建 TCR 实例
参数说明:
- `--registry-name`(必填):实例名称
- `--registry-type`(必填):实例类型,可选 `basic`(基础版)、`standard`(标准版)、`premium`(高级版)
- `--charge-type`:计费类型,0=按量计费(默认),1=预付费
- `--deletion-protection`:开启删除保护
```bash
python {baseDirectory}/tke_cli.py tcr-create-instance --region ap-guangzhou --registry-name my-tcr --registry-type basic
python {baseDirectory}/tke_cli.py tcr-create-instance --region ap-guangzhou --registry-name my-tcr --registry-type standard --charge-type 0
python {baseDirectory}/tke_cli.py tcr-create-instance --region ap-guangzhou --registry-name my-tcr --registry-type premium --deletion-protection
```
### 16. tcr-delete-instance - 删除 TCR 实例
参数说明:
- `--registry-id`(必填):TCR 实例 ID
- `--delete-bucket`:同时删除关联的 COS 存储桶
```bash
python {baseDirectory}/tke_cli.py tcr-delete-instance --region ap-guangzhou --registry-id tcr-xxx
python {baseDirectory}/tke_cli.py tcr-delete-instance --region ap-guangzhou --registry-id tcr-xxx --delete-bucket
```
### 17. tcr-namespaces - 查询 TCR 命名空间列表
```bash
python {baseDirectory}/tke_cli.py tcr-namespaces --region ap-guangzhou --registry-id tcr-xxx
python {baseDirectory}/tke_cli.py tcr-namespaces --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns
```
### 18. tcr-create-ns - 创建 TCR 命名空间
参数说明:
- `--registry-id`(必填):TCR 实例 ID
- `--namespace-name`(必填):命名空间名称
- `--is-public`:设为公开命名空间(默认私有)
```bash
python {baseDirectory}/tke_cli.py tcr-create-ns --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns
python {baseDirectory}/tke_cli.py tcr-create-ns --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns --is-public
```
### 19. tcr-delete-ns - 删除 TCR 命名空间
```bash
python {baseDirectory}/tke_cli.py tcr-delete-ns --region ap-guangzhou --registry-id tcr-xxx --namespace-name my-ns
```
---
## 第二部分:K8s 资源操作(k8s_cli.py)
基础格式:
```bash
python {baseDirectory}/k8s_cli.py <command> -n <namespace> [参数]
```
使用 TKE 集群时可自动获取 kubeconfig:
```bash
python {baseDirectory}/k8s_cli.py <command> --cluster-id cls-xxx --region ap-guangzhou -n <namespace> [参数]
```
### K8s 资源操作
#### 1. get - 查看资源
```bash
# 查看 Pod
python {baseDirectory}/k8s_cli.py get pods -n default
python {baseDirectory}/k8s_cli.py get pods -A
python {baseDirectory}/k8s_cli.py get pods -n default -o wide
python {baseDirectory}/k8s_cli.py get pods -n default -l app=nginx
# 查看其他资源
python {baseDirectory}/k8s_cli.py get deployments -n default
python {baseDirectory}/k8s_cli.py get services -n default
python {baseDirectory}/k8s_cli.py get nodes -o wide
python {baseDirectory}/k8s_cli.py get pvc -n default
python {baseDirectory}/k8s_cli.py get ingress -n default
```
#### 2. describe - 详细描述资源
```bash
python {baseDirectory}/k8s_cli.py describe pod my-pod -n default
python {baseDirectory}/k8s_cli.py describe deployment my-app -n default
python {baseDirectory}/k8s_cli.py describe node node-01
python {baseDirectory}/k8s_cli.py describe service my-svc -n default
```
#### 3. apply - 应用 YAML 资源清单
```bash
python {baseDirectory}/k8s_cli.py apply -f deployment.yaml -n default
python {baseDirectory}/k8s_cli.py apply -f ./manifests/ -n production
python {baseDirectory}/k8s_cli.py apply -k ./overlays/prod -n production
# 试运行(不实际创建)
python {baseDirectory}/k8s_cli.py apply -f deployment.yaml -n default --dry-run server
```
#### 4. delete - 删除资源
```bash
python {baseDirectory}/k8s_cli.py delete pod my-pod -n default
python {baseDirectory}/k8s_cli.py delete deployment my-app -n default
python {baseDirectory}/k8s_cli.py delete -f deployment.yaml -n default
python {baseDirectory}/k8s_cli.py delete pods -n default -l app=test
```
#### 5. create - 快速创建资源
```bash
python {baseDirectory}/k8s_cli.py create deployment my-app --image nginx:1.25 -n default
python {baseDirectory}/k8s_cli.py create deployment my-app --image nginx:1.25 --replicas 3 --port 80 -n default
# 生成 YAML 而不实际创建
python {baseDirectory}/k8s_cli.py create deployment my-app --image nginx:1.25 --dry-run client -o yaml -n default
```
#### 6. events - 查看事件
```bash
python {baseDirectory}/k8s_cli.py events -n default
python {baseDirectory}/k8s_cli.py events -A
python {baseDirectory}/k8s_cli.py events -n default --field-selector involvedObject.name=my-pod
python {baseDirectory}/k8s_cli.py events -n default -w
```
### Pod 操作
#### 7. logs - 查看 Pod 日志
```bash
python {baseDirectory}/k8s_cli.py logs my-pod -n default
python {baseDirectory}/k8s_cli.py logs my-pod -n default --tail 100
python {baseDirectory}/k8s_cli.py logs my-pod -n default -f
python {baseDirectory}/k8s_cli.py logs my-pod -n default --previous
python {baseDirectory}/k8s_cli.py logs my-pod -n default -c my-container
python {baseDirectory}/k8s_cli.py logs my-pod -n default --since 1h --timestamps
python {baseDirectory}/k8s_cli.py logs -n default -l app=nginx --all-containers
```
#### 8. exec - 在容器中执行命令
```bash
python {baseDirectory}/k8s_cli.py exec my-pod -n default -- ls /app
python {baseDirectory}/k8s_cli.py exec my-pod -n default -- cat /etc/resolv.conf
python {baseDirectory}/k8s_cli.py exec my-pod -n default -c sidecar -- env
python {baseDirectory}/k8s_cli.py exec my-pod -n default -- wget -qO- http://localhost:8080/healthz
```
#### 9. top - 查看资源使用情况
```bash
python {baseDirectory}/k8s_cli.py top pods -n default
python {baseDirectory}/k8s_cli.py top pods -n default --sort-by cpu
python {baseDirectory}/k8s_cli.py top pods -n default --containers
python {baseDirectory}/k8s_cli.py top nodes
```
### Helm 操作
#### 10. helm-install - 安装 Chart
```bash
python {baseDirectory}/k8s_cli.py helm-install my-release bitnami/nginx -n default
python {baseDirectory}/k8s_cli.py helm-install my-release ./mychart -n production --create-namespace
python {baseDirectory}/k8s_cli.py helm-install my-release bitnami/nginx -n default -f values.yaml --wait --atomic
python {baseDirectory}/k8s_cli.py helm-install my-release bitnami/nginx -n default --set image.tag=1.25 --version 15.0.0
python {baseDirectory}/k8s_cli.py helm-install my-release oci://registry.example.com/charts/myapp -n default --version 1.0.0
# 试运行
python {baseDirectory}/k8s_cli.py helm-install my-release bitnami/nginx -n default --dry-run
```
#### 11. helm-upgrade - 升级 Release
```bash
python {baseDirectory}/k8s_cli.py helm-upgrade my-release bitnami/nginx -n default --set image.tag=1.26
python {baseDirectory}/k8s_cli.py helm-upgrade my-release ./mychart -n production -f values-prod.yaml --atomic --wait
python {baseDirectory}/k8s_cli.py helm-upgrade my-release bitnami/nginx -n default --install --reuse-values
```
#### 12. helm-uninstall - 卸载 Release
```bash
python {baseDirectory}/k8s_cli.py helm-uninstall my-release -n default
python {baseDirectory}/k8s_cli.py helm-uninstall my-release -n default --keep-history
```
#### 13. helm-list - 列出 Release
```bash
python {baseDirectory}/k8s_cli.py helm-list -n default
python {baseDirectory}/k8s_cli.py helm-list -A
python {baseDirectory}/k8s_cli.py helm-list -n default -o json
python {baseDirectory}/k8s_cli.py helm-list -n default --filter "nginx.*"
```
#### 14. helm-status - 查看 Release 状态
```bash
python {baseDirectory}/k8s_cli.py helm-status my-release -n default
python {baseDirectory}/k8s_cli.py helm-status my-release -n default --show-resources
python {baseDirectory}/k8s_cli.py helm-status my-release -n default -o json
```
### Context / Kubeconfig 管理
#### 15. context-list - 列出所有 context
```bash
python {baseDirectory}/k8s_cli.py context-list
python {baseDirectory}/k8s_cli.py context-list -o name
python {baseDirectory}/k8s_cli.py context-list --kubeconfig ~/.kube/config
```
#### 16. context-use - 切换当前 context
> 注意:此命令会修改 kubeconfig 文件中的 current-context 字段。仅适用于持久化的 kubeconfig 文件(--kubeconfig / KUBECONFIG 环境变量 / ~/.kube/config),不适用于通过 --cluster-id 自动获取的临时 kubeconfig。
```bash
python {baseDirectory}/k8s_cli.py context-use my-cluster-context
python {baseDirectory}/k8s_cli.py context-use production-context --kubeconfig ~/.kube/config
```
#### 17. context-current - 显示当前 context
```bash
python {baseDirectory}/k8s_cli.py context-current
python {baseDirectory}/k8s_cli.py context-current --kubeconfig ~/.kube/config
```
#### 18. kubeconfig-add - 合并外部 kubeconfig
将外部 kubeconfig 文件合并到当前配置中,支持 --dry-run 预览。
目标文件优先级:--kubeconfig 参数 > KUBECONFIG 环境变量 > ~/.kube/config
```bash
# 预览合并结果(不写入)
python {baseDirectory}/k8s_cli.py kubeconfig-add --from-file /tmp/new-cluster.kubeconfig --dry-run
# 合并到默认 kubeconfig
python {baseDirectory}/k8s_cli.py kubeconfig-add --from-file /tmp/new-cluster.kubeconfig
# 合并到指定文件
python {baseDirectory}/k8s_cli.py kubeconfig-add --from-file /tmp/new-cluster.kubeconfig --kubeconfig ~/.kube/multi-cluster.config
```
### RBAC 租户管理
#### 19. rbac-create-tenant - 创建租户
为租户自动创建 ServiceAccount + Role + RoleBinding,4 种角色模板:
- `readonly`: 只读权限(get/list/watch)
- `developer`: 开发者权限(查看 + 管理工作负载)
- `admin`: 管理员权限(绑定 K8s 内置 ClusterRole admin)
- `custom`: 自定义规则(需配合 --rules-file)
```bash
python {baseDirectory}/k8s_cli.py rbac-create-tenant zhangsan --role readonly -n team-a
python {baseDirectory}/k8s_cli.py rbac-create-tenant lisi --role developer -n team-b
python {baseDirectory}/k8s_cli.py rbac-create-tenant wangwu --role admin -n team-c
python {baseDirectory}/k8s_cli.py rbac-create-tenant custom-user --role custom -n team-d --rules-file /path/to/rules.yaml
# 试运行
python {baseDirectory}/k8s_cli.py rbac-create-tenant zhangsan --role developer -n team-a --dry-run server
```
#### 20. rbac-list-tenants - 列出所有租户
```bash
python {baseDirectory}/k8s_cli.py rbac-list-tenants -n team-a
python {baseDirectory}/k8s_cli.py rbac-list-tenants -A
```
#### 21. rbac-delete-tenant - 删除租户
删除顺序:RoleBinding → Role → ServiceAccount,通过 label 批量删除。
```bash
python {baseDirectory}/k8s_cli.py rbac-delete-tenant zhangsan -n team-a
```
#### 22. rbac-get-token - 获取租户 Token
使用 `kubectl create token`(K8s 1.24+)创建短期 Token。
```bash
python {baseDirectory}/k8s_cli.py rbac-get-token zhangsan -n team-a
python {baseDirectory}/k8s_cli.py rbac-get-token zhangsan -n team-a --duration 8760h
python {baseDirectory}/k8s_cli.py rbac-get-token zhangsan -n team-a -o json
```
#### 23. prompt-generate - 为租户生成一键安装 Prompt
生成包含 kubeconfig + Token + 安装指引的完整 Prompt 文本,可直接发给租户用户。
```bash
python {baseDirectory}/k8s_cli.py prompt-generate zhangsan -n team-a
python {baseDirectory}/k8s_cli.py prompt-generate zhangsan -n team-a --cluster-name my-tke-cluster --duration 8760h
```
---
## 标准操作流程
### 集群巡检
1. `tke_cli.py clusters` 获取所有集群列表
2. `tke_cli.py cluster-status` 检查每个集群运行状态
3. `tke_cli.py node-pools --cluster-id cls-xxx` 检查节点池健康
4. `k8s_cli.py get nodes -o wide` 检查节点状态
5. `k8s_cli.py top nodes` 检查节点资源使用
6. `k8s_cli.py get pods -A` 检查 Pod 运行状态
7. 汇总输出:集群名称、状态、节点数、资源使用率、异常项
### 获取集群访问凭证
1. `tke_cli.py endpoints --cluster-id cls-xxx` 查看是否已开启访问
2. 如未开启,使用 `tke_cli.py create-endpoint` 开启内网或外网访问
3. `tke_cli.py endpoint-status --cluster-id cls-xxx` 确认端点状态为 Created
4. `tke_cli.py kubeconfig --cluster-id cls-xxx` 获取 kubeconfig
5. 指引用户保存 kubeconfig 并配置 kubectl
### 应用部署流程
1. 编写或准备 YAML 资源清单
2. `k8s_cli.py apply -f deployment.yaml -n production --dry-run server` 试运行验证
3. `k8s_cli.py apply -f deployment.yaml -n production` 实际部署
4. `k8s_cli.py get pods -n production -l app=my-app -w` 监听 Pod 启动
5. `k8s_cli.py describe deployment my-app -n production` 确认部署状态
6. `k8s_cli.py logs <pod-name> -n production --tail 50` 检查应用日志
### Pod 排障流程
1. `k8s_cli.py get pods -n <namespace>` 查看 Pod 状态
2. `k8s_cli.py describe pod <pod-name> -n <namespace>` 查看详细信息和事件
3. `k8s_cli.py logs <pod-name> -n <namespace>` 查看当前日志
4. `k8s_cli.py logs <pod-name> -n <namespace> --previous` 查看崩溃前日志
5. `k8s_cli.py events -n <namespace> --field-selector involvedObject.name=<pod-name>` 查看相关事件
6. `k8s_cli.py exec <pod-name> -n <namespace> -- <cmd>` 进入容器检查
7. `k8s_cli.py top pods -n <namespace>` 检查资源使用
### Helm 部署流程
1. `k8s_cli.py helm-install my-release <chart> -n production --dry-run` 预览
2. `k8s_cli.py helm-install my-release <chart> -n production --atomic --wait` 安装
3. `k8s_cli.py helm-status my-release -n production --show-resources` 确认状态
4. `k8s_cli.py get pods -n production -l app.kubernetes.io/instance=my-release` 确认 Pod
### 镜像发布流程
1. `tke_cli.py tcr-instances --region ap-guangzhou` 查看 TCR 实例
2. 如无实例,`tke_cli.py tcr-create-instance --region ap-guangzhou --registry-name my-tcr --registry-type basic` 创建
3. `tke_cli.py tcr-namespaces --registry-id tcr-xxx` 查看命名空间
4. 如无命名空间,`tke_cli.py tcr-create-ns --registry-id tcr-xxx --namespace-name my-ns` 创建
5. `tke_cli.py tcr-create-repo --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app` 创建仓库(如需要)
6. 用户本地执行 `docker push <tcr-addr>/my-ns/my-app:v1.0` 推送镜像
7. `tke_cli.py tcr-images --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app` 确认镜像
### 集群规格评估
1. `tke_cli.py cluster-level` 查看所有可用规格及资源限制
2. `tke_cli.py clusters` 查看当前集群规格
3. 对比当前使用量与规格上限,给出升降配建议
### 多集群 Context 管理
1. `k8s_cli.py context-list` 查看所有可用 context
2. `k8s_cli.py context-use <context-name>` 切换到目标集群
3. `k8s_cli.py context-current` 确认当前 context
4. 如需添加新集群:`k8s_cli.py kubeconfig-add --from-file <kubeconfig>` 合并到当前配置
### 租户创建与分发
1. `k8s_cli.py rbac-create-tenant <name> --role developer -n <ns>` 创建租户
2. `k8s_cli.py rbac-get-token <name> -n <ns> --duration 8760h` 获取 Token
3. `k8s_cli.py prompt-generate <name> -n <ns>` 生成一键安装 Prompt
4. 将生成的 Prompt 通过安全渠道发送给租户用户
5. `k8s_cli.py rbac-list-tenants -A` 确认租户列表
---
## 安全约束
### 部署资源时必须遵守
- 所有容器**必须设置** resource requests 和 limits
- 必须包含 liveness 和 readiness 健康检查
- 使用 Secrets 存储敏感数据,**禁止**明文写入 ConfigMap 或环境变量
- 应用 Pod **禁止**使用 default ServiceAccount
- 生产镜像**禁止**使用 `latest` 标签
- 容器应以非 root 用户运行(除非有充分理由)
- 设置 `readOnlyRootFilesystem: true` 和 `allowPrivilegeEscalation: false`
### 网络安全
- 实施 NetworkPolicy 进行网络隔离
- 不暴露不必要的端口和服务
- 使用 RBAC 实现最小权限原则
### 租户管理安全
- `rbac-create-tenant`、`rbac-delete-tenant` 为写操作,使用前请确认
- Token 默认有效期建议不超过 8760h(1 年),定期轮换
- `custom` 角色需审核 `--rules-file` 内容,避免授予过高权限
- `prompt-generate` 生成的内容包含 Token,请通过安全渠道传输
---
## 排障参考
### 常见 Pod 状态及排查
| 状态 | 含义 | 排查步骤 |
|------|------|----------|
| Pending | 等待调度 | `describe pod` 查原因 → `top nodes` 查资源 → 检查 nodeSelector/affinity |
| ContainerCreating | 创建中 | `describe pod` 查事件 → 检查镜像拉取、Volume 挂载 |
| CrashLoopBackOff | 持续崩溃 | `logs --previous` 查崩溃日志 → 检查 livenessProbe → 检查 resource limits |
| ImagePullBackOff | 拉取镜像失败 | 检查镜像名称/Tag → 检查 imagePullSecrets → 确认 TCR 权限 |
| OOMKilled | 内存不足被杀 | `describe pod` 确认 → 增大 memory limits → 排查内存泄漏 |
| Evicted | 被驱逐 | `describe pod` 查原因 → `top nodes` 查节点资源压力 |
---
## YAML 模板参考
### 标准 Deployment + Service
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
labels:
app: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: my-app-sa
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
containers:
- name: my-app
image: my-registry/my-app:1.0.0
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: production
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
type: ClusterIP
```
---
## 输出规范
- 查询结果优先以**表格形式**呈现关键信息
- 对于集群列表,展示:集群ID、名称、状态、版本、节点数、地域
- 对于节点池,展示:节点池ID、名称、节点数、机型、状态
- 对于 Pod 列表,展示:Pod 名称、状态、重启次数、运行时间、节点
- 对于 Helm Release,展示:名称、命名空间、版本、状态、更新时间
- JSON 原始数据可作为补充展示
- 异常状态用明确文字标注
## 注意事项
- `tke_cli.py` 所有命令默认地域为 `ap-guangzhou`,如需查询其他地域请指定 `--region`
- `k8s_cli.py` 所有命令默认命名空间为 `default`,如需操作其他命名空间请指定 `-n`
- 凭证不会被记录到日志或输出中
- `create-endpoint`、`delete-endpoint`、`tcr-create-instance`、`tcr-delete-instance`、`tcr-create-ns`、`tcr-delete-ns`、`tcr-create-repo`、`tcr-delete-repo` 为写操作,使用前请确认
- `apply`、`delete`、`create`、`helm-install`、`helm-upgrade`、`helm-uninstall`、`rbac-create-tenant`、`rbac-delete-tenant` 为写操作,建议先用 `--dry-run` 预览
- `kubeconfig-add` 会修改 kubeconfig 文件,建议先用 `--dry-run` 预览合并结果
- 其他命令均为只读查询,不会修改集群状态
FILE:rbac_templates.yaml
# RBAC 角色模板配置
# 每个模板定义了对应角色的 Kubernetes RBAC 规则
# 由 k8s_cli.py rbac-create-tenant 命令使用
templates:
readonly:
description: "只读权限 - 查看集群资源"
cluster_role: false
rules:
- apiGroups: [""]
resources: ["pods", "services", "endpoints", "configmaps", "secrets",
"persistentvolumeclaims", "events", "namespaces",
"replicationcontrollers", "serviceaccounts"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "replicasets", "statefulsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
developer:
description: "开发者权限 - 查看+管理工作负载和配置"
cluster_role: false
rules:
# 全资源查看权限
- apiGroups: [""]
resources: ["pods", "services", "endpoints", "configmaps", "secrets",
"persistentvolumeclaims", "events", "namespaces",
"replicationcontrollers", "serviceaccounts"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments", "daemonsets", "replicasets", "statefulsets"]
verbs: ["get", "list", "watch"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses", "networkpolicies"]
verbs: ["get", "list", "watch"]
- apiGroups: ["autoscaling"]
resources: ["horizontalpodautoscalers"]
verbs: ["get", "list", "watch"]
# 工作负载管理权限
- apiGroups: ["apps"]
resources: ["deployments", "statefulsets"]
verbs: ["create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["services", "configmaps", "secrets", "pods"]
verbs: ["create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods/log", "pods/exec"]
verbs: ["get", "create"]
- apiGroups: ["batch"]
resources: ["jobs", "cronjobs"]
verbs: ["create", "update", "patch", "delete"]
admin:
description: "管理员权限 - 命名空间内完全控制(绑定 K8s 内置 ClusterRole admin)"
cluster_role: true
cluster_role_ref: "admin"
rules: []
# custom 模板不在此文件中定义 rules
# 用户通过 --rules-file 参数传入自定义规则文件
custom:
description: "自定义权限 - 用户自定义规则"
cluster_role: false
rules: []
FILE:k8s_cli.py
#!/usr/bin/env python3
"""
Kubernetes & Helm CLI - 轻量级 K8s 集群内操作命令行工具
封装 kubectl 和 helm 命令,支持通过 TKE API 自动获取 kubeconfig。
依赖:kubectl、helm 二进制(需提前安装)
"""
import argparse
import atexit
import json
import os
import shutil
import subprocess
import sys
import tempfile
# ========== 临时文件管理 ==========
_temp_files = []
def _cleanup_temp_files():
"""进程退出时清理临时文件"""
for f in _temp_files:
try:
if os.path.exists(f):
os.unlink(f)
except OSError:
pass
atexit.register(_cleanup_temp_files)
# ========== 工具函数 ==========
def check_binary(name):
"""检查命令行工具是否已安装"""
if not shutil.which(name):
print(f"错误:未找到 {name} 命令。请先安装 {name}。", file=sys.stderr)
sys.exit(1)
def resolve_kubeconfig(args):
"""
解析 kubeconfig 路径,优先级:
1. --kubeconfig 参数(显式指定文件)
2. --cluster-id + --region 从 TKE API 自动获取(显式指定集群)
3. KUBECONFIG 环境变量
4. ~/.kube/config 默认路径
设计原则:用户显式指定的参数(1、2)优先于隐式的环境配置(3、4)。
"""
# 1. --kubeconfig 参数(最高优先级)
kubeconfig = getattr(args, 'kubeconfig', None)
if kubeconfig:
if not os.path.isfile(kubeconfig):
print(f"错误:kubeconfig 文件不存在: {kubeconfig}", file=sys.stderr)
sys.exit(1)
return kubeconfig
# 2. --cluster-id 指定了 TKE 集群,从 API 自动获取
cluster_id = getattr(args, 'cluster_id', None)
region = getattr(args, 'region', None)
if cluster_id and region:
return fetch_kubeconfig_from_tke(args)
# 3. KUBECONFIG 环境变量
kubeconfig = os.getenv("KUBECONFIG")
if kubeconfig and os.path.isfile(kubeconfig):
return kubeconfig
# 4. ~/.kube/config 默认路径
default_path = os.path.expanduser("~/.kube/config")
if os.path.isfile(default_path):
return default_path
print("错误:未找到 kubeconfig。请通过以下方式之一提供:\n"
" 1. --kubeconfig <路径>\n"
" 2. --cluster-id + --region 自动从 TKE API 获取\n"
" 3. 设置 KUBECONFIG 环境变量\n"
" 4. 将 kubeconfig 放到 ~/.kube/config",
file=sys.stderr)
sys.exit(1)
def fetch_kubeconfig_from_tke(args):
"""通过 tke_cli.py 从 TKE API 获取 kubeconfig 并写入临时文件"""
script_dir = os.path.dirname(os.path.abspath(__file__))
tke_cli = os.path.join(script_dir, "tke_cli.py")
if not os.path.isfile(tke_cli):
print("错误:未找到 tke_cli.py,无法自动获取 kubeconfig。", file=sys.stderr)
sys.exit(1)
cmd = [sys.executable, tke_cli, "kubeconfig",
"--cluster-id", args.cluster_id,
"--region", args.region]
# 传递腾讯云凭证
secret_id = getattr(args, 'secret_id', None) or os.getenv("TENCENTCLOUD_SECRET_ID")
secret_key = getattr(args, 'secret_key', None) or os.getenv("TENCENTCLOUD_SECRET_KEY")
if secret_id:
cmd.extend(["--secret-id", secret_id])
if secret_key:
cmd.extend(["--secret-key", secret_key])
is_extranet = getattr(args, 'is_extranet', False)
if is_extranet:
cmd.append("--is-extranet")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"错误:从 TKE API 获取 kubeconfig 失败: {result.stderr}", file=sys.stderr)
sys.exit(1)
try:
data = json.loads(result.stdout)
kubeconfig_content = data.get("Kubeconfig", "")
if not kubeconfig_content:
print("错误:TKE API 返回的 kubeconfig 为空。请确认集群端点已开启。", file=sys.stderr)
sys.exit(1)
except (json.JSONDecodeError, KeyError) as e:
print(f"错误:解析 TKE API 返回的 kubeconfig 失败: {e}", file=sys.stderr)
sys.exit(1)
# 写入临时文件(进程退出时自动清理)
tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.kubeconfig', delete=False, prefix='tke-')
tmp.write(kubeconfig_content)
tmp.close()
os.chmod(tmp.name, 0o600)
_temp_files.append(tmp.name)
print(f"[info] 已从 TKE API 自动获取 kubeconfig,临时文件: {tmp.name}", file=sys.stderr)
return tmp.name
def run_command(cmd, env=None):
"""执行命令并输出结果"""
result = subprocess.run(cmd, capture_output=True, text=True, env=env)
if result.stdout:
print(result.stdout, end='')
if result.returncode != 0:
if result.stderr:
print(result.stderr, end='', file=sys.stderr)
sys.exit(result.returncode)
return result
def build_kubeconfig_args(args):
"""构建 --kubeconfig 参数"""
kubeconfig = resolve_kubeconfig(args)
if kubeconfig:
return ["--kubeconfig", kubeconfig]
return []
def print_json(data):
"""格式化输出 JSON"""
print(json.dumps(data, indent=2, ensure_ascii=False))
# ========== K8s 资源操作命令 ==========
def cmd_get(args):
"""查看 K8s 资源"""
check_binary("kubectl")
cmd = ["kubectl", "get", args.resource]
if args.name:
cmd.append(args.name)
if args.all_namespaces:
cmd.append("-A")
else:
cmd.extend(["-n", args.namespace])
if args.output:
cmd.extend(["-o", args.output])
if args.selector:
cmd.extend(["-l", args.selector])
if args.show_labels:
cmd.append("--show-labels")
if args.watch:
cmd.append("-w")
if args.sort_by:
cmd.extend(["--sort-by", args.sort_by])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_describe(args):
"""详细描述 K8s 资源"""
check_binary("kubectl")
cmd = ["kubectl", "describe", args.resource]
if args.name:
cmd.append(args.name)
cmd.extend(["-n", args.namespace])
if args.selector:
cmd.extend(["-l", args.selector])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_apply(args):
"""应用 YAML 资源清单"""
check_binary("kubectl")
cmd = ["kubectl", "apply"]
if args.filename:
cmd.extend(["-f", args.filename])
if args.kustomize:
cmd.extend(["-k", args.kustomize])
cmd.extend(["-n", args.namespace])
if args.dry_run:
cmd.extend(["--dry-run", args.dry_run])
if args.server_side:
cmd.append("--server-side")
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_delete(args):
"""删除 K8s 资源"""
check_binary("kubectl")
cmd = ["kubectl", "delete", args.resource]
if args.name:
cmd.append(args.name)
cmd.extend(["-n", args.namespace])
if args.filename:
cmd.extend(["-f", args.filename])
if args.selector:
cmd.extend(["-l", args.selector])
if args.force:
cmd.append("--force")
cmd.extend(["--grace-period", "0"])
if args.cascade:
cmd.extend(["--cascade", args.cascade])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_create(args):
"""快速创建 K8s 资源"""
check_binary("kubectl")
cmd = ["kubectl", "create", args.resource, args.name]
cmd.extend(["-n", args.namespace])
if args.image:
cmd.extend(["--image", args.image])
if args.replicas:
cmd.extend(["--replicas", str(args.replicas)])
if args.port:
cmd.extend(["--port", str(args.port)])
if args.dry_run:
cmd.extend(["--dry-run", args.dry_run])
if args.output:
cmd.extend(["-o", args.output])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_events(args):
"""查看 K8s 事件"""
check_binary("kubectl")
cmd = ["kubectl", "get", "events"]
if args.all_namespaces:
cmd.append("-A")
else:
cmd.extend(["-n", args.namespace])
if args.field_selector:
cmd.extend(["--field-selector", args.field_selector])
if args.sort_by:
cmd.extend(["--sort-by", args.sort_by])
else:
cmd.extend(["--sort-by", ".lastTimestamp"])
if args.watch:
cmd.append("-w")
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
# ========== Pod 操作命令 ==========
def cmd_logs(args):
"""查看 Pod 日志"""
check_binary("kubectl")
cmd = ["kubectl", "logs", args.pod]
cmd.extend(["-n", args.namespace])
if args.container:
cmd.extend(["-c", args.container])
if args.follow:
cmd.append("-f")
if args.previous:
cmd.append("--previous")
if args.tail is not None:
cmd.extend(["--tail", str(args.tail)])
if args.since:
cmd.extend(["--since", args.since])
if args.timestamps:
cmd.append("--timestamps")
if args.all_containers:
cmd.append("--all-containers")
if args.selector:
cmd.extend(["-l", args.selector])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_exec(args):
"""在容器中执行单条命令"""
check_binary("kubectl")
cmd = ["kubectl", "exec", args.pod]
cmd.extend(["-n", args.namespace])
if args.container:
cmd.extend(["-c", args.container])
# --kubeconfig 必须在 -- 之前,否则 kubectl 不识别
cmd.extend(build_kubeconfig_args(args))
cmd.append("--")
cmd.extend(args._exec_command)
run_command(cmd)
def cmd_top(args):
"""查看资源使用情况"""
check_binary("kubectl")
cmd = ["kubectl", "top", args.resource_type]
if args.name:
cmd.append(args.name)
if args.all_namespaces:
cmd.append("-A")
else:
cmd.extend(["-n", args.namespace])
if args.containers and args.resource_type == "pods":
cmd.append("--containers")
if args.sort_by:
cmd.extend(["--sort-by", args.sort_by])
if args.selector:
cmd.extend(["-l", args.selector])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
# ========== Context / Kubeconfig 管理命令 ==========
def cmd_context_list(args):
"""列出 kubeconfig 中所有 context"""
check_binary("kubectl")
cmd = ["kubectl", "config", "get-contexts"]
cmd.extend(build_kubeconfig_args(args))
if args.output:
cmd.extend(["-o", args.output])
run_command(cmd)
def cmd_context_use(args):
"""切换当前 context"""
check_binary("kubectl")
cmd = ["kubectl", "config", "use-context", args.context_name]
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_context_current(args):
"""显示当前 context"""
check_binary("kubectl")
cmd = ["kubectl", "config", "current-context"]
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_kubeconfig_add(args):
"""合并外部 kubeconfig 到当前配置"""
check_binary("kubectl")
source = args.from_file
if not os.path.isfile(source):
print(f"错误:文件不存在: {source}", file=sys.stderr)
sys.exit(1)
# 目标 kubeconfig
target = getattr(args, 'kubeconfig', None) or os.getenv("KUBECONFIG") or os.path.expanduser("~/.kube/config")
# 使用 KUBECONFIG 环境变量合并多个文件
merge_env = os.environ.copy()
merge_env["KUBECONFIG"] = f"{target}:{source}"
cmd = ["kubectl", "config", "view", "--flatten"]
result = subprocess.run(cmd, capture_output=True, text=True, env=merge_env)
if result.returncode != 0:
print(f"错误:合并 kubeconfig 失败: {result.stderr}", file=sys.stderr)
sys.exit(result.returncode)
if args.dry_run:
# 仅预览合并结果,不写入文件
print(result.stdout, end='')
return
# 写回目标文件
os.makedirs(os.path.dirname(os.path.abspath(target)), exist_ok=True)
with open(target, 'w') as f:
f.write(result.stdout)
print(f"已将 {source} 合并到 {target}")
# ========== RBAC 租户管理命令 ==========
def load_rbac_template(template_name, rules_file=None):
"""加载 RBAC 角色模板"""
import yaml
script_dir = os.path.dirname(os.path.abspath(__file__))
template_path = os.path.join(script_dir, "rbac_templates.yaml")
if not os.path.isfile(template_path):
print(f"错误:未找到 RBAC 模板文件: {template_path}", file=sys.stderr)
sys.exit(1)
with open(template_path, 'r') as f:
config = yaml.safe_load(f)
templates = config.get("templates", {})
if template_name not in templates:
valid = ", ".join(templates.keys())
print(f"错误:未知角色模板 '{template_name}',可选: {valid}", file=sys.stderr)
sys.exit(1)
template = templates[template_name]
# custom 模板需要 --rules-file
if template_name == "custom":
if not rules_file:
print("错误:custom 模板需要通过 --rules-file 指定自定义规则文件", file=sys.stderr)
sys.exit(1)
if not os.path.isfile(rules_file):
print(f"错误:规则文件不存在: {rules_file}", file=sys.stderr)
sys.exit(1)
with open(rules_file, 'r') as f:
custom_rules = yaml.safe_load(f)
template["rules"] = custom_rules.get("rules", custom_rules)
return template
def generate_rbac_yaml(tenant_name, namespace, template):
"""生成租户 RBAC 资源的 YAML 清单"""
import yaml
sa_name = f"tke-tenant-{tenant_name}"
role_name = f"tke-tenant-{tenant_name}-role"
binding_name = f"tke-tenant-{tenant_name}-binding"
# 公共 labels,用于识别和查询 TKE Skill 管理的租户资源
labels = {
"app.kubernetes.io/managed-by": "tke-skill",
"tke-skill/tenant": tenant_name,
"tke-skill/component": "rbac"
}
resources = []
# 1. ServiceAccount
resources.append({
"apiVersion": "v1",
"kind": "ServiceAccount",
"metadata": {
"name": sa_name,
"namespace": namespace,
"labels": labels
}
})
if template.get("cluster_role") and template.get("cluster_role_ref"):
# admin 模式:RoleBinding 绑定内置 ClusterRole
resources.append({
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {
"name": binding_name,
"namespace": namespace,
"labels": labels
},
"subjects": [{
"kind": "ServiceAccount",
"name": sa_name,
"namespace": namespace
}],
"roleRef": {
"kind": "ClusterRole",
"name": template["cluster_role_ref"],
"apiGroup": "rbac.authorization.k8s.io"
}
})
else:
# readonly / developer / custom:创建自定义 Role + RoleBinding
resources.append({
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "Role",
"metadata": {
"name": role_name,
"namespace": namespace,
"labels": labels
},
"rules": template.get("rules", [])
})
resources.append({
"apiVersion": "rbac.authorization.k8s.io/v1",
"kind": "RoleBinding",
"metadata": {
"name": binding_name,
"namespace": namespace,
"labels": labels
},
"subjects": [{
"kind": "ServiceAccount",
"name": sa_name,
"namespace": namespace
}],
"roleRef": {
"kind": "Role",
"name": role_name,
"apiGroup": "rbac.authorization.k8s.io"
}
})
# 多文档 YAML 输出
return yaml.dump_all(resources, default_flow_style=False, allow_unicode=True)
def cmd_rbac_create_tenant(args):
"""创建租户(ServiceAccount + Role + RoleBinding)"""
check_binary("kubectl")
template = load_rbac_template(args.role, getattr(args, 'rules_file', None))
yaml_content = generate_rbac_yaml(args.tenant_name, args.namespace, template)
# 写入临时文件并 kubectl apply
tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False, prefix='rbac-')
tmp.write(yaml_content)
tmp.close()
try:
cmd = ["kubectl", "apply", "-f", tmp.name]
cmd.extend(["-n", args.namespace])
cmd.extend(build_kubeconfig_args(args))
if args.dry_run:
cmd.append(f"--dry-run={args.dry_run}")
run_command(cmd)
finally:
os.unlink(tmp.name)
def cmd_rbac_list_tenants(args):
"""列出所有 TKE Skill 管理的租户"""
check_binary("kubectl")
cmd = ["kubectl", "get", "serviceaccount",
"-l", "app.kubernetes.io/managed-by=tke-skill",
"-o", "json"]
if args.all_namespaces:
cmd.append("-A")
else:
cmd.extend(["-n", args.namespace])
cmd.extend(build_kubeconfig_args(args))
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
if result.stderr:
print(result.stderr, end='', file=sys.stderr)
sys.exit(result.returncode)
data = json.loads(result.stdout)
items = data.get("items", [])
if not items:
print("未找到 TKE Skill 管理的租户。")
return
# 格式化输出
tenants = []
for sa in items:
labels = sa["metadata"].get("labels", {})
tenants.append({
"tenant": labels.get("tke-skill/tenant", "unknown"),
"namespace": sa["metadata"]["namespace"],
"service_account": sa["metadata"]["name"],
"created": sa["metadata"].get("creationTimestamp", ""),
})
print_json(tenants)
def cmd_rbac_delete_tenant(args):
"""删除租户的所有 RBAC 资源"""
check_binary("kubectl")
label_selector = f"tke-skill/tenant={args.tenant_name},app.kubernetes.io/managed-by=tke-skill"
# 删除顺序:RoleBinding -> Role -> ServiceAccount
for resource_type in ["rolebinding", "role", "serviceaccount"]:
cmd = ["kubectl", "delete", resource_type,
"-l", label_selector,
"-n", args.namespace]
cmd.extend(build_kubeconfig_args(args))
# 不用 run_command,因为某些资源可能不存在(如 admin 没有 Role)
result = subprocess.run(cmd, capture_output=True, text=True)
if result.stdout:
print(result.stdout, end='')
def cmd_rbac_get_token(args):
"""获取租户 ServiceAccount 的访问 Token"""
check_binary("kubectl")
sa_name = f"tke-tenant-{args.tenant_name}"
# 使用 kubectl create token(K8s 1.24+)
cmd = ["kubectl", "create", "token", sa_name,
"-n", args.namespace]
if args.duration:
cmd.extend(["--duration", args.duration])
cmd.extend(build_kubeconfig_args(args))
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"错误:获取 token 失败: {result.stderr}", file=sys.stderr)
sys.exit(result.returncode)
token = result.stdout.strip()
if args.output == "json":
print_json({
"tenant": args.tenant_name,
"namespace": args.namespace,
"service_account": sa_name,
"token": token
})
else:
print(token)
def cmd_prompt_generate(args):
"""为租户生成一键安装 Prompt"""
check_binary("kubectl")
sa_name = f"tke-tenant-{args.tenant_name}"
# 1. 获取 token
cmd = ["kubectl", "create", "token", sa_name,
"-n", args.namespace, "--duration", args.duration or "8760h"]
cmd.extend(build_kubeconfig_args(args))
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"错误:获取 token 失败: {result.stderr}", file=sys.stderr)
sys.exit(result.returncode)
token = result.stdout.strip()
# 2. 获取集群 API Server 地址
cmd2 = ["kubectl", "config", "view", "--minify", "-o",
"jsonpath={.clusters[0].cluster.server}"]
cmd2.extend(build_kubeconfig_args(args))
result2 = subprocess.run(cmd2, capture_output=True, text=True)
server = result2.stdout.strip()
# 3. 获取集群 CA 证书
cmd3 = ["kubectl", "config", "view", "--minify", "--raw", "-o",
"jsonpath={.clusters[0].cluster.certificate-authority-data}"]
cmd3.extend(build_kubeconfig_args(args))
result3 = subprocess.run(cmd3, capture_output=True, text=True)
ca_data = result3.stdout.strip()
# 4. 生成 kubeconfig YAML
cluster_name = args.cluster_name or "tke-cluster"
kubeconfig_yaml = f"""apiVersion: v1
kind: Config
clusters:
- cluster:
certificate-authority-data: {ca_data}
server: {server}
name: {cluster_name}
contexts:
- context:
cluster: {cluster_name}
namespace: {args.namespace}
user: {sa_name}
name: {sa_name}@{cluster_name}
current-context: {sa_name}@{cluster_name}
users:
- name: {sa_name}
user:
token: {token}
"""
# 5. 生成 Prompt
prompt = f"""# TKE Skill 一键安装 Prompt
# 租户: {args.tenant_name} | 命名空间: {args.namespace} | 集群: {cluster_name}
## 步骤 1:保存 kubeconfig
将以下内容保存到 ~/.kube/tke-tenant-{args.tenant_name}.config:
```yaml
{kubeconfig_yaml}```
## 步骤 2:配置环境变量
```bash
export KUBECONFIG=~/.kube/tke-tenant-{args.tenant_name}.config
```
## 步骤 3:安装 TKE Skill
将 tke-skill 目录复制到你的 AI Agent 的 Skill 目录中(如 ~/.codebuddy/skills/tke/)。
## 步骤 4:验证连接
```bash
python k8s_cli.py get pods -n {args.namespace}
```
"""
print(prompt)
# ========== Helm 操作命令 ==========
def cmd_helm_install(args):
"""安装 Helm Chart"""
check_binary("helm")
cmd = ["helm", "install", args.release, args.chart]
cmd.extend(["-n", args.namespace])
if args.create_namespace:
cmd.append("--create-namespace")
if args.values:
for v in args.values:
cmd.extend(["-f", v])
if args.set:
for s in args.set:
cmd.extend(["--set", s])
if args.version:
cmd.extend(["--version", args.version])
if args.repo:
cmd.extend(["--repo", args.repo])
if args.wait:
cmd.append("--wait")
if args.timeout:
cmd.extend(["--timeout", args.timeout])
if args.dry_run:
cmd.append("--dry-run")
if args.atomic:
cmd.append("--atomic")
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_helm_upgrade(args):
"""升级 Helm Release"""
check_binary("helm")
cmd = ["helm", "upgrade", args.release, args.chart]
cmd.extend(["-n", args.namespace])
if args.install:
cmd.append("--install")
if args.values:
for v in args.values:
cmd.extend(["-f", v])
if args.set:
for s in args.set:
cmd.extend(["--set", s])
if args.version:
cmd.extend(["--version", args.version])
if args.repo:
cmd.extend(["--repo", args.repo])
if args.wait:
cmd.append("--wait")
if args.timeout:
cmd.extend(["--timeout", args.timeout])
if args.dry_run:
cmd.append("--dry-run")
if args.atomic:
cmd.append("--atomic")
if args.reuse_values:
cmd.append("--reuse-values")
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_helm_uninstall(args):
"""卸载 Helm Release"""
check_binary("helm")
cmd = ["helm", "uninstall", args.release]
cmd.extend(["-n", args.namespace])
if args.keep_history:
cmd.append("--keep-history")
if args.wait:
cmd.append("--wait")
if args.timeout:
cmd.extend(["--timeout", args.timeout])
if args.dry_run:
cmd.append("--dry-run")
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_helm_list(args):
"""列出 Helm Release"""
check_binary("helm")
cmd = ["helm", "list"]
if args.all_namespaces:
cmd.append("-A")
else:
cmd.extend(["-n", args.namespace])
if args.all:
cmd.append("--all")
if args.output:
cmd.extend(["-o", args.output])
if args.filter:
cmd.extend(["--filter", args.filter])
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
def cmd_helm_status(args):
"""查看 Helm Release 状态"""
check_binary("helm")
cmd = ["helm", "status", args.release]
cmd.extend(["-n", args.namespace])
if args.output:
cmd.extend(["-o", args.output])
if args.show_resources:
cmd.append("--show-resources")
cmd.extend(build_kubeconfig_args(args))
run_command(cmd)
# ========== 主入口 ==========
def main():
# 公共参数
common_parser = argparse.ArgumentParser(add_help=False)
common_parser.add_argument("--kubeconfig", help="kubeconfig 文件路径")
common_parser.add_argument("-n", "--namespace", default="default", help="命名空间(默认 default)")
common_parser.add_argument("--cluster-id", dest="cluster_id", help="TKE 集群 ID(用于自动获取 kubeconfig)")
common_parser.add_argument("--region", default="ap-guangzhou", help="地域(配合 --cluster-id 使用,默认 ap-guangzhou)")
common_parser.add_argument("--secret-id", dest="secret_id", help="腾讯云 SecretId(配合 --cluster-id 使用)")
common_parser.add_argument("--secret-key", dest="secret_key", help="腾讯云 SecretKey(配合 --cluster-id 使用)")
common_parser.add_argument(
"--is-extranet", dest="is_extranet", action="store_true",
help="使用外网 kubeconfig(配合 --cluster-id 使用)")
parser = argparse.ArgumentParser(
description="Kubernetes & Helm CLI - 轻量级 K8s 集群内操作工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python k8s_cli.py get pods -n default
python k8s_cli.py logs my-pod -n default --tail 100
python k8s_cli.py apply -f deployment.yaml
python k8s_cli.py helm-install my-release bitnami/nginx -n default
python k8s_cli.py get pods --cluster-id cls-xxx --region ap-guangzhou
"""
)
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# ---- K8s 资源操作 ----
# get
p = subparsers.add_parser("get", parents=[common_parser], help="查看 K8s 资源")
p.add_argument("resource", help="资源类型(如 pods, deployments, services, nodes 等)")
p.add_argument("name", nargs="?", help="资源名称(可选,不指定则列出所有)")
p.add_argument("-o", "--output", help="输出格式(wide, yaml, json, name 等)")
p.add_argument("-l", "--selector", help="标签选择器")
p.add_argument("-A", "--all-namespaces", dest="all_namespaces", action="store_true", help="查看所有命名空间")
p.add_argument("--show-labels", dest="show_labels", action="store_true", help="显示标签")
p.add_argument("-w", "--watch", action="store_true", help="持续监听变化")
p.add_argument("--sort-by", dest="sort_by", help="排序字段(如 .metadata.creationTimestamp)")
p.set_defaults(func=cmd_get)
# describe
p = subparsers.add_parser("describe", parents=[common_parser], help="详细描述 K8s 资源")
p.add_argument("resource", help="资源类型")
p.add_argument("name", nargs="?", help="资源名称(可选)")
p.add_argument("-l", "--selector", help="标签选择器")
p.set_defaults(func=cmd_describe)
# apply
p = subparsers.add_parser("apply", parents=[common_parser], help="应用 YAML 资源清单")
p.add_argument("-f", "--filename", help="YAML 文件路径或 URL")
p.add_argument("-k", "--kustomize", help="Kustomize 目录路径")
p.add_argument("--dry-run", dest="dry_run", choices=["client", "server"], help="试运行模式")
p.add_argument("--server-side", dest="server_side", action="store_true", help="服务端 apply")
p.set_defaults(func=cmd_apply)
# delete
p = subparsers.add_parser("delete", parents=[common_parser], help="删除 K8s 资源")
p.add_argument("resource", help="资源类型")
p.add_argument("name", nargs="?", help="资源名称")
p.add_argument("-f", "--filename", help="YAML 文件路径")
p.add_argument("-l", "--selector", help="标签选择器")
p.add_argument("--force", action="store_true", help="强制删除")
p.add_argument("--cascade", choices=["background", "orphan", "foreground"], help="级联删除策略")
p.set_defaults(func=cmd_delete)
# create
p = subparsers.add_parser("create", parents=[common_parser], help="快速创建 K8s 资源")
p.add_argument("resource", help="资源类型(如 deployment, service, configmap 等)")
p.add_argument("name", help="资源名称")
p.add_argument("--image", help="容器镜像(创建 deployment 时使用)")
p.add_argument("--replicas", type=int, help="副本数")
p.add_argument("--port", type=int, help="端口")
p.add_argument("--dry-run", dest="dry_run", choices=["client", "server"], help="试运行模式")
p.add_argument("-o", "--output", help="输出格式")
p.set_defaults(func=cmd_create)
# events
p = subparsers.add_parser("events", parents=[common_parser], help="查看 K8s 事件")
p.add_argument("-A", "--all-namespaces", dest="all_namespaces", action="store_true", help="查看所有命名空间")
p.add_argument("--field-selector", dest="field_selector", help="字段选择器(如 involvedObject.name=my-pod)")
p.add_argument("--sort-by", dest="sort_by", help="排序字段(默认 .lastTimestamp)")
p.add_argument("-w", "--watch", action="store_true", help="持续监听")
p.set_defaults(func=cmd_events)
# ---- Pod 操作 ----
# logs
p = subparsers.add_parser("logs", parents=[common_parser], help="查看 Pod 日志")
p.add_argument("pod", help="Pod 名称")
p.add_argument("-c", "--container", help="容器名称(多容器 Pod 时指定)")
p.add_argument("-f", "--follow", action="store_true", help="实时跟踪日志")
p.add_argument("--previous", action="store_true", help="查看上一个容器的日志(排查崩溃原因)")
p.add_argument("--tail", type=int, help="显示最后 N 行")
p.add_argument("--since", help="显示指定时间段内的日志(如 1h, 30m, 2h30m)")
p.add_argument("--timestamps", action="store_true", help="显示时间戳")
p.add_argument("--all-containers", dest="all_containers", action="store_true", help="显示所有容器日志")
p.add_argument("-l", "--selector", help="标签选择器(查看匹配 Pod 的日志)")
p.set_defaults(func=cmd_logs)
# exec(注意:容器命令通过 -- 分隔,在 main() 中手动拆分后存入 _exec_command)
p = subparsers.add_parser("exec", parents=[common_parser], help="在容器中执行命令")
p.add_argument("pod", help="Pod 名称")
p.add_argument("-c", "--container", help="容器名称")
p.set_defaults(func=cmd_exec)
# top
p = subparsers.add_parser("top", parents=[common_parser], help="查看资源使用情况")
p.add_argument("resource_type", choices=["pods", "nodes"], help="资源类型(pods 或 nodes)")
p.add_argument("name", nargs="?", help="资源名称(可选)")
p.add_argument("-A", "--all-namespaces", dest="all_namespaces", action="store_true", help="查看所有命名空间")
p.add_argument("--containers", action="store_true", help="显示容器级别的资源用量(仅 pods)")
p.add_argument("--sort-by", dest="sort_by", choices=["cpu", "memory"], help="排序方式")
p.add_argument("-l", "--selector", help="标签选择器")
p.set_defaults(func=cmd_top)
# ---- Context / Kubeconfig 管理 ----
# context-list
p = subparsers.add_parser("context-list", parents=[common_parser], help="列出所有 context")
p.add_argument("-o", "--output", choices=["name"], help="输出格式(name 只输出名称)")
p.set_defaults(func=cmd_context_list)
# context-use
p = subparsers.add_parser("context-use", parents=[common_parser], help="切换当前 context")
p.add_argument("context_name", help="要切换到的 context 名称")
p.set_defaults(func=cmd_context_use)
# context-current
p = subparsers.add_parser("context-current", parents=[common_parser], help="显示当前 context")
p.set_defaults(func=cmd_context_current)
# kubeconfig-add
p = subparsers.add_parser("kubeconfig-add", parents=[common_parser], help="合并外部 kubeconfig")
p.add_argument("--from-file", dest="from_file", required=True, help="要合并的 kubeconfig 文件路径")
p.add_argument("--dry-run", dest="dry_run", action="store_true", help="仅预览合并结果,不写入文件")
p.set_defaults(func=cmd_kubeconfig_add)
# ---- RBAC 租户管理 ----
# rbac-create-tenant
p = subparsers.add_parser("rbac-create-tenant", parents=[common_parser], help="创建租户(SA + Role + RoleBinding)")
p.add_argument("tenant_name", help="租户名称")
p.add_argument("--role", required=True, choices=["readonly", "developer", "admin", "custom"], help="角色模板")
p.add_argument("--rules-file", dest="rules_file", help="自定义规则文件路径(仅 custom 角色需要)")
p.add_argument("--dry-run", dest="dry_run", choices=["client", "server"], help="试运行模式")
p.set_defaults(func=cmd_rbac_create_tenant)
# rbac-list-tenants
p = subparsers.add_parser("rbac-list-tenants", parents=[common_parser], help="列出所有管理的租户")
p.add_argument("-A", "--all-namespaces", dest="all_namespaces", action="store_true", help="查看所有命名空间")
p.set_defaults(func=cmd_rbac_list_tenants)
# rbac-delete-tenant
p = subparsers.add_parser("rbac-delete-tenant", parents=[common_parser], help="删除租户 RBAC 资源")
p.add_argument("tenant_name", help="租户名称")
p.set_defaults(func=cmd_rbac_delete_tenant)
# rbac-get-token
p = subparsers.add_parser("rbac-get-token", parents=[common_parser], help="获取租户 Token")
p.add_argument("tenant_name", help="租户名称")
p.add_argument("--duration", help="Token 有效期(如 8760h、720h,默认由 K8s 控制)")
p.add_argument("-o", "--output", choices=["json"], help="输出格式")
p.set_defaults(func=cmd_rbac_get_token)
# prompt-generate
p = subparsers.add_parser("prompt-generate", parents=[common_parser], help="为租户生成一键安装 Prompt")
p.add_argument("tenant_name", help="租户名称")
p.add_argument("--cluster-name", dest="cluster_name", help="集群显示名称(默认 tke-cluster)")
p.add_argument("--duration", help="Token 有效期(默认 8760h)")
p.set_defaults(func=cmd_prompt_generate)
# ---- Helm 操作 ----
# helm-install
p = subparsers.add_parser("helm-install", parents=[common_parser], help="安装 Helm Chart")
p.add_argument("release", help="Release 名称")
p.add_argument("chart", help="Chart 名称或路径(如 bitnami/nginx 或 ./mychart)")
p.add_argument("-f", "--values", nargs="*", help="values 文件路径(可多个)")
p.add_argument("--set", nargs="*", help="覆盖 values(如 image.tag=v1.0)")
p.add_argument("--version", help="Chart 版本")
p.add_argument("--repo", help="Chart 仓库 URL")
p.add_argument("--create-namespace", dest="create_namespace", action="store_true", help="自动创建命名空间")
p.add_argument("--wait", action="store_true", help="等待所有资源就绪")
p.add_argument("--timeout", help="超时时间(如 5m0s)")
p.add_argument("--dry-run", dest="dry_run", action="store_true", help="试运行")
p.add_argument("--atomic", action="store_true", help="失败时自动回滚")
p.set_defaults(func=cmd_helm_install)
# helm-upgrade
p = subparsers.add_parser("helm-upgrade", parents=[common_parser], help="升级 Helm Release")
p.add_argument("release", help="Release 名称")
p.add_argument("chart", help="Chart 名称或路径")
p.add_argument("-f", "--values", nargs="*", help="values 文件路径")
p.add_argument("--set", nargs="*", help="覆盖 values")
p.add_argument("--version", help="Chart 版本")
p.add_argument("--repo", help="Chart 仓库 URL")
p.add_argument("--install", action="store_true", help="如果 Release 不存在则安装")
p.add_argument("--wait", action="store_true", help="等待所有资源就绪")
p.add_argument("--timeout", help="超时时间")
p.add_argument("--dry-run", dest="dry_run", action="store_true", help="试运行")
p.add_argument("--atomic", action="store_true", help="失败时自动回滚")
p.add_argument("--reuse-values", dest="reuse_values", action="store_true", help="复用上次的 values")
p.set_defaults(func=cmd_helm_upgrade)
# helm-uninstall
p = subparsers.add_parser("helm-uninstall", parents=[common_parser], help="卸载 Helm Release")
p.add_argument("release", help="Release 名称")
p.add_argument("--keep-history", dest="keep_history", action="store_true", help="保留发布历史")
p.add_argument("--wait", action="store_true", help="等待删除完成")
p.add_argument("--timeout", help="超时时间")
p.add_argument("--dry-run", dest="dry_run", action="store_true", help="试运行")
p.set_defaults(func=cmd_helm_uninstall)
# helm-list
p = subparsers.add_parser("helm-list", parents=[common_parser], help="列出 Helm Release")
p.add_argument("-A", "--all-namespaces", dest="all_namespaces", action="store_true", help="查看所有命名空间")
p.add_argument("--all", action="store_true", help="显示所有状态的 Release")
p.add_argument("-o", "--output", choices=["table", "json", "yaml"], help="输出格式")
p.add_argument("--filter", help="按名称过滤(支持正则)")
p.set_defaults(func=cmd_helm_list)
# helm-status
p = subparsers.add_parser("helm-status", parents=[common_parser], help="查看 Helm Release 状态")
p.add_argument("release", help="Release 名称")
p.add_argument("-o", "--output", choices=["table", "json", "yaml"], help="输出格式")
p.add_argument("--show-resources", dest="show_resources", action="store_true", help="显示关联的 K8s 资源")
p.set_defaults(func=cmd_helm_status)
# 解析并执行
# exec 特殊处理:在 argparse 解析前,手动拆分 -- 后的容器命令
# 这样 argparse 只解析 -- 前的参数,避免 REMAINDER 吞掉 --kubeconfig 等选项
argv = sys.argv[1:]
exec_command = []
if len(argv) > 0 and argv[0] == "exec":
if "--" in argv:
sep_idx = argv.index("--")
exec_command = argv[sep_idx + 1:] # -- 后面的部分
argv = argv[:sep_idx] # -- 前面的部分
args = parser.parse_args(argv)
if not args.command:
parser.print_help()
sys.exit(1)
# exec 命令:将拆分出的容器命令挂到 args 上
if args.command == "exec":
args._exec_command = exec_command
if not exec_command:
print("错误:请指定要执行的命令,如:exec my-pod -n default -- ls /app", file=sys.stderr)
sys.exit(1)
try:
args.func(args)
except KeyboardInterrupt:
sys.exit(130)
except (subprocess.SubprocessError, json.JSONDecodeError,
OSError, ValueError) as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
FILE:tke_cli.py
#!/usr/bin/env python3
"""
腾讯云 TKE CLI - 轻量级 TKE 集群管理与 TCR 镜像仓库命令行工具
支持通过环境变量或命令行参数传入腾讯云凭证。
依赖:pip install tencentcloud-sdk-python-tke tencentcloud-sdk-python-tcr
"""
import argparse
import json
import os
import sys
def get_credentials(args):
"""获取腾讯云凭证(命令行参数优先,环境变量兜底)"""
secret_id = getattr(args, 'secret_id', None) or os.getenv("TENCENTCLOUD_SECRET_ID")
secret_key = getattr(args, 'secret_key', None) or os.getenv("TENCENTCLOUD_SECRET_KEY")
if not secret_id or not secret_key:
print(
"错误:未提供腾讯云凭证。"
"请通过 --secret-id/--secret-key 参数"
"或环境变量 TENCENTCLOUD_SECRET_ID/"
"TENCENTCLOUD_SECRET_KEY 配置。",
file=sys.stderr)
sys.exit(1)
return secret_id, secret_key
def create_common_client(secret_id, secret_key, region, version="2018-05-25"):
"""创建腾讯云通用客户端"""
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.common_client import CommonClient
cred = credential.Credential(secret_id, secret_key)
http_profile = HttpProfile()
http_profile.endpoint = "tke.tencentcloudapi.com"
client_profile = ClientProfile()
client_profile.httpProfile = http_profile
return CommonClient("tke", version, cred, region, profile=client_profile)
def call_api(client, action, params=None):
"""调用 API 并返回解析后的 Response"""
resp = client.call(action, params or {})
data = json.loads(resp)
return data.get("Response", data)
def print_json(data):
"""格式化输出 JSON"""
print(json.dumps(data, indent=2, ensure_ascii=False))
# ========== 子命令实现 ==========
def cmd_clusters(args):
"""查询集群列表"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {}
if args.cluster_ids:
params["ClusterIds"] = args.cluster_ids
if args.cluster_type:
params["ClusterType"] = args.cluster_type
if args.limit is not None:
params["Limit"] = args.limit
if args.offset is not None:
params["Offset"] = args.offset
result = call_api(client, "DescribeClusters", params)
print_json(result)
def cmd_cluster_status(args):
"""查询集群状态"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {}
if args.cluster_ids:
params["ClusterIds"] = args.cluster_ids
result = call_api(client, "DescribeClusterStatus", params)
print_json(result)
def cmd_cluster_level(args):
"""查询集群规格"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {}
if args.cluster_id:
params["ClusterID"] = args.cluster_id
result = call_api(client, "DescribeClusterLevelAttribute", params)
print_json(result)
def cmd_endpoints(args):
"""查询集群访问地址"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {"ClusterId": args.cluster_id}
result = call_api(client, "DescribeClusterEndpoints", params)
print_json(result)
def cmd_endpoint_status(args):
"""查询集群端点状态"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {"ClusterId": args.cluster_id}
if args.is_extranet:
params["IsExtranet"] = True
result = call_api(client, "DescribeClusterEndpointStatus", params)
print_json(result)
def cmd_kubeconfig(args):
"""获取集群 kubeconfig"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {"ClusterId": args.cluster_id}
if args.is_extranet:
params["IsExtranet"] = True
result = call_api(client, "DescribeClusterKubeconfig", params)
print_json(result)
def cmd_create_endpoint(args):
"""创建集群访问端点(开启内网/外网访问)"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {"ClusterId": args.cluster_id}
if args.is_extranet:
params["IsExtranet"] = True
if args.security_group:
params["SecurityGroup"] = args.security_group
else:
if args.subnet_id:
params["SubnetId"] = args.subnet_id
if args.domain:
params["Domain"] = args.domain
if args.existed_lb_id:
params["ExistedLoadBalancerId"] = args.existed_lb_id
if args.extensive_parameters:
params["ExtensiveParameters"] = args.extensive_parameters
result = call_api(client, "CreateClusterEndpoint", params)
print_json(result)
def cmd_delete_endpoint(args):
"""删除集群访问端点(关闭内网/外网访问)"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region)
params = {"ClusterId": args.cluster_id}
if args.is_extranet:
params["IsExtranet"] = True
result = call_api(client, "DeleteClusterEndpoint", params)
print_json(result)
def cmd_node_pools(args):
"""查询节点池"""
secret_id, secret_key = get_credentials(args)
client = create_common_client(secret_id, secret_key, args.region, version="2022-05-01")
params = {"ClusterId": args.cluster_id}
if args.limit is not None:
params["Limit"] = args.limit
if args.offset is not None:
params["Offset"] = args.offset
result = call_api(client, "DescribeNodePools", params)
print_json(result)
# ========== TCR 镜像仓库命令 ==========
def create_tcr_client(secret_id, secret_key, region):
"""创建腾讯云 TCR 客户端"""
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.common.common_client import CommonClient
cred = credential.Credential(secret_id, secret_key)
http_profile = HttpProfile()
http_profile.endpoint = "tcr.tencentcloudapi.com"
client_profile = ClientProfile()
client_profile.httpProfile = http_profile
return CommonClient("tcr", "2019-09-24", cred, region, profile=client_profile)
def cmd_tcr_create_instance(args):
"""创建 TCR 实例"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {
"RegistryName": args.registry_name,
"RegistryType": args.registry_type,
}
if args.charge_type is not None:
params["RegistryChargeType"] = args.charge_type
if args.deletion_protection:
params["DeletionProtection"] = True
result = call_api(client, "CreateInstance", params)
print_json(result)
def cmd_tcr_delete_instance(args):
"""删除 TCR 实例"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {"RegistryId": args.registry_id}
if args.delete_bucket:
params["DeleteBucket"] = True
result = call_api(client, "DeleteInstance", params)
print_json(result)
def cmd_tcr_instances(args):
"""查询 TCR 实例列表"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {}
if args.instance_name:
params["Registryname"] = args.instance_name
if args.limit is not None:
params["Limit"] = args.limit
if args.offset is not None:
params["Offset"] = args.offset
if args.all_instances:
params["AllRegion"] = True
result = call_api(client, "DescribeInstances", params)
print_json(result)
def cmd_tcr_repos(args):
"""查询镜像仓库列表"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {"RegistryId": args.registry_id}
if args.namespace_name:
params["NamespaceName"] = args.namespace_name
if args.repository_name:
params["RepositoryName"] = args.repository_name
if args.limit is not None:
params["Limit"] = args.limit
if args.offset is not None:
params["Offset"] = args.offset
result = call_api(client, "DescribeRepositories", params)
print_json(result)
def cmd_tcr_create_repo(args):
"""创建镜像仓库"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {
"RegistryId": args.registry_id,
"NamespaceName": args.namespace_name,
"RepositoryName": args.repository_name,
}
if args.brief_description:
params["BriefDescription"] = args.brief_description
if args.description:
params["Description"] = args.description
result = call_api(client, "CreateRepository", params)
print_json(result)
def cmd_tcr_delete_repo(args):
"""删除镜像仓库"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {
"RegistryId": args.registry_id,
"NamespaceName": args.namespace_name,
"RepositoryName": args.repository_name,
}
result = call_api(client, "DeleteRepository", params)
print_json(result)
def cmd_tcr_images(args):
"""查询镜像版本列表"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {
"RegistryId": args.registry_id,
"NamespaceName": args.namespace_name,
"RepositoryName": args.repository_name,
}
if args.image_version:
params["ImageVersion"] = args.image_version
if args.limit is not None:
params["Limit"] = args.limit
if args.offset is not None:
params["Offset"] = args.offset
result = call_api(client, "DescribeImages", params)
print_json(result)
def cmd_tcr_namespaces(args):
"""查询 TCR 命名空间列表"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {"RegistryId": args.registry_id}
if args.namespace_name:
params["NamespaceName"] = args.namespace_name
if args.limit is not None:
params["Limit"] = args.limit
if args.offset is not None:
params["Offset"] = args.offset
result = call_api(client, "DescribeNamespaces", params)
print_json(result)
def cmd_tcr_create_ns(args):
"""创建 TCR 命名空间"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {
"RegistryId": args.registry_id,
"NamespaceName": args.namespace_name,
"IsPublic": args.is_public,
}
result = call_api(client, "CreateNamespace", params)
print_json(result)
def cmd_tcr_delete_ns(args):
"""删除 TCR 命名空间"""
secret_id, secret_key = get_credentials(args)
client = create_tcr_client(secret_id, secret_key, args.region)
params = {
"RegistryId": args.registry_id,
"NamespaceName": args.namespace_name,
}
result = call_api(client, "DeleteNamespace", params)
print_json(result)
# ========== 主入口 ==========
def main():
# 公共参数(通过 parents 共享给所有子命令)
common_parser = argparse.ArgumentParser(add_help=False)
common_parser.add_argument("--region", default="ap-guangzhou", help="地域(默认 ap-guangzhou)")
common_parser.add_argument("--secret-id", dest="secret_id", help="腾讯云 SecretId(优先于环境变量)")
common_parser.add_argument("--secret-key", dest="secret_key", help="腾讯云 SecretKey(优先于环境变量)")
parser = argparse.ArgumentParser(
description="腾讯云 TKE CLI - 集群管理与 TCR 镜像仓库工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
示例:
python tke_cli.py clusters --region ap-guangzhou
python tke_cli.py cluster-status --cluster-ids cls-xxx cls-yyy
python tke_cli.py endpoints --cluster-id cls-xxx --region ap-beijing
python tke_cli.py kubeconfig --cluster-id cls-xxx
python tke_cli.py node-pools --cluster-id cls-xxx
python tke_cli.py tcr-instances --region ap-guangzhou
python tke_cli.py tcr-repos --registry-id tcr-xxx --namespace-name my-ns
python tke_cli.py tcr-images --registry-id tcr-xxx --namespace-name my-ns --repository-name my-app
"""
)
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# clusters
p = subparsers.add_parser("clusters", parents=[common_parser], help="查询集群列表")
p.add_argument("--cluster-ids", nargs="*", dest="cluster_ids", help="集群ID列表")
p.add_argument(
"--cluster-type", dest="cluster_type",
choices=["MANAGED_CLUSTER", "INDEPENDENT_CLUSTER"],
help="集群类型")
p.add_argument("--limit", type=int, help="最大返回数量(默认20,最大100)")
p.add_argument("--offset", type=int, help="偏移量")
p.set_defaults(func=cmd_clusters)
# cluster-status
p = subparsers.add_parser("cluster-status", parents=[common_parser], help="查询集群状态")
p.add_argument("--cluster-ids", nargs="*", dest="cluster_ids", help="集群ID列表")
p.set_defaults(func=cmd_cluster_status)
# cluster-level
p = subparsers.add_parser("cluster-level", parents=[common_parser], help="查询集群规格")
p.add_argument("--cluster-id", dest="cluster_id", help="集群ID(查询特定集群可变配规格)")
p.set_defaults(func=cmd_cluster_level)
# endpoints
p = subparsers.add_parser("endpoints", parents=[common_parser], help="查询集群访问地址")
p.add_argument("--cluster-id", dest="cluster_id", required=True, help="集群ID")
p.set_defaults(func=cmd_endpoints)
# endpoint-status
p = subparsers.add_parser("endpoint-status", parents=[common_parser], help="查询集群端点状态")
p.add_argument("--cluster-id", dest="cluster_id", required=True, help="集群ID")
p.add_argument("--is-extranet", dest="is_extranet", action="store_true", help="是否查询外网端点")
p.set_defaults(func=cmd_endpoint_status)
# kubeconfig
p = subparsers.add_parser("kubeconfig", parents=[common_parser], help="获取集群 kubeconfig")
p.add_argument("--cluster-id", dest="cluster_id", required=True, help="集群ID")
p.add_argument("--is-extranet", dest="is_extranet", action="store_true", help="是否获取外网 kubeconfig")
p.set_defaults(func=cmd_kubeconfig)
# node-pools
p = subparsers.add_parser("node-pools", parents=[common_parser], help="查询节点池")
p.add_argument("--cluster-id", dest="cluster_id", required=True, help="集群ID")
p.add_argument("--limit", type=int, help="最大返回数量")
p.add_argument("--offset", type=int, help="偏移量")
p.set_defaults(func=cmd_node_pools)
# create-endpoint
p = subparsers.add_parser("create-endpoint", parents=[common_parser], help="开启集群访问端点(内网/外网)")
p.add_argument("--cluster-id", dest="cluster_id", required=True, help="集群ID")
p.add_argument("--is-extranet", dest="is_extranet", action="store_true", help="是否开启外网访问(默认开启内网)")
p.add_argument("--subnet-id", dest="subnet_id", help="子网ID(开启内网时需要,必须为集群所在VPC内的子网)")
p.add_argument("--security-group", dest="security_group", help="安全组ID(开启外网且不使用已有CLB时必传)")
p.add_argument("--existed-lb-id", dest="existed_lb_id", help="使用已有CLB的ID")
p.add_argument("--domain", help="设置域名")
p.add_argument(
"--extensive-parameters", dest="extensive_parameters",
help="创建LB参数(JSON字符串,仅外网需要)")
p.set_defaults(func=cmd_create_endpoint)
# delete-endpoint
p = subparsers.add_parser("delete-endpoint", parents=[common_parser], help="关闭集群访问端点(内网/外网)")
p.add_argument("--cluster-id", dest="cluster_id", required=True, help="集群ID")
p.add_argument("--is-extranet", dest="is_extranet", action="store_true", help="是否关闭外网访问(默认关闭内网)")
p.set_defaults(func=cmd_delete_endpoint)
# ---- TCR 镜像仓库命令 ----
# tcr-create-instance
p = subparsers.add_parser("tcr-create-instance", parents=[common_parser], help="创建 TCR 实例")
p.add_argument("--registry-name", dest="registry_name", required=True, help="实例名称(如 my-tcr)")
p.add_argument(
"--registry-type", dest="registry_type", required=True,
choices=["basic", "standard", "premium"],
help="实例类型:basic(基础版), standard(标准版), premium(高级版)")
p.add_argument("--charge-type", dest="charge_type", type=int, choices=[0, 1], help="计费类型:0=按量计费(默认),1=预付费")
p.add_argument("--deletion-protection", dest="deletion_protection", action="store_true", help="开启删除保护")
p.set_defaults(func=cmd_tcr_create_instance)
# tcr-delete-instance
p = subparsers.add_parser("tcr-delete-instance", parents=[common_parser], help="删除 TCR 实例")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--delete-bucket", dest="delete_bucket", action="store_true", help="同时删除关联的 COS 存储桶")
p.set_defaults(func=cmd_tcr_delete_instance)
# tcr-instances
p = subparsers.add_parser("tcr-instances", parents=[common_parser], help="查询 TCR 实例列表")
p.add_argument("--instance-name", dest="instance_name", help="按实例名称筛选")
p.add_argument("--limit", type=int, help="最大返回数量")
p.add_argument("--offset", type=int, help="偏移量")
p.add_argument("--all-instances", dest="all_instances", action="store_true", help="查看所有地域的实例")
p.set_defaults(func=cmd_tcr_instances)
# tcr-repos
p = subparsers.add_parser("tcr-repos", parents=[common_parser], help="查询镜像仓库列表")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", help="命名空间名称")
p.add_argument("--repository-name", dest="repository_name", help="仓库名称(模糊匹配)")
p.add_argument("--limit", type=int, help="最大返回数量")
p.add_argument("--offset", type=int, help="偏移量")
p.set_defaults(func=cmd_tcr_repos)
# tcr-create-repo
p = subparsers.add_parser("tcr-create-repo", parents=[common_parser], help="创建镜像仓库")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", required=True, help="命名空间名称")
p.add_argument("--repository-name", dest="repository_name", required=True, help="仓库名称")
p.add_argument("--brief-description", dest="brief_description", help="简短描述")
p.add_argument("--description", help="详细描述")
p.set_defaults(func=cmd_tcr_create_repo)
# tcr-delete-repo
p = subparsers.add_parser("tcr-delete-repo", parents=[common_parser], help="删除镜像仓库")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", required=True, help="命名空间名称")
p.add_argument("--repository-name", dest="repository_name", required=True, help="仓库名称")
p.set_defaults(func=cmd_tcr_delete_repo)
# tcr-images
p = subparsers.add_parser("tcr-images", parents=[common_parser], help="查询镜像版本列表")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", required=True, help="命名空间名称")
p.add_argument("--repository-name", dest="repository_name", required=True, help="仓库名称")
p.add_argument("--image-version", dest="image_version", help="镜像版本/Tag(模糊匹配)")
p.add_argument("--limit", type=int, help="最大返回数量")
p.add_argument("--offset", type=int, help="偏移量")
p.set_defaults(func=cmd_tcr_images)
# tcr-namespaces
p = subparsers.add_parser("tcr-namespaces", parents=[common_parser], help="查询 TCR 命名空间列表")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", help="命名空间名称(模糊匹配)")
p.add_argument("--limit", type=int, help="最大返回数量")
p.add_argument("--offset", type=int, help="偏移量")
p.set_defaults(func=cmd_tcr_namespaces)
# tcr-create-ns
p = subparsers.add_parser("tcr-create-ns", parents=[common_parser], help="创建 TCR 命名空间")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", required=True, help="命名空间名称")
p.add_argument("--is-public", dest="is_public", action="store_true", default=False, help="是否为公开命名空间(默认私有)")
p.set_defaults(func=cmd_tcr_create_ns)
# tcr-delete-ns
p = subparsers.add_parser("tcr-delete-ns", parents=[common_parser], help="删除 TCR 命名空间")
p.add_argument("--registry-id", dest="registry_id", required=True, help="TCR 实例 ID")
p.add_argument("--namespace-name", dest="namespace_name", required=True, help="命名空间名称")
p.set_defaults(func=cmd_tcr_delete_ns)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
try:
args.func(args)
except KeyboardInterrupt:
sys.exit(130)
except (json.JSONDecodeError, OSError, ValueError) as e:
print(f"错误: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()
腾讯地图 JavaScript GL(JSAPIGL)开发指南。适用于地图应用或者工具的编写。在编写、审查或调试使用腾讯地图 API的代码时应运用此技能。适用于涉及地图初始化、覆盖物展示、图层控制、事件处理、控件交互、可视化渲染、地图工具、检索、路线规划、查地址、行政区划、ip定位、几何计算、三维模型展示、性能优...
---
name: tencentmap-jsapi-gl-skill
description: 腾讯地图 JavaScript GL(JSAPIGL)开发指南。适用于地图应用或者工具的编写。在编写、审查或调试使用腾讯地图 API的代码时应运用此技能。适用于涉及地图初始化、覆盖物展示、图层控制、事件处理、控件交互、可视化渲染、地图工具、检索、路线规划、查地址、行政区划、ip定位、几何计算、三维模型展示、性能优化的任务。当用户提及 腾讯地图、 jsapi、jsapi-gl或相关地图开发需求时自动触发。
version: 1.0.0
metadata: { "openclaw": { "requires": { "bins": [""], "env": ["TMAP_JSAPI_KEY"] }, "primaryEnv": "TMAP_JSAPI_KEY" } }
---
# TMap JSAPI GL Skill
帮助用户使用腾讯地图 JavaScript API GL 进行地图功能开发,包含基础地图功能和数据可视化功能。
## 目录结构
### API 文档
- **JS API 参考文档**: `references/jsapigl/docs/` (21个md文件)
- 概述.md - API总览和索引
- 地图.md - 地图核心类和配置
- 点标记.md - 标注点相关API
- 矢量图形.md - 折线、多边形、圆形、矩形、椭圆形等矢量图形
- 文本标记.md - 文本标注API
- DOM覆盖物.md - 自定义DOM覆盖物
- 信息窗体.md - 信息窗口API
- 点聚合.md - 点聚合功能
- 控件.md - 地图控件
- 自定义图层.md - 自定义栅格/矢量图层
- 事件.md - 地图事件系统
- 基础类.md - LatLng、Point等基础类
- 室内图.md - 室内地图功能
- 附加库:地图工具.md - 几何编辑器、测量工具
- 附加库:几何计算库.md - 距离、面积计算
- 附加库:服务类库.md - 地点搜索、路线规划等
- 附加库:地图视角附加库.md - 观察者视角
- 附加库:模型库.md - GLTF/3DTiles模型
- 附加库:天气图层.md - 气象图层
- 附加库:矢量数据图层.md - GeoJSON/MVT图层
- 环境检测.md - 浏览器环境检测
- **可视化参考文档**: `references/visualization/docs/` (15个md文件)
- 参考手册.md - 可视化API总览
- 弧线图.md - 3D弧线/流向图
- 散点图.md - 3D散点图
- 热力图.md - 经典热力图
- 蜂窝热力图.md - 蜂窝聚合热力图
- 网格热力图.md - 网格聚合热力图
- 轨迹图.md - 轨迹展示
- 区域图.md - 区域轮廓图
- 管道图.md - 3D管道图
- 辐射圈.md - 辐射圈效果
- 围墙面.md - 围墙面效果
- 水晶体.md - 3D水晶体效果
- 行政区划.md - 行政区划展示
- 事件.md - 可视化事件系统
- 基础类.md - 可视化基础类
### 示例代码
- **JS API Demos**: `references/jsapigl/demos/` (129个html文件)
- 按功能分类:地图操作、点标记、文本标记、点聚合、折线、多边形、控件、信息窗口、服务类、个性化地图、几何计算、模型库、应用工具、自定义覆盖物、城市漫游等
- **可视化 Demos**: `references/visualization/demos/` (44个html文件)
- 按图层类型分类:弧线图、散点图、热力图、轨迹图、蜂窝图、区域图、水晶体等
## 工作流程
### 1. 理解用户需求
当用户询问腾讯地图API相关问题时:
- 明确用户需要的功能类型(基础地图/可视化)
- 确定具体要使用的类或功能
### 2. 查询 API 文档
在 `references/jsapigl/docs/` 或 `references/visualization/docs/` 中查找相关API文档:
- 搜索关键词(如"点标记"、"热力图")
- 阅读对应类的说明、配置参数、方法
### 3. 查找示例代码
在对应 demos 目录中查找示例:
- JS API示例:`references/jsapigl/demos/`
- 可视化示例:`references/visualization/demos/`
- 示例命名格式:`功能分类_具体示例.html`
### 4. 提供解决方案
根据文档和示例,为用户提供:
- API接口说明
- 代码示例
- 注意事项和最佳实践
## 使用示例
**用户问题**: "如何在地图上添加标记点?"
**执行流程**:
1. 读取 `references/jsapigl/docs/点标记.md` 了解 MultiMarker API
2. 查看 `references/jsapigl/demos/` 中的点标记相关示例
3. 提供完整的代码示例和说明
**用户问题**: "怎么画一个热力图?"
**执行流程**:
1. 读取 `references/visualization/docs/热力图.md` 了解 Heat API
2. 查看 `references/visualization/demos/` 中的热力图示例
3. 说明数据格式和配置选项
## 快速开始模板
基础地图初始化:
```html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>腾讯地图示例</title>
<script src="https://map.qq.com/api/gljs?v=3&key={TMAP_JSAPI_KEY}"></script>
<!-- 如需可视化功能,添加: &libraries=visualization -->
</head>
<body>
<div id="map" style="width:100%;height:500px;"></div>
<script>
var map = new TMap.Map("map", {
zoom: 12,
center: new TMap.LatLng(39.984104, 116.307503)
});
</script>
</body>
</html>
```
可视化图层示例(热力图):
```javascript
// 加载可视化库
// <script src="https://map.qq.com/api/gljs?v=1.beta&libraries=visualization&key={TMAP_JSAPI_KEY}"></script>
var heat = new TMap.visualization.Heat({
radius: 50,
height: 100,
gradientColor: {
0: '#13B06A',
0.4: '#13B06A',
0.8: '#E9AB1D',
0.9: '#E9AB1D',
1: '#E05649'
}
}).addTo(map);
heat.setData([
{ lat: 39.984104, lng: 116.307503, count: 100 },
{ lat: 39.984504, lng: 116.307803, count: 80 }
]);
```
## 注意事项
### JS API GL
1. **API Key**: 使用腾讯地图API需要申请Key,通过环境变量 `TMAP_JSAPI_KEY` 配置,在代码中使用 `{TMAP_JSAPI_KEY}` 引用
2. **版本**: 当前为 GL 版本,支持3D地图和WebGL渲染
3. **浏览器兼容**: 现代浏览器,IE11+(需polyfill)
4. **坐标系**: 使用 gcj02 坐标系
5. **地图创建(重要)**: 地图创建的容器一定要有固定宽高,尤其是flex布局下
6. **API使用(重要)**: 所有功能的API调用都必须使用文档中出现的接口、属性、事件,不能自己编造;
7. **API传参(重要)**: 所有的API传入参数必须严格遵守api文档中说明的格式,如果不确定就去看看对应demo,包括demo中的数据格式;
8. **附加库的使用**: 使用附加库需要在API加载URL中添加 `libraries` 参数
| 附加库 | libraries 值 | 命名空间 | 说明 |
|--------|-------------|----------|------|
| 地图工具 | `tools` | `TMap.tools` | 几何编辑器、测量工具 |
| 几何计算库 | `geometry` | `TMap.geometry` | 距离/面积计算、几何关系判断 |
| 服务类库 | `service` | `TMap.service` | 地点搜索、路线规划、行政区划等 |
| 地图视角附加库 | `view` | `TMap` (扩展方法) | 观察者视角操作地图 |
| 模型库 | `model` | `TMap.model` | GLTF/3DTiles/3DMarker 模型 |
| 天气图层 | `weather` | `TMap.weather` | 云图、温度图等气象图层 |
| 矢量数据图层 | `vector` | `TMap.vector` | GeoJSON/MVT 矢量数据图层 |
| 可视化库 | `visualization` | `TMap.visualization` | 可视化API的能力 |
**使用示例**:
```html
<!-- 加载多个附加库 -->
<script src="https://map.qq.com/api/gljs?v=1&libraries=tools,geometry,service,model&key={TMAP_JSAPI_KEY}"></script>
```
### 可视化 API
1. **数据格式**: 可视化图层需要特定格式的数据输入
2. **性能**: 大数据量时注意性能优化
3. **层级**: 可视化图层可以设置显示层级
4. **事件**: 支持点击、悬停等交互事件
5. **API使用(重要)**: 所有功能的API调用都必须使用文档中出现的接口、属性、事件,不能自己编造
6. **API传参(重要)**: 所有的API传入参数必须严格遵守api文档中说明的格式,如果不确定就去看看对应demo,包括demo中的数据格式;
## 最佳实践
1. **模块化加载**: 使用模块化方式按需加载API
2. **错误处理**: 添加地图加载失败的处理逻辑
3. **内存管理**: 及时销毁不需要的图层和覆盖物
4. **性能优化**: 大数据集使用聚合或抽稀
FILE:references/jsapigl/demos/地图操作示例_销毁地图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图销毁监听事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div id="container"></div>
<div class="btnContainer">
<button class="btn1" onclick="destroy()">销毁地图</button>
</div>
<script type="text/javascript">
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
rotation: 20,//设置地图旋转角度
pitch: 30, //设置俯仰角度(0~45)
zoom: 12,//设置地图缩放级别
center: center//设置地图中心点坐标
});
map.on("destroy", function () {
alert("地图已销毁!");
})
function destroy() {
map.destroy();
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_管道线.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>管道线绘制</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization" ></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
</body>
<script type="text/javascript">
function initMap() {
// 初始化地图
var map = new TMap.Map('container', {
zoom: 16, // 地图缩放
center: new TMap.LatLng(40.06072990948079, 116.19881136848187), // 设置中心点,
rotation: -30, // 设置地图旋转角度
pitch: 70, // 设置俯仰角度
baseMap: {
type: 'vector',
features: ['base', 'building3d'], // 隐藏矢量文字
},
});
// visualization.Pipe文档地址:https://lbs.qq.com/webApi/visualizationApi/visualizationDoc/visualizationDocPipe
var pipeline = new TMap.visualization.Pipe({
pickStyle: function (item) {
// 管道图样式映射函数
if (item.styleId === 'blue') {
return {
color: '#a8d4f8',
};
} else if (item.styleId === 'orange') {
return {
color: '#df5d25',
};
}
},
});
pipeline.addTo(map); // 添加至指定地图实例
// 轨迹图样式映射函数
pipeline.setData([
{
path: [
// 管道线经纬度点串
new TMap.LatLng(40.05379447146618, 116.20155948520824, 0),
new TMap.LatLng(40.06004352839526, 116.19229963143528, 80),
new TMap.LatLng(40.06004352839526, 116.19229963143528, 0),
new TMap.LatLng(40.06830098041654, 116.19104553316492, 0),
new TMap.LatLng(40.06830098041654, 116.19104553316492, 80),
new TMap.LatLng(40.068245758301266, 116.1981483027048, 80),
new TMap.LatLng(40.06074739833221, 116.19878108888793, 80),
new TMap.LatLng(40.06089095044468, 116.20647767774494, 40),
],
radius: 5, // 管道半径,单位为米
styleId: 'blue', // 样式id
},
{
path: [
new TMap.LatLng(40.05375583599823, 116.20952192478501, 0),
new TMap.LatLng(40.06086279776988, 116.2063721974971, 0),
new TMap.LatLng(40.06086279776988, 116.2063721974971, 40),
],
radius: 5,
styleId: 'orange',
},
{
path: [
new TMap.LatLng(40.05379447146618, 116.20155948520824),
new TMap.LatLng(40.06072990948079, 116.19881136848187),
],
radius: 5,
styleId: 'orange',
},
{
path: [
new TMap.LatLng(40.068245758301266, 116.1981483027048, 80),
new TMap.LatLng(40.068245758301266, 116.1981483027048, 0),
new TMap.LatLng(40.06074739833221, 116.19878108888793, 0),
new TMap.LatLng(40.06074739833221, 116.19878108888793, 80),
],
radius: 5,
styleId: 'orange',
},
{
path: [
new TMap.LatLng(40.06830098041654, 116.19104553316492, 80),
new TMap.LatLng(40.06004352839526, 116.19229963143528, 80),
],
radius: 5,
styleId: 'orange',
},
{
path: [
new TMap.LatLng(40.06254901759929, 116.19192572726774, 80),
new TMap.LatLng(40.0624535719374, 116.19862933314357, 80),
],
radius: 5,
styleId: 'blue',
},
]);
}
</script>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_自定义marker样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义marker样式</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#set-style{
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
padding: 10px;
}
</style>
<body>
<div id="container"></div>
<input type="button" id="set-style" onclick="setStyle()" value="setStyle">
<script>
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始化marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
})
},
geometries: [{
"id": 'demo',
"styleId": 'marker',
"position": new TMap.LatLng(39.984104, 116.307503),
"properties": {
"title": "marker"
}
}]
});
function setStyle(){
//更换marker样式方法
marker.setStyles({"marker": new TMap.MarkerStyle({
"width": 70,
"height": 70,
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerNew.png',
"opacity": 0.5
})})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/信息窗口_包含图文信息的信息窗口.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>包含图文信息的信息窗口</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
function initMap() {
var center = new TMap.LatLng(40.040452,116.273486);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//设置infoWindow
var infoWindow = new TMap.InfoWindow({
map: map,
position: center,
content: "<div><img src='https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/em.jpg'><p>Hello World!</p></div>"
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_标记跨图层碰撞.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>标记跨图层碰撞</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.98210863924864, 116.31310899739151);
var centerHeight = new TMap.LatLng(39.98210863924864, 116.31310899739151, 134);
// 初始化地图
var map = new TMap.Map('container', {
zoom: 17, // 设置地图缩放
center: new TMap.LatLng(39.98210863924864, 116.31310899739151), // 设置地图中心点坐标,
pitch: 0, // 俯仰度
rotation: 0 // 旋转角度
});
// MultiMarker文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
var marker = new TMap.MultiMarker({
map: map,
styles: {
// 点标记样式
marker: new TMap.MarkerStyle({
width: 20, // 样式宽
height: 30, // 样式高
anchor: { x: 10, y: 30 } // 描点位置
})
},
zIndex: 10,
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
},
geometries: [
// 点标记数据数组
{
// 标记位置(纬度,经度,高度)
position: center,
id: 'marker',
rank: 20
},
{
// 标记位置(纬度,经度,高度)
position: new TMap.LatLng(39.98116386328488, 116.31517290860688),
rank: 20
}
]
});
var label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
styles: {
label: new TMap.LabelStyle({
color: '#3777FF', // 颜色属性
size: 20, // 文字大小属性
offset: { x: 0, y: 0 }, // 文字偏移属性单位为像素
angle: 0, // 文字旋转属性
alignment: 'center', // 文字水平对齐属性
verticalAlignment: 'middle' // 文字垂直对齐属性
})
},
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
},
zIndex: 1,
geometries: [
{
id: 'label', // 点图形数据的标志信息
styleId: 'label', // 样式id
position: new TMap.LatLng(39.981566421647344, 116.31369332548206), // 标注点位置
content: 'label图层优先级低', // 标注文本
rank: 30
},
]
});
map.rotateTo(272,{duration: 5000})
</script>
</body>
</html>
FILE:references/jsapigl/demos/模型库_GLTF模型加载.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>GLTF模型加载</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry,model"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script>
var center = new TMap.LatLng(39.994104, 116.307503); // 设置中心点坐标
// 初始化地图
var map = new TMap.Map('container', {
center: center,
zoom: 19,
pitch: 50,
rotation: -20,
});
var model = new TMap.model.GLTFModel({
// GLTFModel文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glModel
url: 'https://mapapi.qq.com/web/visualization/demo-asset/airplane.glb',
map: map,
id: 'model',
position: new TMap.LatLng(39.994104, 116.307503, 100),
rotation: [0, -90, 0], // 模型XYZ三轴上的旋转角度
scale: [20, 20, 30], // 模型在XYZ三轴上的缩放比例
});
// model资源加载完成回调
model.on('loaded', () => {
console.log('模型加载成功');
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_简单圆形.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>简单圆形</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
zoom: 12, //设置地图缩放级别
center: center //设置地图中心点坐标
});
var circle = new TMap.MultiCircle({
map,
styles: { // 设置圆形样式
'circle': new TMap.CircleStyle({
'color': 'rgba(41,91,255,0.16)',
'showBorder': true,
'borderColor': 'rgba(41,91,255,1)',
'borderWidth': 2,
}),
},
geometries: [{
styleId: 'circle',
center: center,
radius: 6500,
}],
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义覆盖物_DOMOverlay跨图层碰撞.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>DOMOverlay跨图层碰撞</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
var SVG_NS = 'http://www.w3.org/2000/svg';
// 自定义环状饼图 - 继承DOMOverlay
function Donut2(options) {
TMap.DOMOverlay.call(this, options);
}
Donut2.prototype = new TMap.DOMOverlay();
// 初始化
Donut2.prototype.onInit = function (options) {
this.position = options.position;
this.data = options.data;
this.minRadius = options.minRadius || 0;
this.maxRadius = options.maxRadius || 50;
this.zIndex = options.zIndex || 0;
};
// 销毁时需解绑事件监听
Donut2.prototype.onDestroy = function () {
if (this.onClick) {
this.dom.removeEventListener(this.onClick);
}
};
// 创建DOM元素,返回一个DOMElement,使用this.dom可以获取到这个元素
Donut2.prototype.createDOM = function () {
let svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('version', '1.1');
svg.setAttribute('baseProfile', 'full');
let r = this.maxRadius;
svg.setAttribute('viewBox', [-r, -r, r * 2, r * 2].join(' '));
svg.setAttribute('width', r * 2);
svg.setAttribute('height', r * 2);
svg.style.cssText = `position:absolute;top:0px;left:0px;z-index: this.zIndex`;
let donut = createDonut2(this.data, this.minRadius, this.maxRadius);
svg.appendChild(donut);
// click事件监听
this.onClick = () => {
// DOMOverlay继承自EventEmitter,可以使用emit触发事件
this.emit('click');
};
// pc端注册click事件,移动端注册touchend事件
svg.addEventListener('click', this.onClick);
return svg;
};
// 更新DOM元素,在地图移动/缩放后执行
Donut2.prototype.updateDOM = function () {
if (!this.map) {
return;
}
// 经纬度坐标转容器像素坐标
let pixel = this.map.projectToContainer(this.position);
// 使饼图中心点对齐经纬度坐标点
let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
let top = pixel.getY() - this.dom.clientHeight / 2 + 'px';
this.dom.style.transform = `translate(left, top)`;
};
// 使用SVG创建环状饼图
function createDonut2(data, minRadius, maxRadius) {
const colorList = ['#7AF4FF', '#67D7FF', '#52B5FF', '#295BFF'];
let sum = data.reduce((prev, curr) => prev + curr, 0);
let angle = 0;
let group = document.createElementNS(SVG_NS, 'g');
data.forEach((d, i) => {
let delta = (d / sum) * Math.PI * 2;
(color = colorList[i]),
(r = maxRadius),
(startAngle = angle),
(endAngle = angle + delta);
angle += delta;
// 对每个数据创建一个扇形
let fanShape = document.createElementNS(SVG_NS, 'path');
fanShape.setAttribute('style', `fill: color;`);
fanShape.setAttribute(
'd',
[
'M0 0',
`Lr * Math.sin(startAngle) -r * Math.cos(startAngle)`,
`Ar r 0 0 1 r * Math.sin(endAngle) -r * Math.cos(endAngle)`
].join(' ') + ' z'
);
group.appendChild(fanShape);
});
// 在中心创建一个圆形
let circleShape = document.createElementNS(SVG_NS, 'circle');
circleShape.setAttribute('style', 'fill: #FFFFFF');
circleShape.setAttribute('cx', 0);
circleShape.setAttribute('cy', 0);
circleShape.setAttribute('r', minRadius);
group.appendChild(circleShape);
// 绘制文字
let textShape = document.createElementNS(SVG_NS, 'text');
textShape.setAttribute('x', 0);
textShape.setAttribute('y', '0.3em');
textShape.setAttribute('text-anchor', 'middle');
textShape.innerHTML = sum;
group.appendChild(textShape);
return group;
}
window.Donut2 = Donut2;
</script>
<script type="text/javascript">
var map;
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503), // 设置地图中心点坐标
rotation: 82.4
});
new Donut2({
map,
position: new TMap.LatLng(39.991143, 116.274421),
data: [12, 1],
minRadius: 13,
maxRadius: 20,
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
}
});
new Donut2({
map,
position: new TMap.LatLng(39.964471, 116.300526),
data: [23, 400],
minRadius: 25,
maxRadius: 35,
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
},
zIndex: 100
});
var marker = new TMap.MultiMarker({
map: map,
geometries: [
{
'position': new TMap.LatLng(40.023461749240006, 116.34966865780575), //标注点位置
rank: 4
},
],
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
},
zIndex: 30
});
var label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
styles: {
label: new TMap.LabelStyle({
color: '#3777FF', // 颜色属性
size: 20, // 文字大小属性
offset: { x: 0, y: 0 }, // 文字偏移属性单位为像素
angle: 0, // 文字旋转属性
alignment: 'center', // 文字水平对齐属性
verticalAlignment: 'middle' // 文字垂直对齐属性
})
},
geometries: [
{
styleId: 'label', // 样式id
position: new TMap.LatLng(39.976346201186146, 116.33186961161482), // 标注点位置
content: 'label碰撞文字' // 标注文本
},
],
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
}
});
map.rotateTo(0,{duration: 5000})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/城市漫游_绕点环绕.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>绕点环绕</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.info {
width: 300px;
background-color: white;
padding: 10px;
font-size: 14px;
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
</style>
<body>
<div class="info">
<p></p>
<p></p>
</div>
<div id="container"></div>
<script type="text/javascript">
// 初始化地图
var map = new TMap.Map('container', {
center: new TMap.LatLng(40.04087989704351, 116.27359195572853), // 地图中心坐标
zoom: 17, // 地图缩放比例
pitch: 40, // 地图俯仰角度
});
var keyframe = [
{
percentage: 0, // 动画过程中该关键帧的百分比,
rotation: 0, // 地图在水平面上的旋转角度。
},
{
percentage: 0.2,
rotation: 72,
},
{
percentage: 0.6,
rotation: 120,
},
{
percentage: 0.8,
rotation: 288,
},
{
percentage: 1,
rotation: 360,
},
];
// map.startAnimation 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap
// 开始动画,通过keyFrames定义关键帧
map.startAnimation(keyframe, {
duration: 16000, // 动画周期时长,单位为ms
loop: Infinity, // 动画周期循环次数,若为Infinity则无限循环,默认为1
});
var infoList = document.querySelectorAll('.info p');
map.on('animation_playing', function (event) {
infoList[0].innerText = '当前地图旋转角度:' + Math.round(event.frame.rotation);
infoList[1].innerText = '动画执行进度:' + event.percentage.toFixed(2) + '(区间范围0-1)';
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_文本标记背景.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>设置文本标记背景</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1">label背景样式1</button>
<button class="btn2">label背景样式2</button>
</div>
<div id="mapContainer"></div>
<script>
var center = new TMap.LatLng(40.040074, 116.273519); // 设置中心点坐标
var centerHeight = new TMap.LatLng(40.040074, 116.273519, 100); // 带高度的坐标
// 初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 18,
});
// 初始化label
var label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
styles: {
label: new TMap.LabelStyle({
color: '#3777FF', // 颜色属性
size: 20, // 文字大小属性
width: 150, // 文字背景框宽度
height: 60, // 文字背景框高度
backgroundColor: 'rgba(175,238,238,0.6)', // 文字背景框颜色属性
borderWidth: 4, // 文字背景框边线宽度
borderRadius: 10, // 文字背景框圆角
borderColor: 'rgba(255,192,203,1.0)', // 文字背景框边线颜色
}),
},
geometries: [
{
id: 'label', // 点图形数据的标志信息
styleId: 'label', // 样式id
position: center, // 标注点位置
content: '腾讯北京总部', // 标注文本
properties: {
// 标注点的属性数据
title: 'label',
},
},
],
});
document.querySelector('button.btn1').onclick = function () {
label.setStyles({
'label': new TMap.LabelStyle({
color: '#3777FF', // 颜色属性
size: 20, // 文字大小属性
width: 150, // 文字背景框宽度
height: 60, // 文字背景框高度
backgroundColor: 'rgba(175,238,238,0.6)', // 文字背景框颜色属性
borderWidth: 4, // 文字背景框边线宽度
borderRadius: 10, // 文字背景框圆角
borderColor: 'rgba(255,192,203,1.0)', // 文字背景框边线颜色
})
});
};
document.querySelector('button.btn2').onclick = function () {
label.setStyles({
'label': new TMap.LabelStyle({
color: '#3777FF', // 颜色属性
size: 20, // 文字大小属性
width: 160, // 文字背景框宽度
height: 40, // 文字背景框高度
backgroundColor: 'rgba(255,228,181,1.0)', // 文字背景框颜色属性
borderWidth: 8, // 文字背景框边线宽度
borderRadius: 0, // 文字背景框圆角
borderColor: 'rgba(128,128,0,0.5)', // 文字背景框边线颜色
})
});
};
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置地图显示比例.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图显示比例设置</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#btn {
position: absolute;
top: 30px;
left: 30px;
z-index: 1001;
padding: 6px 8px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<input type="button" id="btn" onclick="changeScale()" value="设置显示比例为2">
<script type="text/javascript">
var map, scale = 1;
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
map = new TMap.Map("container", {
zoom:12,//设置地图缩放级别
center: center,//设置地图中心点坐标
});
map.on('scale_changed', (e) => {
console.log(e);
});
}
// 改变地图显示比例
function changeScale() {
var btn = document.getElementById('btn');
btn.value = `设置显示比例为scale`;
if (scale === 1) {
scale = 2;
} else {
scale = 1;
}
map.setScale(scale);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义栅格图层_自定义栅格图层-销毁.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>销毁自定义栅格图层</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div id="mapContainer"></div>
<div class="btnContainer">
<button class="btn1" onclick="destroy()">销毁自定义栅格图层</button>
<button class="btn2" onclick="create()">创建自定义栅格图层</button>
</div>
<script>
var center = new TMap.LatLng(26.870355, 100.239704);//设置中心点坐标
//初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 15,
maxZoom: 16
});
//初始化imageTileLayer
var imageTileLayer;
create();
function destroy() {
imageTileLayer.destroy();
imageTileLayer = null;
}
function create() {
if(imageTileLayer) return;
imageTileLayer = new TMap.ImageTileLayer({
getTileUrl: function (x, y, z) {
//拼接瓦片URL
var url = 'https://3gimg.qq.com/visual/lbs_gl_demo/image_tiles_layers/' + z + '/' + x + '_' + y + '.png';
return url;
},
tileSize: 256, //瓦片像素尺寸
minZoom: 14, //显示自定义瓦片的最小级别
maxZoom: 16, //显示自定义瓦片的最大级别
visible: true, //是否可见
zIndex: 5000, //层级高度(z轴)
opacity: 0.95, //图层透明度:1不透明,0为全透明
map: map, //设置图层显示到哪个地图实例中
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/应用工具_绘制几何图形.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>绘制几何图形</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
}
#toolControl {
position: absolute;
top: 10px;
left: 0px;
right: 0px;
margin: auto;
width: 252px;
z-index: 1001;
}
.toolItem {
width: 30px;
height: 30px;
float: left;
margin: 1px;
padding: 4px;
border-radius: 3px;
background-size: 30px 30px;
background-position: 4px 4px;
background-repeat: no-repeat;
box-shadow: 0 1px 2px 0 #e4e7ef;
background-color: #ffffff;
border: 1px solid #ffffff;
}
.toolItem:hover {
border-color: #789cff;
}
.active {
border-color: #d5dff2;
background-color: #d5dff2;
}
#marker {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/marker_editor.png');
}
#polyline {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/polyline.png');
}
#polygon {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/polygon.png');
}
#circle {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/circle.png');
}
#rectangle {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/rectangle.png');
}
#ellipse {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/ellipse.png');
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="toolControl">
<div class="toolItem active" id="marker" title="点标记"></div>
<div class="toolItem" id="polyline" title="折线"></div>
<div class="toolItem" id="polygon" title="多边形"></div>
<div class="toolItem" id="circle" title="圆形"></div>
<div class="toolItem" id="rectangle" title="矩形"></div>
<div class="toolItem" id="ellipse" title="椭圆"></div>
</div>
<div>
绘制:鼠标左键点击及移动即可绘制图形
<br />
结束绘制:鼠标左键双击即可结束绘制折线、多边形会自动闭合;圆形、矩形、椭圆单击即可结束
<br />
中断:绘制过程中按下esc键可中断该过程
<br />
撤销:使用ctrl+z或鼠标右键可以在绘制线和多边形时撤销上个绘制点
</div>
<script type="text/javascript">
var map; // 地图
var editor; // 编辑器
var activeType = 'marker'; // 激活的图形编辑类型
// 切换激活图层
document.getElementById('toolControl').addEventListener('click', (e) => {
var id = e.target.id;
if (id !== 'toolControl') {
document.getElementById(activeType).className = 'toolItem';
document.getElementById(id).className = 'toolItem active';
activeType = id;
editor.setActiveOverlay(id);
}
});
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503), // 设置地图中心点坐标
});
// 初始化几何图形及编辑器
var marker = new TMap.MultiMarker({
map: map,
});
var polyline = new TMap.MultiPolyline({
map: map,
});
var polygon = new TMap.MultiPolygon({
map: map,
});
var circle = new TMap.MultiCircle({
map: map,
});
var rectangle = new TMap.MultiRectangle({
map: map,
});
var ellipse = new TMap.MultiEllipse({
map: map,
});
editor = new TMap.tools.GeometryEditor({
// TMap.tools.GeometryEditor 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor
map: map, // 编辑器绑定的地图对象
overlayList: [
// 可编辑图层 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#4
{
overlay: marker,
id: 'marker',
},
{
overlay: polyline,
id: 'polyline',
},
{
overlay: polygon,
id: 'polygon',
},
{
overlay: circle,
id: 'circle',
},
{
overlay: rectangle,
id: 'rectangle',
},
{
overlay: ellipse,
id: 'ellipse',
},
],
actionMode: TMap.tools.constants.EDITOR_ACTION.DRAW, // 编辑器的工作模式
activeOverlayId: 'marker', // 激活图层
snappable: true, // 开启吸附
});
// 监听绘制结束事件,获取绘制几何图形
editor.on('draw_complete', (geometry) => {
// 判断当前处于编辑状态的图层id是否是overlayList中id为rectangle(矩形)图层
// 判断当前处于编辑状态的图层id是否是overlayList中id为rectangle(矩形)图层
var id = geometry.id;
if (editor.getActiveOverlay().id === 'rectangle') {
// 获取矩形顶点坐标
var geo = rectangle.geometries.filter(function (item) {
return item.id === id;
});
console.log('绘制的矩形定位的坐标:', geo[0].paths);
}
if (editor.getActiveOverlay().id === 'polygon') {
// 获取多边形顶点坐标
var geo = polygon.geometries.filter(function (item) {
return item.id === id;
});
console.log('绘制的多边形坐标:', geo[0].paths);
}
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义遮罩图层_自定义遮罩图层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>遮罩图层</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
}
#container {
width: 100%;
height: 100%;
}
#btn {
position: absolute;
top: 30px;
left: 30px;
z-index: 1001;
padding: 6px 8px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<input type="button" id="btn" onclick="changeMaskLayer()" value="添加遮罩层">
<script type="text/javascript">
var map, mask;
var isMaskExist = false;
var maskId = 'mask-layer';
var paths = [
new TMap.LatLng(40.04116648755316, 116.27218534259555),
new TMap.LatLng(40.03936793557013, 116.2725983350083),
new TMap.LatLng(40.03971286702527, 116.27493147278983),
new TMap.LatLng(40.04147856016226, 116.27450239006976)
];
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);
//初始化地图
map = new TMap.Map("container", {
zoom: 18, //设置地图缩放级别
center: center, //设置地图中心点坐标
});
mask = new TMap.MaskLayer({
map: map,
geometries: [],
});
}
function changeMaskLayer() {
var btn = document.getElementById('btn');
if (!isMaskExist) {
mask.add([{
id: maskId,
paths: paths,
}]);
btn.value = '移除遮罩层';
isMaskExist = true;
} else {
mask.remove([maskId]);
btn.value = '添加遮罩层';
isMaskExist = false;
}
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/几何计算库_路径顺逆时针判断与转换.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>路径顺逆时针判断与转化</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 80px;
width: 400px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
line-height: 80px;
text-align: center;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div id="container"></div>
<div class="btnContainer">
<button class="btn1" onclick="transform()">路径变为顺时针</button>
<button class="btn2" onclick="transformAnti()">路径变为逆时针</button>
</div>
<div id="info"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.980309851613775, 116.30836114421982);
//初始化地图
var map = new TMap.Map("container", {
zoom: 17,
center: center
});
var paths = [
new TMap.LatLng(39.98250495981251, 116.30587233910774),
new TMap.LatLng(39.979150868392445, 116.30612982298771),
new TMap.LatLng(39.97921663629626, 116.3083828075105),
new TMap.LatLng(39.97892067935242, 116.31089327579048),
new TMap.LatLng(39.982554283513245, 116.31100056083596),
new TMap.LatLng(39.98250495981251, 116.3059933910774),
];
var polylineLayer = new TMap.MultiPolyline({
map, // 绘制到目标地图
styles: { // 折线样式定义
'arrow_animation': new TMap.PolylineStyle({
'color': 'rgba(255,0,0,0.6)', //线填充色
'borderWidth': 2, //边线宽度
'borderColor': '#008FFF', //边线颜色
'width': 10, //折线宽度
showArrow: true,
arrowOptions: {
width: 8, // 箭头图标宽度
height: 5, // 箭头图标高度
space: 50, // 箭头图标之间的孔隙长度
animSpeed: 50 // 箭头动态移动的速度 单位(像素/秒)
},
})
},
geometries: [{
styleId: 'arrow_animation',
paths: paths
}],
});
var infoDom = document.getElementById('info');
infoDom.innerText = `路线为'逆时针'`;
function transform() {
paths = TMap.geometry.transfromClockwise(paths);
polylineLayer.setGeometries([{
styleId: 'arrow_animation',
paths: paths
}]);
infoDom.innerText = `路线为'逆时针'`;
}
function transformAnti() {
paths = TMap.geometry.transfromAntiClockwise(paths);
polylineLayer.setGeometries([{
styleId: 'arrow_animation',
paths: paths
}]);
infoDom.innerText = `路线为'逆时针'`;
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_marker拖动.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>拖动marker</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 90%;
}
#information {
margin: 5px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="information">点击Marker后选中目标,然后进行拖动。</div>
<script type="text/javascript">
function initMap() {
// 初始化地图
const map = new TMap.Map("container", {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503) // 设置地图中心点坐标
});
const marker = new TMap.MultiMarker({
map: map,
styles: {
"highlight": new TMap.MarkerStyle({
"src": "https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/marker-pink.png"
})
},
geometries: [{ //点标注数据数组
"position": new TMap.LatLng(39.984104, 116.307503),
}, {
"position": new TMap.LatLng(39.984104, 116.337503),
}, {
"position": new TMap.LatLng(39.984104, 116.367503),
}]
});
// 初始化几何图形及编辑器
const editor = new TMap.tools.GeometryEditor({
map, // 编辑器绑定的地图对象
overlayList: [{
overlay: marker, // 可编辑图层
id: "marker",
selectedStyleId: "highlight" // 被选中的marker会变为高亮样式
}],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
activeOverlayId: "marker", // 激活图层
selectable: true
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_自定义多边形样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义多边形样式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="initMap()">
<div id="buttonContainer">
<input type="button" onclick="setStyle('styleOne')" value="设置样式1">
<input type="button" onclick="setStyle('styleTwo')" value="设置样式2">
</div>
<div id="mapContainer"></div>
</body>
</html>
<script>
var map = null;
var polygon = null;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486); // 设置中心点坐标
// 初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 16,
});
var path = [
// 多边形的位置信息
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
// 初始化polygon
polygon = new TMap.MultiPolygon({
id: 'polygon-layer', // 图层id
map: map, // 显示多边形图层的底图
styles: {
// 多边形的相关样式
polygon: new TMap.PolygonStyle({
color: 'rgba(0,125,255,0.5)', // 面填充色
showBorder: false, // 是否显示拔起面的边线
// 'borderWidth':20,
borderColor: '#FF0000', // 边线颜色
}),
},
geometries: [
{
id: 'polygon', // 多边形图形数据的标志信息
styleId: 'polygon', // 样式id
paths: path, // 多边形的位置信息
properties: {
// 多边形的属性数据
title: 'polygon',
},
},
],
});
}
function setStyle(type) {
switch (type) {
case 'styleOne':
polygon.setStyles({
polygon: new TMap.PolygonStyle({
color: 'rgba(0,125,255,0.5)',
showBorder: false,
borderColor: '#999',
}),
});
break;
case 'styleTwo':
polygon.setStyles({
polygon: new TMap.PolygonStyle({
color: '#FF00FF',
showBorder: false,
borderColor: 'rgba(0,255,0,0.4)',
}),
});
break;
default:
}
}
</script>
FILE:references/jsapigl/demos/多边形与3D棱柱_多边形飞地.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>多边形飞地</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
function initMap() {
var center = new TMap.LatLng(40.035004,116.216400);//设置中心点坐标
//初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 16
});
var path = [
[ //多边形的位置信息
[
new TMap.LatLng(40.035246,116.216367),
new TMap.LatLng(40.034733,116.216539),
new TMap.LatLng(40.034774,116.217440),
new TMap.LatLng(40.034819,116.217987),
new TMap.LatLng(40.034852,116.218315),
new TMap.LatLng(40.035328,116.218197),
new TMap.LatLng(40.035271,116.217295)
],//roll
[
new TMap.LatLng(40.034942,116.216695),
new TMap.LatLng(40.034823,116.216711),
new TMap.LatLng(40.034844,116.217279),
new TMap.LatLng(40.034971,116.217247)
]//hole
], [
[
new TMap.LatLng(40.035599,116.215204),
new TMap.LatLng(40.035082,116.215429),
new TMap.LatLng(40.035246,116.216341),
new TMap.LatLng(40.035731,116.216094)
]
],
[
[
new TMap.LatLng(40.034675,116.215627),
new TMap.LatLng(40.034199,116.215686),
new TMap.LatLng(40.034277,116.216620),
new TMap.LatLng(40.034770,116.216539)
]
]
];
//初始化polygon
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map, //显示多边形图层的底图
styles: { //多边形的相关样式
'polygon': new TMap.PolygonStyle({
'color': 'rgba(0,125,255,0.7)', //面填充色
'showBorder': false, //是否显示拔起面的边线
'borderColor': '#00FFFF' //边线颜色
})
},
geometries: [
{
'id': 'polygon', //多边形图形数据的标志信息
'styleId': 'polygon', //样式id
'paths': path, //多边形的位置信息
'properties': { //多边形的属性数据
'title': 'polygon'
}
}
]
});
}
</script>
FILE:references/jsapigl/demos/几何计算库_计算多边形形心.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算简单多边形的形心</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 80px;
width: 420px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
line-height: 80px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">计算多边形的形心,在该点添加文本标注</div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 17,
baseMap: {
type: 'vector',
features: ['base', 'building3d', 'label'],
}
});
var path = [
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
// 计算多边形形心
var position = TMap.geometry.computeCentroid(path);
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map,
geometries: [
{
id: 'polygon', //多边形图形数据的标志信息
paths: path, //多边形的位置信息
}
]
});
// 在形心位置添加文本标注
var label = new TMap.MultiLabel({
id: 'label-layer', //图层id
map: map,
styles: {
building: new TMap.LabelStyle({
color: '#3777FF', //颜色属性
size: 20, //文字大小属性
alignment: 'center', //文字水平对齐属性
verticalAlignment: 'middle' //文字垂直对齐属性
})
},
geometries: [
{
id: 'label',
styleId: 'building',
position: position,
content: '腾讯北京总部',
}
]
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义覆盖物_DOMOverlay.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DOMOverlay</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
var SVG_NS = 'http://www.w3.org/2000/svg';
// 自定义环状饼图 - 继承DOMOverlay
function Donut(options) {
TMap.DOMOverlay.call(this, options);
}
Donut.prototype = new TMap.DOMOverlay();
// 初始化
Donut.prototype.onInit = function(options) {
this.position = options.position;
this.data = options.data;
this.minRadius = options.minRadius || 0;
this.maxRadius = options.maxRadius || 50;
};
// 销毁时需解绑事件监听
Donut.prototype.onDestroy = function() {
if (this.onClick) {
this.dom.removeEventListener(this.onClick);
}
};
// 创建DOM元素,返回一个DOMElement,使用this.dom可以获取到这个元素
Donut.prototype.createDOM = function() {
let svg = document.createElementNS(SVG_NS, 'svg');
svg.setAttribute('version', '1.1');
svg.setAttribute('baseProfile', 'full');
let r = this.maxRadius;
svg.setAttribute('viewBox', [-r, -r, r * 2, r * 2].join(' '));
svg.setAttribute('width', r * 2);
svg.setAttribute('height', r * 2);
svg.style.cssText = 'position:absolute;top:0px;left:0px;';
let donut = createDonut(this.data, this.minRadius, this.maxRadius);
svg.appendChild(donut);
// click事件监听
this.onClick = () => {
// DOMOverlay继承自EventEmitter,可以使用emit触发事件
this.emit('click');
};
// pc端注册click事件,移动端注册touchend事件
svg.addEventListener('click', this.onClick);
return svg;
};
// 更新DOM元素,在地图移动/缩放后执行
Donut.prototype.updateDOM = function() {
if (!this.map) {
return;
}
// 经纬度坐标转容器像素坐标
let pixel = this.map.projectToContainer(this.position);
// 使饼图中心点对齐经纬度坐标点
let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
let top = pixel.getY() - this.dom.clientHeight / 2 + 'px';
this.dom.style.transform = `translate(left, top)`;
};
// 使用SVG创建环状饼图
function createDonut(data, minRadius, maxRadius) {
const colorList = [
'#7AF4FF',
'#67D7FF',
'#52B5FF',
'#295BFF'
];
let sum = data.reduce((prev, curr) => prev + curr, 0);
let angle = 0;
let group = document.createElementNS(SVG_NS, "g");
data.forEach((d, i) => {
let delta = d / sum * Math.PI * 2;
color = colorList[i],
r = maxRadius,
startAngle = angle,
endAngle = angle + delta;
angle += delta;
// 对每个数据创建一个扇形
let fanShape = document.createElementNS(SVG_NS, 'path');
fanShape.setAttribute('style', `fill: color;`);
fanShape.setAttribute('d', [
'M0 0',
`Lr * Math.sin(startAngle) -r * Math.cos(startAngle)`,
`Ar r 0 0 1 r * Math.sin(endAngle) -r * Math.cos(endAngle)`,
].join(' ') + ' z');
group.appendChild(fanShape);
});
// 在中心创建一个圆形
let circleShape = document.createElementNS(SVG_NS, 'circle');
circleShape.setAttribute('style', 'fill: #FFFFFF');
circleShape.setAttribute('cx', 0);
circleShape.setAttribute('cy', 0);
circleShape.setAttribute('r', minRadius);
group.appendChild(circleShape);
// 绘制文字
let textShape = document.createElementNS(SVG_NS, 'text');
textShape.setAttribute('x', 0);
textShape.setAttribute('y', '0.3em');
textShape.setAttribute('text-anchor', 'middle');
textShape.innerHTML = sum;
group.appendChild(textShape);
return group;
}
window.Donut = Donut;
</script>
<script type="text/javascript">
var map;
function initMap() {
// 初始化地图
map = new TMap.Map("container", {
zoom:12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503) // 设置地图中心点坐标
});
let donutList = [
new Donut({
map,
position: new TMap.LatLng(39.96030543872138, 116.25809083213608),
data: [12, 24],
minRadius: 13,
maxRadius: 20
}),
new Donut({
map,
position: new TMap.LatLng(39.9986945980902, 116.33598362780685),
data: [23, 99, 101, 400],
minRadius: 25,
maxRadius: 35
}),
new Donut({
map,
position: new TMap.LatLng(40.02906301748584, 116.25499991104516),
data: [18, 41, 50],
minRadius: 20,
maxRadius: 28
})
];
donutList.forEach((donut, index) => {
donut.on('click', () => {
console.log(`第index个环形图被点击,位置为donut.position`);
});
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_同时加载两个地图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>同时加载2个地图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#containerOne {
width: 100%;
height: 45%;
}
#containerTwo {
width: 100%;
height: 45%;
}
p{
margin: 0;
margin-bottom: 20px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="containerOne"></div>
<p>地图一</p>
<div id="containerTwo"></div>
<p>地图二</p>
<script>
function initMap() {
//初始化地图一
var mapOne = new TMap.Map("containerOne", {
pitch:45,
center: new TMap.LatLng(39.984104, 116.307503)
});
//初始化地图二
var mapTwo = new TMap.Map("containerTwo", {
center: new TMap.LatLng(39.984104, 116.307503)
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置地图中心点偏移.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>map中心点偏移</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#container-center {
width: 10px;
height: 10px;
background-color: blue;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="container-center"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);
//初始化地图
map = new TMap.Map("container", {
zoom: 17, //设置地图缩放级别
center: center, //设置地图中心点坐标
offset: { // 中心点偏移
x: 0,
y: 100,
}
});
var markerGeo = {
id: 'center',
position: map.getCenter(),
};
// 创建一个位于地图中心点的marker
markerLayer = new TMap.MultiMarker({
map: map,
geometries: [
markerGeo
]
});
// 监听中心点变化事件,更新marker的位置
map.on('center_changed', () => {
markerGeo.position = map.getCenter();
markerLayer.updateGeometries([markerGeo]);
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_简单折线.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>简单折线</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map('mapContainer', {
center: new TMap.LatLng(40.040452,116.273486),//地图显示中心点
zoom:16 //缩放级别
});
//创建 MultiPolyline
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer', //图层唯一标识
map: map,//绘制到目标地图
//折线样式定义
styles: {
'style_blue': new TMap.PolylineStyle({
'color': '#3777FF', //线填充色
'width': 6, //折线宽度
'borderWidth': 5, //边线宽度
'borderColor': '#FFF', //边线颜色
'lineCap': 'butt' //线端头方式
}),
'style_red': new TMap.PolylineStyle({
'color': '#CC0000', //线填充色
'width': 6, //折线宽度
'borderWidth': 5, //边线宽度
'borderColor': '#CCC', //边线颜色
'lineCap': 'round' //线端头方式
})
},
//折线数据定义
geometries: [
{//第1条线
'id': 'pl_1',//折线唯一标识,删除时使用
'styleId': 'style_blue',//绑定样式名
'paths': [new TMap.LatLng(40.038540, 116.272389), new TMap.LatLng(40.038844, 116.275210), new TMap.LatLng(40.041407, 116.274738)]
},
{//第2条线
'id': 'pl_2',
'styleId': 'style_red',
'paths': [new TMap.LatLng(40.039492,116.271893), new TMap.LatLng(40.041562,116.271421), new TMap.LatLng(40.041957,116.274211)]
}
]
});
}
</script>
FILE:references/jsapigl/demos/环境检测_浏览器环境检测.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>浏览器环境检测</title>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
</head>
<style>
body {
font-family: Arial, sans-serif;
margin: 20px;
}
table {
width: 100%;
border-collapse: collapse;
overflow-y: scroll;
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
tr:nth-child(even) {
background-color: #f9f9f9;
}
tr:hover {
background-color: #f1f1f1;
}
.table-container {
height: 85vh;
overflow-y: auto;
overflow-x: hidden;
}
</style>
<body>
<h1>环境信息展示</h1>
<div class="table-container">
<table id="envTable">
<thead>
<tr>
<th>属性</th>
<th>值</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<script>
var env = {
'移动设备': TMap.Browser.isMobileDevice,
'当前系统平台': TMap.Browser.platform,
'是否运行window设备': TMap.Browser.isWindowsDevice,
'是否运行在iOS设备': TMap.Browser.isIosDevice,
'是否为触摸设备': TMap.Browser.isTouchDevice,
'是否iPad': TMap.Browser.isIPad,
'是否Phone': TMap.Browser.isIPhone,
'是否安卓设备': TMap.Browser.isAndroidDevice,
'是否Chrome浏览器': TMap.Browser.isChrome,
'是否火狐浏览器': TMap.Browser.isFirefox,
'是否Safari浏览器': TMap.Browser.isSafari,
'是否Edge浏览器': TMap.Browser.isEdge,
'是否微信': TMap.Browser.isWeChat,
'是否为微信小程序': TMap.Browser.isMiniProgram,
'是否运行在微信小程序移动端': TMap.Browser.isMiniProgramMobile,
'是否为高清屏': TMap.Browser.isRetinaDevice,
'是否webkit浏览器': TMap.Browser.isWebkit,
'运行环境是否支持canvas': TMap.Browser.isSupportCanvas,
'运行环境是否支持webgl': TMap.Browser.isSupportWebGL,
'运行环境是否支持webgl 2.0': TMap.Browser.isSupportWebGL2,
'当前浏览器的userAgent信息': TMap.Browser.userAgent,
};
function populateTable(env) {
var tbody = document.querySelector('#envTable tbody');
for (var key in env) {
if (env.hasOwnProperty(key)) {
var row = document.createElement('tr');
var cellKey = document.createElement('td');
var cellValue = document.createElement('td');
cellKey.textContent = key;
cellValue.textContent = env[key];
row.appendChild(cellKey);
row.appendChild(cellValue);
tbody.appendChild(row);
}
}
}
// 填充表格
populateTable(env);
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_文本标记开启碰撞避让.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>文本标记开启碰撞避让</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="mapContainer"></div>
<script>
var center = new TMap.LatLng(40.040074, 116.273519);//设置中心点坐标
var centerLabel2 = new TMap.LatLng(40.040074, 116.243519);//设置中心点坐标
//初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
rotation: 30,
zoom: 12
});
//初始化label
var label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
collisionOptions: {
sameSource: true,
}, //开启图层内部的文本标注碰撞
styles: {
'label': new TMap.LabelStyle({
'color': '#3777FF', //颜色属性
'size': 20, //文字大小属性
'offset': { x: 0, y: 0 }, //文字偏移属性单位为像素
'angle': 0, //文字旋转属性
'alignment': 'center', //文字水平对齐属性
'verticalAlignment': 'middle' //文字垂直对齐属性
})
},
geometries: [
{
'id': 'label', //点图形数据的标志信息
'styleId': 'label', //样式id
'position': center, //标注点位置
'content': '腾讯北京总部', //标注文本
'properties': { //标注点的属性数据
'title': 'label'
},
rank:10 //文本碰撞时的优先级,值越大优先级越高
},
{
'id': 'labe2', //点图形数据的标志信息
'styleId': 'label', //样式id
'position': centerLabel2, //标注点位置
'content': '腾讯北京总部(低优先级)', //标注文本
'properties': { //标注点的属性数据
'title': 'labe2'
},
rank:1 //文本碰撞时的优先级,值越大优先级越高
}
]
});
map.rotateTo(0,{duration: 5000})
</script>
</body>
</html>
FILE:references/jsapigl/demos/模型库_在地图上使用Three.js.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>GLCustomLayer-全国行政区划图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=model"></script>
<script src="https://mapapi.qq.com/web/jsapiGL/three-155/[email protected]"></script>
<script src="https://lbs.gtimg.com/visual/lbs_component/v/jquery-1.11.0.min.js"></script>
<script src="https://mapapi.qq.com/web/visualization/demo-asset/styleMap.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 150px;
top: 110px;
background: #fff;
border-radius: 5px;
padding: 10px;
opacity: 0.8;
}
.demo-title {
position: absolute;
top: 50px;
left: 50px;
z-index: 1;
}
h1 {
margin: 0;
color: #ACB3BB;
}
h3 {
font-weight: normal;
margin-top: 5px;
color: #8E939D;
}
</style>
<body>
<div id="container"></div>
<div id="info"></div>
<div class="demo-title">
<h1>自定义GL图层—全国行政区划图</h1>
</div>
<script>
const styles = {
//设置区域图样式
styel1: {
fillColor: "#7DF4FF", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel2: {
fillColor: "#17D5DC", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel3: {
fillColor: "#0EB2E7", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel4: {
fillColor: "#0896EF", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel5: {
fillColor: "#017CF7", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
};
// 初始化地图
var map = new TMap.Map('container', {
center: new TMap.LatLng(33.36060741272668, 107.84272611116683), //设置地图中心坐标
zoom: 5.7, //地图进行缩放,
pitch: 53,
// mapStyleId: "style1", //个性化样式
baseMap: {
//设置底图样式
type: "vector", //设置底图为矢量底图
features: [
//设置矢量底图要素类型
"base",
"point",
],
},
});
let renderer, camera, scene, group, raycaster;
let mapCamera; // 地图相机
var glCustomLayer = new TMap.GLCustomLayer(
{
id: 'glCustomLayer',
map,
keepFps: false, // 是否保持恒定帧率渲染,当渲染存在动画时设置为true
// visible: false,
init: (gl) => {
initThree(gl);
createJsonMap(); // 构建全国区域行政图
initEvent(); // 注册点击事件
// 需将构建的Three相关项返回
return { renderer, camera, scene, group };
},
render: () => {
renderer.render(scene, camera);
}
}
);
function initThree(gl) {
renderer = new THREE.WebGLRenderer({
context: gl,
antialias: true
});
renderer.autoClear = false;
renderer.outputEncoding = THREE.sRGBEncoding;
mapCamera = map.getCamera(); // 获取地图相机
const { fovy, view, near, far, distance } = mapCamera;
const aspect = (view.right - view.left) / (view.top - view.bottom);
camera = new THREE.PerspectiveCamera(fovy, aspect, near, far); // 创建threejs中的相机
camera.position.z = distance;
scene = new THREE.Scene()
group = new THREE.Group(); // 涉及内部坐标转换,所有新增THREE.Object3D需添加入group
scene.add(group);
raycaster = new THREE.Raycaster();
}
function createJsonMap() {
$.ajax({
url: "https://apis.map.qq.com/ws/district/v1/getchildren?key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&output=jsonp&get_polygon=2&max_offset=3000", // 请求地址+参数
type: "GET", //get请求
dataType: "JSONP", //指定服务器返回的数据类型
success: function (res) {
var list = [];
list = res.result[0]; // 省份列表
list.forEach((item, index) => {
const styleId = styleMap[item.id]; //数据所有的id值
const itemStyle = styles[styleId];
item.polygon.forEach((polygonPoints) => {
let path = [];
for (let i = 0; i < polygonPoints.length; i += 2) {
const position = new TMap.LatLng(polygonPoints[i + 1], polygonPoints[i])
path.push(map.glLatLngToPosition(position));
}
let linePoints = [];
let shapePoints = [];
path.forEach((p, index) => {
linePoints.push(new THREE.Vector3(p.x, p.y, p.z + 500000.1));
shapePoints.push(new THREE.Vector3(p.x, p.y, p.z));
})
// 轮廓
const lineMaterial = new THREE.LineBasicMaterial({ color: itemStyle.strokeColor, linewidth: 1 })
const linGeometry = new THREE.BufferGeometry().setFromPoints(linePoints);
const line = new THREE.LineLoop(linGeometry, lineMaterial)
group.add(line);
// 填充形状
const shape = new THREE.Shape(shapePoints);
const extrudeSettings = {
depth: 500000,
bevelEnabled: false
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
const material = new THREE.MeshBasicMaterial({ color: itemStyle.fillColor, transparent: true, opacity: 0.6 })
var mesh = new THREE.Mesh(geometry, material);
mesh.defaultMaterial = material;
mesh.name = item.name;
group.add(mesh);
});
});
}
});
}
// 选中物体的材质
let selectedMaterial = new THREE.MeshBasicMaterial({ color: '#FF7F50', transparent: true, opacity: 0.65 })
// 注册THREE拾取事件
let lastItem = null; // 上个选中的物体
var infoDom = document.getElementById('info');
function initEvent() {
map.on('click', (evt) => {
if (lastItem) {
lastItem.material = lastItem.defaultMaterial; // 恢复默认材质
infoDom.style.display = '';
}
const { x, y } = evt.point;
const { width, height } = mapCamera.resolution;
const mouse = new THREE.Vector2((x / width * 2) - 1, 1 - (y / height * 2));
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(group.children, true);
// if(intersects.length > 0) console.log('点击了'+ intersects[0].object.name, intersects)
if (intersects.length > 0) {
const item = intersects[0].object;
item.material = selectedMaterial; // 更变点击物体材质
lastItem = item;
// 展示选中信息
infoDom.style.display = 'block';
infoDom.innerHTML = '当前选中: '+item.name;
}
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/矢量数据图层_polygonGeoJSON.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>polygonGeoJSON</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=vector"></script>
<script charset="utf-8" src="https://mapapi.qq.com/web/jsapi/jsapi-gl/assets/polygonGeoJSON.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script>
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center,
zoom: 9
});
new TMap.vector.GeoJSONLayer({
map: map,
data: polygonGeoJSON,
polygonStyle: new TMap.PolygonStyle({
'color': '#3777FF', //面填充色
'showBorder': true, //是否显示边线
'borderColor': '#fff', //边线颜色
'borderWidth': 1 //边线宽度
}),
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/信息窗口_拖拽信息窗口.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>信息窗口实现拖动</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.info_card {
display: inline-block;
margin: 50px auto;
position: absolute;
width: 200px;
height: 100px;
background-color: #c7c9c8;
border: 5px solid #ffffff;
color: #000000;
/* 禁止文字选中 */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.info_card .title {
width: 100%;
height: 40px;
background-color: #000000;
color: #ffffff;
}
.title span.title_name {
position: relative;
top: 7px;
left: 10px;
font-size: 18px;
}
.info_card .title .close_img {
position: absolute;
top: 10px;
right: 10px;
width: 20px;
height: 20px;
background-color: #ffffff;
}
.info_card .title .close_img .min {
width: 0;
height: 0;
font-size: 0;
overflow: hidden;
position: absolute;
border-width: 10px;
}
.info_card .title .close_img .top_img {
border-style: solid dashed dashed;
border-color: #000000 transparent transparent transparent;
top: -2px;
}
.info_card .title .close_img .right_img {
border-style: solid dashed dashed;
border-color: transparent #000000 transparent transparent;
left: 2px;
}
.info_card .title .close_img .bottom_img {
border-style: solid dashed dashed;
border-color: transparent transparent #000000 transparent;
top: 2px;
}
.info_card .title .close_img .left_img {
border-style: solid dashed dashed;
border-color: transparent transparent transparent #000000;
left: -2px;
}
.info_card span.cancle {
width: 0;
height: 0;
font-size: 0;
overflow: hidden;
position: absolute;
}
.info_card span.bot {
border-width: 20px;
border-style: solid dashed dashed;
border-color: #ffffff transparent transparent;
left: 80px;
bottom: -40px;
}
.info_card span.top {
border-width: 20px;
border-style: solid dashed dashed;
border-color: #c7c9c8 transparent transparent;
left: 80px;
bottom: -33px;
}
.info_card .content {
margin-top: 10px;
margin-left: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
var infoWindow;
function closeInfoWindow() {
infoWindow.close();
}
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
center: new TMap.LatLng(40.040422, 116.273521)
});
//初始化infoWindow
infoWindow = new TMap.InfoWindow({
map: map,
enableCustom: true,
position: new TMap.LatLng(40.040422, 116.273521),
content: '<div id="info_card" class="info_card"><div class="title"><span class="title_name">可拖动信息窗口</span><div class="close_img" onclick="closeInfoWindow()"><span class="min top_img"></span><span class="min right_img"></span><span class="min bottom_img"></span><span class="min left_img"></span></div></div><div align="left" class="content">请拖动我~</div><span class="cancle bot"></span><span class="cancle top"></span></div>',
});
const infoCard = document.getElementById('info_card');
let isDragging = false;
let offset = {
x: 0,
y: 0
}
// 鼠标拖动事件
infoCard.addEventListener('mousedown', (e) => {
e.stopPropagation();
isDragging = true;
// 优化拖动平滑:计算鼠标与DOM中心的偏移量
const rect = infoCard.getBoundingClientRect();
const rectX = rect.left + rect.width / 2;
const rectY = rect.top + rect.height / 2;
offset.x = rectX - e.clientX;
offset.y = rectY - e.clientY;
});
document.addEventListener('mousemove', (e) => {
e.stopPropagation();
if (isDragging) {
// 计算拖动时的经纬度坐标(补偿计算的偏移量)
const p = new TMap.Point(e.clientX + offset.x, e.clientY + offset.y);
const latLng = map.unprojectFromContainer(p);
infoWindow.setPosition(latLng);
}
});
document.addEventListener('mouseup', (e) => {
e.stopPropagation();
isDragging = false;
});
// 触屏拖动事件
infoCard.addEventListener('touchstart', (e) => {
e.stopPropagation();
isDragging = true;
// 优化拖动平滑:计算触摸点与DOM中心的偏移量
const touch = e.touches[0];
const rect = infoCard.getBoundingClientRect();
const rectX = rect.left + rect.width / 2;
const rectY = rect.top + rect.height / 2;
offset.x = rectX - touch.clientX;
offset.y = rectY - touch.clientY;
});
document.addEventListener('touchmove', (e) => {
e.stopPropagation();
if (isDragging) {
const touch = e.touches[0];
// 计算拖动时的经纬度坐标(补偿计算的偏移量)
const p = new TMap.Point(touch.clientX + offset.x, touch.clientY + offset.y);
const latLng = map.unprojectFromContainer(p);
infoWindow.setPosition(latLng);
}
});
document.addEventListener('touchend', (e) => {
e.stopPropagation();
isDragging = false;
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图叠加图层_自定义贴地图层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义贴地图层</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<canvas id="canvas" width=300 height=300 style="display: none;"></canvas>
<script type="text/javascript">
var center = new TMap.LatLng(39.98187197091523, 116.31111860275269);
//初始化地图
var map = new TMap.Map("container", {
zoom: 15, //设置地图缩放级别
center: center, //设置地图中心点坐标
});
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext("2d");
// 画布的宽高
var cWidth = canvas.width;
var cHeight = canvas.height;
// 中心点
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
// 半径
var radius = centerX;
var drawCircle = function (r, lineWidth = 1, color = "rgba(123,153,255,0.9)") {
ctx.beginPath();
ctx.setLineDash([]);
ctx.arc(centerX, centerY, r, 0, 2 * Math.PI);
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
ctx.stroke();
};
var drawSector = function (sAngle, eAngle) {
var blob = 50;
var increase = 0;
if (sAngle < eAngle) {
increase = (eAngle - sAngle) / blob;
} else if (sAngle > eAngle) {
increase = (Math.PI * 2 - sAngle + eAngle) / blob;
} else {
return;
}
var angle1 = sAngle;
var angle2 = sAngle + increase;
for (var i = 0; i < blob; i++) {
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, angle1, angle2);
ctx.fillStyle = "rgba(42,91,255," + i / blob + ")";
ctx.fill();
angle1 = angle2;
angle2 = angle1 + increase;
if (angle2 >= Math.PI * 2) {
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, angle1, Math.PI * 2);
ctx.fillStyle = "rgba(42,91,255," + i / blob + ")";
ctx.fill();
angle1 = 0;
angle2 = angle1 + increase;
}
}
};
var init = function () {
drawCircle(1 * centerY, 2.5);
ctx.fillStyle = "rgba(42,91,255,0.07)";
ctx.arc(150, 150, 150, 0, 2 * Math.PI);
ctx.fill();
drawCircle(0.6 * centerY);
drawCircle(0.3 * centerY);
};
function scan() {
var angle = Math.PI / 4;
var scanBegin = 0;
var scanEnd = angle;
var pointRadius = 1;
// 绘制雷达扫描
var move = () => {
ctx.clearRect(0, 0, cWidth, cHeight); // 清除画布
init(); // 重绘背景
drawSector(scanBegin, scanEnd); // 绘制扇形扫描区域
// 改变点的半径以及扇形的角度
pointRadius += 0.08;
scanBegin += angle / 25;
scanEnd = scanBegin + angle;
// 超过阈值变为初始值
if (scanBegin >= Math.PI * 2) {
scanBegin = 0;
scanEnd = scanBegin + angle;
}
if (pointRadius >= 7) pointRadius = 0;
// 再次绘制
canvasGroundLayer.refresh();
animID = window.requestAnimFrame(move);
}
window.requestAnimFrame = (function () {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
};
canvasGroundLayer.refresh();
})();
animID = window.requestAnimFrame(move);
}
scan();
var imageSW = new TMap.LatLng(39.97897813636327, 116.3060975074768);
var imageNE = new TMap.LatLng(39.98506162381882, 116.316397190094);
var canvasLatLngBounds = new TMap.LatLngBounds(imageSW, imageNE);
var canvasGroundLayer = new TMap.CanvasGroundLayer({
bounds: canvasLatLngBounds,
canvas: canvas, // canvas节点
map: map
})
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_实时路况.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>实时路况</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.control {
position: absolute;
top: 0px;
left: 0px;
z-index: 9999;
padding: 10px;
background-color: white;
}
</style>
<body>
<div class="control">
<p>
设置路况底图透明度:<input id="range" type="range" value="10" min="0" max="10" />
<span id="number">1</span>
</p>
</div>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.907093, 116.396875);
var range = document.querySelector('#range');
var number = document.querySelector('#number');
var map;
// 初始化地图 MapOptions文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#2
map = new TMap.Map('container', {
zoom: 11, // 设置地图缩放级别
center: center, // 设置地图中心点坐标
baseMap: [
{ type: 'vector' }, // 设置矢量底图
// traffic底图文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#7
{
type: 'traffic',
opacity: 1,
}, // 设置路况底图
],
});
range.addEventListener('change', function (e) {
// 根据范围值改变路况透明度
var val = e.target.value / 10;
number.innerText = val;
map.destroy(); // 销毁之前的地图
// 重新渲染地图
map = new TMap.Map('container', {
zoom: 11,
center: center,
baseMap: [
{ type: 'vector' },
{
type: 'traffic',
opacity: val,
},
],
});
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_地址至坐标转换.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地址至坐标转换</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:300px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<p><label>地址</label><input id='address' type="text" value='北京市海淀区彩和坊路海淀西大街74号' ><input id="convert" type="button" class="btn" value="转换为坐标" onclick="convert()" /></p>
<p><label>坐标</label><input id='location' type='text' disabled value='' /></p>
</div>
</body>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 14,
center: new TMap.LatLng(39.986785, 116.301012),
});
var geocoder = new TMap.service.Geocoder(); // 新建一个正逆地址解析类
var markers = new TMap.MultiMarker({
map: map,
geometries: [],
});
function convert() {
markers.setGeometries([]);
// 将给定的地址转换为坐标位置
geocoder
.getLocation({ address: document.getElementById('address').value })
.then((result) => {
markers.updateGeometries([
{
id: 'main',
position: result.result.location, // 将得到的坐标位置用点标记标注在地图上
},
]);
map.setCenter(result.result.location);
document.getElementById(
'location'
).value = result.result.location.toString();
// 显示坐标数值
});
}
</script>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_带飞地的区域实现高亮.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>飞地区域实现高亮</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=service"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
</body>
<script>
var map;
var district;
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
zoom: 8,
center: new TMap.LatLng(32.87441813772688, 119.32388989226047), // 设置地图中心点坐标
rotation: 0, // 旋转角度,
mapStyleId: 'style1',
baseMap: [
{ type: 'vector', features: ['base'] },
{
type: 'traffic',
opacity: 1,
},
],
renderOptions: {
enableBloom: true, // 是否启用泛光效果 注:为true才会有效果
},
});
highlight();
}
function highlight() {
// 服务类库文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocService
district = new TMap.service.District({
// 新建一个行政区划类
polygon: 2, // 返回行政区划边界的类型
});
// 关键字搜索行政区划,110000 = 北京行政区划代码
district.search({ keyword: 320000 }).then((result) => {
if (!result && !result.result) return;
console.log(result)
// 获取省市区列表及其边界信息
var paths = result.result[0][0].polygon;
// 外边界
var outPaths = [
new TMap.LatLng(30, 125),
new TMap.LatLng(36, 125),
new TMap.LatLng(36, 114),
new TMap.LatLng(30, 114),
]
// 设置地图边界范围
let boundary = new TMap.LatLngBounds(outPaths[3], outPaths[1]);
map.setBoundary(boundary);
// 绘制高亮区域
new TMap.MultiPolygon({
map: map,
styles: {
outPoly: new TMap.PolygonStyle({
color: '#000',
showBorder: false,
}),
},
geometries: [
{
styleId: 'outPoly', // 样式id
paths: [outPaths, ...paths],
},
],
});
let polylineGeos = paths.map((path) => {
path.push(path[0]); // 路线首位相连
return {
styleId: 'polyline',
paths: paths,
}
})
new TMap.MultiPolyline({
map: map,
styles: {
polyline: new TMap.PolylineStyle({
color: '#017cf7', // 线条填充色,
width: 8,
lineCap: 'round',
enableBloom: true, // 是否启用泛光 注:为true才会有效果
}),
},
geometries: polylineGeos,
});
});
}
</script>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_Marker文本背景色.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>设置Marker文本背景</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1">Marker背景样式1</button>
<button class="btn2">Marker背景样式2</button>
</div>
<div id="mapContainer"></div>
<script>
var center = new TMap.LatLng(40.040074, 116.273519); // 设置中心点坐标
var centerHeight = new TMap.LatLng(40.040074, 116.273519, 100); // 带高度的坐标
// 初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 17,
});
// 初始化Marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
marker: new TMap.MarkerStyle({
offset:{x:0, y:40},
color:'#3876ff',
backgroundColor: 'rgba(204, 134, 108,0.6)',
backgroundBorderColor: 'rgba(227, 200, 171,1.0)',
backgroundBorderRadius: 20,
backgroundBorderWidth: 4,
padding:'5px 15px'
}),
},
geometries: [
{
id: 'marker', // 点图形数据的标志信息
styleId: 'marker', // 样式id
position: center, // 标注点位置
content: '腾讯北京总部', // 标注文本
properties: {
// 标注点的属性数据
title: 'marker',
},
},
],
});
document.querySelector('button.btn1').onclick = function () {
marker.setStyles({
'marker': new TMap.MarkerStyle({
offset:{x:0, y:40}, // 标注点文本文字基于direction方位的偏移量
color:'#3876ff', // 字体颜色
backgroundColor: 'rgba(204, 134, 108,0.6)', // 文本背景颜色
backgroundBorderColor: 'rgba(227, 200, 171,1.0)', // 文本背景框边线颜色
backgroundBorderRadius: 20, // 文本背景框圆角
backgroundBorderWidth: 4, // 文字背景框边线宽度
padding:'5px 15px', // 标注点文字背景框内边距,单位为像素,属性支持接受1~2个值,规则符合css规范
})
});
};
document.querySelector('button.btn2').onclick = function () {
marker.setStyles({
'marker': new TMap.MarkerStyle({
width: 30, // 标注点图片的宽度
height: 40, // 标注点图片的高度
color:'#FFFFFF',
offset:{x:0, y:40},
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png',
size: 16,
backgroundColor: '#68b8ec',
backgroundWidth: 120, // 标注点文本背景的宽度
backgroundHeight: 30, // 标注点文本背景的高度
backgroundBorderColor: '#3470a1', // 文字背景框边线颜色
backgroundBorderRadius: 5,
backgroundBorderWidth: 4,
})
});
};
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_多边形点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>多边形点击事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#info {
display: none;
position: absolute;
left: 30px;
top: 90px;
background: #fff;
border-radius: 5px;
padding: 10px;
z-index: 9999;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="initMap()">
<div id="buttonContainer">
<input type="button" id = "bindClick" value="绑定点击事件">
<input type="button" id = "unbindClick" value="解绑点击事件">
</div>
<div id="info">
</div>
<div id="mapContainer"></div>
</body>
</html>
<script>
var map = null;
var polygon = null;
var infoDom = document.getElementById('info');
var bindBtn = document.getElementById('bindClick');
var unbindBtn = document.getElementById('unbindClick');
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486); // 设置中心点坐标
// 初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 16,
});
var buildingPaths = {
tencent: [
// 多边形的位置信息
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
],
other: [
new TMap.LatLng(40.041649, 116.275059),
new TMap.LatLng(40.040828, 116.275237),
new TMap.LatLng(40.040934, 116.276079),
new TMap.LatLng(40.041041, 116.276229),
new TMap.LatLng(40.041095, 116.276481),
new TMap.LatLng(40.041058, 116.276787),
new TMap.LatLng(40.041144, 116.277613),
new TMap.LatLng(40.041965, 116.277404),
new TMap.LatLng(40.041879, 116.276653),
new TMap.LatLng(40.041776, 116.276293),
new TMap.LatLng(40.041752, 116.276073),
new TMap.LatLng(40.041768, 116.275864),
],
};
// 初始化polygon
polygon = new TMap.MultiPolygon({
id: 'polygon-layer', // 图层id
map: map, // 显示多边形图层的底图
styles: {
// 多边形的相关样式
polygon: new TMap.PolygonStyle({
color: '#3777FF', // 面填充色
showBorder: false, // 是否显示拔起面的边线
borderColor: '#00FFFF', // 边线颜色
}),
},
geometries: [
{
id: 'polygonTen', // 多边形图形数据的标志信息
styleId: 'polygon', // 样式id
paths: buildingPaths.tencent, // 多边形的位置信息
properties: {
// 多边形的属性数据
title: '腾讯北京总部',
},
},
{
id: 'polygonSina', // 多边形图形数据的标志信息
styleId: 'polygon', // 样式id
paths: buildingPaths.other, // 多边形的位置信息
properties: {
// 多边形的属性数据
title: '其他建筑物',
},
},
],
});
}
var eventClick = function (res) {
var res = res && res.geometry;
if (res) {
infoDom.style.display = 'block';
infoDom.innerHTML = '多边形ID:' + res.id + '; 样式ID:' + res.styleId + '; 自定义字段:' + res.properties.title;
}
};
// 绑定点击事件
bindBtn.addEventListener('click', bindClick, false);
function bindClick() {
polygon.on('click', eventClick);
bindBtn.removeEventListener('click', bindClick, false); // 绑定polygon点击事件后,解绑绑定按钮(即只绑定一次polygon)
unbindBtn.addEventListener('click', unbindClick, false); // 绑定解绑按钮
}
// 解绑点击事件
function unbindClick() {
infoDom.style.display = 'none';
polygon.off('click', eventClick); // 解绑polygon
unbindBtn.removeEventListener('click', unbindClick, false);
bindBtn.addEventListener('click', bindClick, false);
}
</script>
FILE:references/jsapigl/demos/地图操作示例_调整图层顺序.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
<title>调整图层顺序</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#moveLayerBtn {
position: absolute;
left: 40px;
top: 40px;
z-index: 9999;
}
#moveLayerResetBtn {
position: absolute;
left: 40px;
top: 80px;
z-index: 9999;
}
</style>
<body onload="initMap()">
<div id="mapContainer">
<button id="moveLayerBtn" onclick="moveLayer()">移动到文字图层上方</button>
<button id="moveLayerResetBtn" onclick="moveReSet()">移动到基础底图上方</button>
</div>
<script type="text/javascript">
var imageTileLayer
var map
function initMap () {
var center = new TMap.LatLng(26.870355, 100.239704) //设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 15,
maxZoom: 16,
})
//初始化imageTileLayer
imageTileLayer = new TMap.ImageTileLayer({
getTileUrl: function (x, y, z) {
//拼接瓦片URL
var url =
'https://3gimg.qq.com/visual/lbs_gl_demo/image_tiles_layers/' +
z +
'/' +
x +
'_' +
y +
'.png'
return url
},
tileSize: 256, //瓦片像素尺寸
minZoom: 14, //显示自定义瓦片的最小级别
maxZoom: 16, //显示自定义瓦片的最大级别
visible: true, //是否可见
zIndex: 5000, //层级高度(z轴)
opacity: 0.95, //图层透明度:1不透明,0为全透明
map: map, //设置图层显示到哪个地图实例中
})
}
function moveLayer () {
var layerId = imageTileLayer.getId() // 获取图层ID
// 根据输入 LAYER_LEVEL 常量调整 layerId 对应图层的渲染层级 ,其中layerId可以通过图层getId方法获取。TMap.constants.LAYER_LEVEL 代表图层级别常量,见帮助文档(https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap)
map.moveLayer(layerId, TMap.constants.LAYER_LEVEL.TEXT)
}
// 移动图层至基础图层之上
function moveReSet () {
var layerId = imageTileLayer.getId() // 获取图层ID
// 根据输入 LAYER_LEVEL 常量调整 layerId 对应图层的渲染层级 ,其中layerId可以通过图层getId方法获取。TMap.constants.LAYER_LEVEL 代表图层级别常量,见帮助文档(https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap)
map.moveLayer(layerId, TMap.constants.LAYER_LEVEL.BASE)
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_地图加载完成事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图加载完成事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#tip {
position: absolute;
left: 20px;
top: 20px;
background: #FFF;
color: #666;
width: 200px;
height: 30px;
line-height: 30px;
border-radius: 3px;
padding-left: 5px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="tip">地图正在加载中......</div>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
center: new TMap.LatLng(39.984104, 116.307503)
});
//监听地图瓦片加载完成事件
map.on("tilesloaded", function () {
document.getElementById("tip").innerHTML = "地图瓦片已加载完成"
})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点聚合_基本点聚合功能.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>基本点聚合功能</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}
#mapContainer {
position: relative;
height: 100%;
width: 100%;
}
</style>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<body onload="init()">
<div id='mapContainer'></div>
<script>
var map;
function init() {
var drawContainer = document.getElementById('mapContainer');
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
map = new TMap.Map(drawContainer, {
zoom: 9,
pitch: 40,
center: center,
draggable: true,
scrollable: true,
mapStyleId: "style 1"
});
// 创建点聚合实例
var markerCluster = new TMap.MarkerCluster({
id: 'cluster',
map: map,
enableDefaultStyle: true, // 启用默认样式
minimumClusterSize: 2, // 形成聚合簇的最小个数
geometries: [{ // 点数组
position: new TMap.LatLng(39.953416, 116.480945)
},
{
position: new TMap.LatLng(39.984104, 116.407503)
},
{
position: new TMap.LatLng(39.908802, 116.497502)
},
{
position: new TMap.LatLng(40.040417, 116.373514)
},
{
position: new TMap.LatLng(39.953416, 116.380945)
},
{
position: new TMap.LatLng(39.984104, 116.307503)
},
{
position: new TMap.LatLng(39.908802, 116.397502)
},
{
position: new TMap.LatLng(40.040417, 116.273514)
},
],
zoomOnClick: true, // 点击簇时放大至簇内点分离
gridSize: 60, // 聚合算法的可聚合距离
averageCenter: false, // 每个聚和簇的中心是否应该是聚类中所有标记的平均值
maxZoom: 10 // 采用聚合策略的最大缩放级别
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_线条泛光效果.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>线条泛光效果</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77" ></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
</body>
<script>
function initMap() {
// 初始化地图
var map = new TMap.Map('container', {
// Map文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap
zoom: 16, // 地图缩放
center: new TMap.LatLng(40.047014719415365, 116.28359172117393), // 设置中心点,
mapStyleId: 'style2', // 地图个性化样式
renderOptions: {
enableBloom: true, // 是否启用泛光效果 注:为true才会有效果
},
baseMap: {
type: 'vector',
features: ['base', 'building3d'], // 隐藏矢量文字
},
});
var polyline = new TMap.MultiPolyline({
// MultiPolyline文档地址:https://lbs.qq.com/javascript_gl/doc/multiPolylineOptions.html
map: map,
styles: {
polyline: new TMap.PolylineStyle({
color: '#FFF0F5', // 线条填充色,
width: 13,
lineCap: 'round',
enableBloom: true, // 是否启用泛光 注:为true才会有效果
}),
},
geometries: [
{
styleId: 'polyline', // 样式id
paths: [
// 折线的位置信息
new TMap.LatLng(40.043684622817985, 116.28659936102349),
new TMap.LatLng(40.04979603261721, 116.2851305372144),
new TMap.LatLng(40.04901987016222, 116.28048618532625),
new TMap.LatLng(40.04295271447675, 116.28181142728783),
new TMap.LatLng(40.04367058295327, 116.28660618192646),
],
},
],
});
}
</script>
</html>
FILE:references/jsapigl/demos/自定义栅格图层_叠加自定义网格.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>叠加自定义网格</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="mapContainer"></div>
<script>
var center = new TMap.LatLng(31.71982285502907, 113.5575212744742);//设置中心点坐标
//初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 5,
});
// 瓦片画布
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 256;
canvas.height = 256;
//初始化imageTileLayer
var imageTileLayer = new TMap.ImageTileLayer({
getTileUrl: function (x, y, z) {
// 在左上角绘制坐标
ctx.font = '16px Arial';
ctx.fillStyle = 'black';
const textX = 10;
const textY = 20;
ctx.fillText(`(x, y, z)`, textX, textY);
// 绘制中间透明的图案
ctx.fillStyle = 'rgba(255, 0, 0, 0.0)';
ctx.fillRect(2, 2, 254, 254);
// 绘制四周描边
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.strokeRect(1, 1, 255, 255);
// 返回包含绘制图案的dataURL
const dataURL = canvas.toDataURL();
ctx.clearRect(0, 0, canvas.width, canvas.height);
return dataURL;
},
tileSize: 256, //瓦片像素尺寸
minZoom: 1, //显示自定义瓦片的最小级别
maxZoom: 20, //显示自定义瓦片的最大级别
visible: true, //是否可见
zIndex: 5000, //层级高度(z轴)
opacity: 0.95, //图层透明度:1不透明,0为全透明
map: map, //设置图层显示到哪个地图实例中
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/应用工具_设置编辑点为GL模式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>设置编辑点为GL模式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=vector,tools"></script>
<script charset="utf-8" src="https://mapapi.qq.com/web/jsapi/jsapi-gl/assets/geojson-demo.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#containerOne {
width: 100%;
height: 45%;
}
#containerTwo {
width: 100%;
height: 45%;
}
p {
margin: 0;
margin-bottom: 20px;
text-align: center;
}
</style>
<body>
<div id="containerOne"></div>
<p>地图一: SVG编辑点绘制模式-当数据形点较少时建议使用该模式;默认为该模式</p>
<div id="containerTwo"></div>
<p>地图二: GL编辑点绘制模式-当编辑的数据形点量级较大时使用该模式性能较好;但图形的渲染质量可能受机器配置影响</p>
<script>
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图一
var mapOne = new TMap.Map("containerOne", {
center,
zoom: 9, // 设置地图缩放级别
});
var geoLayerOne = new TMap.vector.GeoJSONLayer({
map: mapOne,
data: geojsonData,
polygonStyle: new TMap.PolygonStyle({
'showBorder': true, //是否显示边线
'borderColor': '#fff', //边线颜色
'borderWidth': 1 //边线宽度
})
});
// 获取geojson生成的polygon图层
var polygonLayer = geoLayerOne.getGeometryOverlay('polygon');
polygonLayer.setStyles({
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
},
);
// 初始化几何图形及编辑器
var editorOne = new TMap.tools.GeometryEditor({
map: mapOne, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: polygonLayer,
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
modifierShapeMode: TMap.tools.constants.MODIFIER_SHAPE_MODE.SVG, // 默认编辑点绘制模式为SVG
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
});
</script>
<script>
//初始化地图二
var mapTwo = new TMap.Map("containerTwo", {
center,
zoom: 9, // 设置地图缩放级别
});
var geoLayerTwo = new TMap.vector.GeoJSONLayer({
map: mapTwo,
data: geojsonData,
polygonStyle: new TMap.PolygonStyle({
'showBorder': true, //是否显示边线
'borderColor': '#fff', //边线颜色
'borderWidth': 1 //边线宽度
})
});
// 获取geojson生成的polygon图层
var polygonLayerTwo = geoLayerTwo.getGeometryOverlay('polygon');
polygonLayerTwo.setStyles({
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
},
);
// 初始化几何图形及编辑器
var editorTwo = new TMap.tools.GeometryEditor({
map: mapTwo, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: polygonLayerTwo,
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
modifierShapeMode: TMap.tools.constants.MODIFIER_SHAPE_MODE.GL, // 大数据量下的编辑点使用GL绘制模式编辑性能更佳
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_地图平滑变化至指定状态.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图平滑变化至指定状态</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#set-EaseTo{
position: absolute;
z-index: 9999;
left:20px;
top:10px;
}
</style>
<body>
<div id="container"></div>
<input type="button" id="set-EaseTo" onclick="easeTo()" value="easeTo">
<script>
var center = new TMap.LatLng(39.907093, 116.396875);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center,
zoom:15,
});
function easeTo() {
map.easeTo({zoom:17,rotation:90},{duration: 2000});//平滑缩放,旋转到指定级别
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/几何计算库_计算区域面积.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算简单多边形的面积</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 80px;
width: 420px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
line-height: 80px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 17
});
var path = [
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map,
geometries: [
{
id: 'polygon', //多边形图形数据的标志信息
paths: path, //多边形的位置信息
}
]
});
// 计算路径围成的多边形的面积
var area = TMap.geometry.computeArea(path);
var infoDom = document.getElementById('info');
infoDom.innerText = `腾讯北京总部大楼占地面积约为area.toFixed(2)平方米`;
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/个性化地图_玉露(绿色系活泼风格).html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>玉露(绿色系活泼风格)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<style type="text/css">
html,body{
width:100%;
height:100%;
overflow: hidden;
}
*{
margin:0px;
padding:0px;
}
body {
font: 12px/16px Verdana, Helvetica, Arial, sans-serif;
}
#container{
width: 100%;
height: 100%;
position: relative;
}
</style>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<script>
window.onload = function () {
function init() {
var map = new TMap.Map(document.getElementById("container"), {
center: new TMap.LatLng(39.916527, 116.397128),
zoom: 11,
mapStyleId: 'style3'
});
}
init();
}
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
FILE:references/jsapigl/demos/自定义覆盖物_添加动态gif图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>添加动态gif图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
var map;
var unit = 'px';
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.96030543872138, 116.25809083213608), // 设置地图中心点坐标
pitch: 40, // 俯仰度,
});
var flameList = [
new Flame({
map,
position: new TMap.LatLng(39.96030543872138, 116.25809083213608), // 动态图放置位置
url: 'https://mapapi.qq.com/web/visualization/demo-asset/flame.gif', // 路径
width: 80, // 宽度
height: 240, // 高度
}),
];
}
function Flame(options) {
// DOMOverlay文档地址: https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocDomOverlay
TMap.DOMOverlay.call(this, options);
}
Flame.prototype = new TMap.DOMOverlay();
// 初始化
Flame.prototype.onInit = function (options) {
this.position = options.position;
this.url = options.url;
this.width = options.width || 20;
this.height = options.height || 20;
};
// 创建DOM元素,返回一个DOMElement,使用this.dom可以获取到这个元素
Flame.prototype.createDOM = function () {
var img = document.createElement('img');
img.style.height = this.height + unit;
img.style.width = this.width + unit;
img.src = this.url;
return img;
};
// 更新DOM元素。在地图上移动/缩放后执行
Flame.prototype.updateDOM = function () {
if (!this.map) {
return;
}
// 经纬度坐标转转容器像素坐标
var pixel = this.map.projectToContainer(this.position);
// 使图中心点对齐经纬度坐标点
var width = this.dom.clientWidth / 2;
var left = pixel.getX() - width + unit;
var top = pixel.getY() - this.dom.clientHeight + unit;
this.dom.style.transform = `translate(left, top)`;
};
window.Flame = Flame;
</script>
</body>
</html>
FILE:references/jsapigl/demos/个性化地图_墨渊(夜间模式效果).html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>墨渊(夜间模式效果)</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<style type="text/css">
html,body{
width:100%;
height:100%;
position: relative;
overflow: hidden;
}
*{
margin:0px;
padding:0px;
}
body {
font: 12px/16px Verdana, Helvetica, Arial, sans-serif;
}
#container{
position: relative;
width: 100%;
height: 100%;
}
</style>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<script>
window.onload = function () {
function init() {
var map = new TMap.Map(document.getElementById("container"), {
center: new TMap.LatLng(39.916527, 116.397128),
zoom: 11,
mapStyleId: 'style2'
});
}
init();
}
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_自定义边线样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义边线样式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
function initMap() {
var center = new TMap.LatLng(39.98193773845799, 116.31178379058838);
//初始化地图
var map = new TMap.Map("container", {
zoom: 16, // 设置地图缩放级别
center: center // 设置地图中心点坐标
});
var circle = new TMap.MultiCircle({
map,
styles: { // 设置圆形样式
'circle': new TMap.CircleStyle({
'color': 'rgba(41,91,255,0.16)', // 面填充
'showBorder': true, // 是否显示边线
'borderColor': 'rgba(41,91,255,1)', // 边线颜色
'borderWidth': 2, // 边线宽度
}),
},
geometries: [{
styleId: 'circle',
center: center,
radius: 340,
}],
});
var polygonPath = [
new TMap.LatLng(39.98476568290943, 116.30566835403442),
new TMap.LatLng(39.97894525117546, 116.30614042282104),
new TMap.LatLng(39.978813710266024, 116.31120443344116),
new TMap.LatLng(39.97886303813672, 116.3168478012085),
new TMap.LatLng(39.985242475965705, 116.31631135940552),
];
var polygon = new TMap.MultiPolygon({
map, // 显示多边形图层的底图
styles: { // 多边形的相关样式
'polygon': new TMap.PolygonStyle({
'color': 'rgba(41,91,255,0.16)', // 面填充色
'showBorder': true, // 是否显示拔起面的边线
'borderColor': 'rgba(41,91,255,1)', // 边线颜色
'borderWidth': 3, // 边线宽度
'borderDashArray': [5, 5] // 虚线数组
}),
},
geometries: [{
id: 'polygon', // 多边形图形数据的标志信息
styleId: 'polygon', // 样式id
paths: polygonPath, // 多边形的位置信息
}],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_多边形拔起-3D棱柱.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>多边形拔起:3D棱柱</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map('mapContainer', {
center: new TMap.LatLng(40.036907,116.262577),//地图显示中心点
zoom:16, //缩放级别
pitch:45,
});
var path = [
[40.03847438521698,116.26140117645264],
[40.038178675807515,116.26140117645264],
[40.03791582192258,116.26208782196045],
[40.037488682198514,116.26165866851807],
[40.03719296851454,116.26243114471436],
[40.037258682777356,116.26328945159912],
[40.037455825185845,116.26311779022217],
[40.03768582394209,116.2642765045166],
[40.037882965115706,116.26423358917236],
[40.038342958971086,116.26384735107422],
[40.03870438053774,116.26341819763184],
[40.03854009824492,116.26260280609131],
[40.03854009824492,116.26174449920654]
]
//转为LatLng数组
path = path.map(p => {
return new TMap.LatLng(p[0], p[1]);
});
//初始化polygon
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map, //设置多边形图层显示到哪个地图实例中
//多边形样式
styles: {
'polygon': new TMap.ExtrudablePolygonStyle({
'color': 'rgba(0,125,255,0.9)', //面填充色
'showBorder':true, //是否显示拔起面的边线
'extrudeHeight':30, //多边形拔起高度
'borderColor': 'rgba(0,125,255,1)' //边线颜色
})
},
//多边形数据
geometries: [
{
'id': 'p1', //该多边形在图层中的唯一标识(删除、更新数据时需要)
'styleId': 'polygon', //绑定样式名
'paths': path, //多边形轮廓
}
]
});
}
</script>
FILE:references/jsapigl/demos/点标记与文本标记_文本标记样式自定义.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>文本标记样式自定义</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="initMap()">
<div id="buttonContainer">
<input type="button" onclick="setStyle('styleOne')" value="设置样式1">
<input type="button" onclick="setStyle('styleTwo')" value="设置样式2">
</div>
<div id="mapContainer"></div>
</body>
</html>
<script>
var map = null;
var label = null;
function initMap() {
var center = new TMap.LatLng(40.040074, 116.273519);//设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 18
});
//初始化label
label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
styles: {
'label': new TMap.LabelStyle({
'color': '#333', //颜色属性
'size': 20, //文字大小属性
'offset': { x: 0, y: 0 }, //文字偏移属性单位为像素
'angle': 0, //文字旋转属性
'alignment': 'center', //文字水平对齐属性
'verticalAlignment': 'middle' //文字垂直对齐属性
})
},
geometries: [{
'id': 'label', //点图形数据的标志信息
'styleId': 'label', //样式id
'position': center, //标注点位置
'content': '腾讯北京总部', //标注文本
'properties': { //标注点的属性数据
'title': 'label'
}
}]
});
}
function setStyle(type) {
switch (type) {
case 'styleOne':
label.setStyles({
'label': new TMap.LabelStyle({
'color': '#333',
'size': 20,
'offset': { x: 0, y: 0 },
'angle': 0,
'alignment': 'center',
'verticalAlignment': 'middle'
})
});
break;
case 'styleTwo':
label.setStyles({
'label': new TMap.LabelStyle({
'color': '#FF0000',
'size': 16,
'offset': { x: 2, y: 10 },
'angle': 45,
'alignment': 'left',
'verticalAlignment': 'top'
})
});
break;
default: ;
}
}
</script>
FILE:references/jsapigl/demos/地图操作示例_设置_获取地图视角.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>设置/获取地图视角</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info{
position: absolute;
background: #FFF;
width:300px;
height: 60px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
#set-pitch{
position: absolute;
left:20px;
top:10px;
}
#set-rotate{
position: absolute;
left:110px;
top:10px;
}
p{
margin: 0;
}
#pitch-info{
margin-top: 20px;
margin-bottom: 10px;
}
</style>
<body>
<div id="container"></div>
<div id="info">
<input type="button" id="set-pitch" onclick="setPitch()" value="setPitch">
<input type="button" id="set-rotate" onclick="setRotate()" value="setRotate">
<p id="pitch-info">当前地图俯仰角度为: 10</p>
<p id="rotate-info">当前地图旋转角度为: 20</p>
</div>
<script>
var pitchInfo = document.getElementById("pitch-info");
var rotateInfo = document.getElementById("rotate-info");
var txt = document.getElementById("txt");
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
rotation: 20,//设置地图旋转角度
pitch:10, //设置地图俯仰角度
center: center
});
//监听地图旋转事件
map.on("rotate",function(){
var rotation = map.getRotation(); //获取地图旋转角度
rotateInfo.innerHTML = "当前地图旋转角度为: " + parseInt(rotation);
})
//监听地图俯仰角度变化事件
map.on("pitch",function(){
var pitch = map.getPitch(); //获取地图旋转角度
pitchInfo.innerHTML = "当前地图俯仰角度为: " + parseInt(pitch);
})
function setRotate() {
map.setRotation(90);//设置地图旋转角度
}
function setPitch() {
map.setPitch(30);//设置地图俯仰角度
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义覆盖物_marker动画.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Marker跳动</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 90%;
}
#information {
margin: 5px;
}
/* marker跳动的动画 */
.markerBounce {
animation: bounce 0.5s infinite ease-in-out alternate;
}
/* marker飞入的动画 */
.markerFlash {
animation: flash 0.5s ease-in 1 normal forwards;
}
/* 跳动的动画 */
@keyframes bounce {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(0, -50px);
}
}
/* 飞入的动画 */
@keyframes flash {
0% {
transform: translate(0, -200px);
}
100% {
transform: translate(0, 0);
}
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="information">使用DOMoverlay实现跳动的Marker和飞入的Marker。2秒后,会有Marker飞入。</div>
<script>
//自定义DOM覆盖物 - 继承DOMOverlay
function myMarker(options) {
let mydom;
TMap.DOMOverlay.call(this, options);
}
myMarker.prototype = new TMap.DOMOverlay();
// 初始化
myMarker.prototype.onInit = function (options) {
this.position = options.position;
this.type = options.type; // 当前marker的类型,是跳动或飞入
}
// 创建
myMarker.prototype.createDOM = function () {
mydom = document.createElement('img'); // 新建一个img的dom
mydom.src = 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png';
mydom.style.cssText = [
'position: absolute;',
'top: 0px;',
'left: 0px;'
].join('');
switch (this.type) {
case 'bounce':
mydom.setAttribute('class', 'markerBounce'); // 给新建的dom添加marker类,添加跳动效果
break;
case 'flash':
mydom.setAttribute('class', 'markerFlash'); // 给新建的dom添加marker类,添加飞入效果
break;
}
return mydom;
}
// 更新DOM元素,在地图移动/缩放后执行
myMarker.prototype.updateDOM = function () {
if (!this.map) {
return;
}
let pixel = this.map.projectToContainer(this.position); // 经纬度坐标转容器像素坐标
let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
let top = pixel.getY() - this.dom.clientHeight + 'px';
// 使用top/left将DOM元素定位到指定位置
this.dom.style.top = top;
this.dom.style.left = left;
}
var map, markerBounce;
function initMap() {
// 初始化地图
map = new TMap.Map("container", {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503) // 设置地图中心点坐标
});
// 创建跳动的marker
markerBounce = new myMarker({
map,
position: new TMap.LatLng(39.96030543872138, 116.25809083213608),
type: 'bounce'
});
setTimeout(() => {
// 创建飞入的marker
var markerFlash = new myMarker({
map,
position: new TMap.LatLng(39.96030543872138, 116.35809083213608),
type: 'flash'
});
}, 2000);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_文本标记点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>文本标记点击事件</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#info {
display: none;
position: absolute;
left: 30px;
top: 90px;
background: #fff;
border-radius: 5px;
padding: 10px;
z-index: 9999;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="initMap()">
<div id="buttonContainer">
<input type="button" id = "bindClick" value="绑定点击事件">
<input type="button" id = "unbindClick" value="解绑点击事件">
</div>
<div id="info">
</div>
<div id="mapContainer"></div>
</body>
</html>
<script>
var map = null;
var label = null;
var infoDom = document.getElementById('info');
var bindBtn = document.getElementById('bindClick');
var unbindBtn = document.getElementById('unbindClick');
function initMap() {
var center = new TMap.LatLng(40.040452,116.274486);//设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 17
});
//初始化label
label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
styles: {
'label': new TMap.LabelStyle({
'color': '#3777FF', //颜色属性
'size': 20, //文字大小属性
'offset': { x: 0, y: 0 }, //文字偏移属性单位为像素
'angle': 0, //文字旋转属性
'alignment': 'center', //文字水平对齐属性
'verticalAlignment': 'middle' //文字垂直对齐属性
})
},
geometries: [{
'id': 'Tencent', //点图形数据的标志信息
'styleId': 'label', //样式id
'position': new TMap.LatLng(40.040074, 116.273519), //标注点位置
'content': '腾讯北京总部', //标注文本
'properties': { //标注点的属性数据
'title': 'Tencent'
}
},{
'id': 'Other',
'styleId': 'label',
'position':new TMap.LatLng(40.041334, 116.275886),
'content': '其他建筑物',
'properties': {
'title': 'Other'
}
}]
});
}
var eventClick = function (res) {
var res = res && res.geometry;
if (res) {
infoDom.style.display = 'block';
infoDom.innerHTML = 'label_ID:' + res.id + '; 自定义字段:' + res.properties.title + '; 文本内容:' + res.content + ';坐标:' + res.position.lat + ',' + res.position.lng;
}
}
//绑定点击事件
bindBtn.addEventListener('click',bindClick,false);
function bindClick() {
label.on('click', eventClick);
bindBtn.removeEventListener('click', bindClick, false); //绑定label点击事件后,解绑绑定按钮(即只绑定一次label)
unbindBtn.addEventListener('click',unbindClick,false); //绑定解绑按钮
}
//解绑点击事件
function unbindClick() {
infoDom.style.display = 'none';
label.off('click', eventClick); //解绑label点击事件
unbindBtn.removeEventListener('click', unbindClick, false);
bindBtn.addEventListener('click',bindClick,false); //重新绑定按钮点击事件
}
</script>
FILE:references/jsapigl/demos/服务类库_驾车路线规划.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>驾车路线规划</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=您的key&libraries=service"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<script>
var map;
function initMap() {
map = new TMap.Map('mapContainer', {
center: new TMap.LatLng(39.990619, 116.321277),
zoom: 14,
});
var startPosition = new TMap.LatLng(39.984039, 116.307630307503); // 路线规划起点
var endPosition = new TMap.LatLng(39.977263, 116.337063); // 路线规划终点
var marker = new TMap.MultiMarker({
// 创造MultiMarker显示起终点标记
id: 'marker-layer',
map: map,
styles: {
start: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png',
}),
end: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png',
}),
},
geometries: [
{
id: 'start',
styleId: 'start',
position: startPosition,
},
{
id: 'end',
styleId: 'end',
position: endPosition,
},
],
});
var driving = new TMap.service.Driving({
// 新建一个驾车路线规划类
mp: false, // 是否返回多方案
policy: 'PICKUP,NAV_POINT_FIRST', // 规划策略
});
driving.search({ from: startPosition, to: endPosition }).then((result) => {
// 搜索路径
result.result.routes[0].steps.forEach((step, index) => {
document.getElementById('instruction').innerHTML += `<p>index + 1. step.instruction</p>`;
// 展示路线引导
});
displayPolyline(result.result.routes[0].polyline); // 绘制路径折线
});
}
function displayPolyline(pl) {
// 创建 MultiPolyline显示路径折线
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer',
map: map,
styles: {
style_blue: new TMap.PolylineStyle({
color: '#3777FF',
width: 8,
borderWidth: 5,
borderColor: '#FFF',
lineCap: 'round',
}),
},
geometries: [
{
id: 'pl_1',
styleId: 'style_blue',
paths: pl,
},
],
});
}
</script>
<body onload="initMap()">
<div id="mapContainer"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<h4>驾车路线规划</h4><div id="instruction"></div></div>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_天空盒.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>天空盒</title>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #919aac;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
}
.btnContainer button:focus {
outline: none;
}
.btnContainer .btn1 {
padding: 10px 14px;
background: #3876ff;
border-radius: 2px;
border: none;
box-sizing: border-box;
font-size: 14px;
color: #fff;
line-height: 14px;
font-family: PingFangSC-Regular;
}
</style>
</head>
<body>
<div class="btnContainer">
<button class="btn1">点击切换天空</button>
</div>
<div id="mapContainer"></div>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<script>
window.onload = initMap;
var myMap;
var skyBoxConfig = [
{
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/skybox_day.png',
horizontal: TMap.constants.IMAGE_DISPLAY.REPEAT,
vertical: TMap.constants.IMAGE_DISPLAY.SCALE,
},
{
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/skybox_night.png',
horizontal: TMap.constants.IMAGE_DISPLAY.REPEAT,
vertical: TMap.constants.IMAGE_DISPLAY.SCALE,
},
];
var index = 0;
function initMap() {
myMap = new TMap.Map('mapContainer', {
zoom: 17.058177705186978,
pitch: 80,
rotation: 320.0308641975305,
center: new TMap.LatLng(39.92119128796626, 116.40041656534902),
baseMap: [
{
type: 'vector',
features: ['base', 'building3d']
}
],
renderOptions: {
skyOptions: skyBoxConfig[index],
}
});
document.querySelector('.btn1').addEventListener('click', () => {
if (index) {
myMap.setMapStyleId('DEFAULT');
myMap.setSkyOptions(skyBoxConfig[0]);
index = 0;
} else {
myMap.setMapStyleId('DARK');
myMap.setSkyOptions(skyBoxConfig[1]);
index = 1;
}
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/模型库_3DTiles模型.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>3DTiles模型</title>
</head>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=model"></script>
<script type="text/javascript">
let center = new TMap.LatLng(35.09260704247744, 111.06575713601103);
// 初始化地图
var map = new TMap.Map("container", {
rotation: 349.4, //设置地图旋转角度
pitch: 57.5, //设置俯仰角度(0~45)
zoom: 20,
center: center, //设置地图中心点坐标
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
let Tileset3D = new TMap.model.Tileset3D(
{
id: 'Tileset3DLayer',
url: 'https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/3dTilesGCJ02/tileset.json',
map,
offset: [0, 0, 0],
mask: [
new TMap.LatLng(35.09043722089289, 111.07136274422078),
new TMap.LatLng(35.09037943278917, 111.06080724240826),
new TMap.LatLng(35.10124876283047, 111.06080603927808),
new TMap.LatLng(35.10125914410558, 111.07145236890074),
],
}
);
</script>
</body>
</html>
FILE:references/jsapigl/demos/模型库_3Dmarker.html
<!DOCTYPE html>
<html>
<head>
<title>3Dmarker</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
* {
padding: 0px;
margin: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100vw;
height: 100vh;
}
</style>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=model"></script>
</head>
<body>
<div id='mapContainer'></div>
<script>
//初始化地图
var map = new TMap.Map("mapContainer", {
center: new TMap.LatLng(30.86495306997595, 104.20363363655235),
zoom: 20.4,
rotation: 123.7,
pitch: 72.5,
maxZoom: 24,
enableExtendZoom: true,
baseMap: {
type: "vector",
features: ["base", "building3d"],
},
});
var Marker3D = new TMap.model.Marker3D({
map,
styles: {
style1: new TMap.model.Marker3DStyle({
type:'location',
scale: 1,
}),
},
geometries: [
{
styleId: "style1",
position: new TMap.LatLng(30.86433422782985, 104.20327833274291),
},
{
styleId: "style1",
position: new TMap.LatLng(30.86452729325582, 104.20339118505024),
},
{
styleId: "style1",
position: new TMap.LatLng(30.86472363710379, 104.20351516710048),
},
{
styleId: "style1",
position: new TMap.LatLng(30.86495306997595, 104.20363363655235),
},
{
styleId: "style1",
position: new TMap.LatLng(30.86512547751484, 104.2037208303866),
},
{
styleId: "style1",
position: new TMap.LatLng(30.86533963574645, 104.20384146276342),
},
{
styleId: "style1",
position: new TMap.LatLng(30.865510415050675, 104.20393293929749),
},
],
});
map.on('click',(e)=>{
console.log(e)
})
</script>
</body>
FILE:references/jsapigl/demos/几何计算库_计算多边形的交集区域.html
<!DOCTYPE html>
<html>
<head>
<title>计算多边形与多边形的交集区域</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
</head>
<body>
<div id="mapContainer"></div>
<div class="btnContainer">
<button class="btn1">计算左1和左2交集区域</button>
<button class="btn2">计算左2和左3交集区域</button>
<button class="btn3">计算左3和左1交集区域</button>
</div>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<script>
window.onload = initMap;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486); //设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 11
});
const p1 = [
// 左多边形
new TMap.LatLng(40.01328762425296, 116.2893131162964),
new TMap.LatLng(39.97042810128267, 116.2446964303781),
new TMap.LatLng(39.952803198235955, 116.30063889381927),
new TMap.LatLng(40.00434982223178, 116.3377050664094)
];
const p2 = [
// 中间多边形
new TMap.LatLng(40.01854460829232, 116.34491237943803),
new TMap.LatLng(39.99856591344266, 116.30235492020074),
new TMap.LatLng(39.96516741243243, 116.35520853903654),
new TMap.LatLng(39.98147423199272, 116.37648726800035)
];
const p3 = [
// 右多边形
new TMap.LatLng(39.9404367485336, 116.33009007781648),
new TMap.LatLng(40.00250953930389, 116.37839317743058),
new TMap.LatLng(39.983315080275965, 116.40079037547248)
];
const geos = [
{
id: '1',
'paths': p1 //多边形的位置信息
},
{
id: '2',
'paths': p2 //多边形的位置信息
},
{
id: '3',
'paths': p3 //多边形的位置信息
}
];
//初始化polygon
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map, //显示多边形图层的底图
styles: {
heigh: new TMap.PolygonStyle({
color: 'rgb(255,255,0)'
})
},
geometries: geos
});
const testValue = {
'null': null,
'undefined': undefined,
'[]': [],
'': '',
};
document.querySelector('.btn1').addEventListener('click', (e) => {
var area = TMap.geometry.computePolygonIntersection(p1, p2);
console.log('计算左1和左2交集区域', area);
polygon.setGeometries([
...geos,
{
styleId: 'heigh',
paths: area
}
]);
});
document.querySelector('.btn2').addEventListener('click', (e) => {
var area = TMap.geometry.computePolygonIntersection(p2, p3);
console.log('计算左2和左3交集区域', area);
const g = [...geos];
if (area) {
g.push({
styleId: 'heigh',
paths: area
});
}
polygon.setGeometries(g);
});
document.querySelector('.btn3').addEventListener('click', (e) => {
var area = TMap.geometry.computePolygonIntersection(p1, p3);
console.log('计算左3和左1交集区域', area);
const g = [...geos];
if (area) {
g.push({
styleId: 'heigh',
paths: area
});
}
polygon.setGeometries(g);
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点聚合_自定义聚合样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义点聚合功能</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}
#mapContainer {
position: relative;
height: 100%;
width: 100%;
}
.clusterBubble {
border-radius: 50%;
color: #fff;
font-weight: 500;
text-align: center;
opacity: 0.88;
background-image: linear-gradient(139deg, #4294FF 0%, #295BFF 100%);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.20);
position: absolute;
top: 0px;
left: 0px;
}
</style>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<body onload="init()">
<div id='mapContainer'></div>
<script>
var map;
var ClusterBubbleClick;
function init() {
var drawContainer = document.getElementById('mapContainer');
var center = new TMap.LatLng(39.953416, 116.380945);
map = new TMap.Map('mapContainer', {
zoom: 11,
pitch: 40,
center: center
});
// 创建点聚合
var markerCluster = new TMap.MarkerCluster({
id: 'cluster',
map: map,
enableDefaultStyle: false, // 关闭默认样式
minimumClusterSize: 3,
geometries: [{ // 点数组
position: new TMap.LatLng(39.953416, 116.480945)
},
{
position: new TMap.LatLng(39.984104, 116.407503)
},
{
position: new TMap.LatLng(39.908802, 116.497502)
},
{
position: new TMap.LatLng(40.040417, 116.373514)
},
{
position: new TMap.LatLng(39.953416, 116.380945)
},
{
position: new TMap.LatLng(39.984104, 116.307503)
},
{
position: new TMap.LatLng(39.908802, 116.397502)
},
{
position: new TMap.LatLng(40.040417, 116.273514)
},
],
zoomOnClick: true,
gridSize: 60,
averageCenter: false
});
var clusterBubbleList = [];
var markerGeometries = [];
var marker = null;
// 监听聚合簇变化
markerCluster.on('cluster_changed', function (e) {
// 销毁旧聚合簇生成的覆盖物
if (clusterBubbleList.length) {
clusterBubbleList.forEach(function (item) {
item.destroy();
})
clusterBubbleList = [];
}
markerGeometries = [];
// 根据新的聚合簇数组生成新的覆盖物和点标记图层
var clusters = markerCluster.getClusters();
clusters.forEach(function (item) {
if (item.geometries.length > 1) {
let clusterBubble = new ClusterBubble({
map,
position: item.center,
content: item.geometries.length,
});
clusterBubble.on('click', () => {
map.fitBounds(item.bounds);
});
clusterBubbleList.push(clusterBubble);
} else {
markerGeometries.push({
position: item.center
});
}
});
if (marker) {
// 已创建过点标记图层,直接更新数据
marker.setGeometries(markerGeometries);
} else {
// 创建点标记图层
marker = new TMap.MultiMarker({
map,
styles: {
default: new TMap.MarkerStyle({
'width': 34,
'height': 42,
'anchor': {
x: 17,
y: 21,
},
'src': 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/marker_blue.png',
}),
},
geometries: markerGeometries
});
}
});
}
// 以下代码为基于DOMOverlay实现聚合点气泡
function ClusterBubble(options) {
TMap.DOMOverlay.call(this, options);
}
ClusterBubble.prototype = new TMap.DOMOverlay();
ClusterBubble.prototype.onInit = function (options) {
this.content = options.content;
this.position = options.position;
};
// 销毁时需要删除监听器
ClusterBubble.prototype.onDestroy = function() {
this.dom.removeEventListener('click', this.onClick);
this.removeAllListeners();
};
ClusterBubble.prototype.onClick = function() {
this.emit('click');
};
// 创建气泡DOM元素
ClusterBubble.prototype.createDOM = function () {
var dom = document.createElement('div');
dom.classList.add('clusterBubble');
dom.innerText = this.content;
dom.style.cssText = [
'width: ' + (40 + parseInt(this.content) * 2) + 'px;',
'height: ' + (40 + parseInt(this.content) * 2) + 'px;',
'line-height: ' + (40 + parseInt(this.content) * 2) + 'px;',
].join(' ');
// 监听点击事件,实现zoomOnClick
this.onClick = this.onClick.bind(this);
// pc端注册click事件,移动端注册touchend事件
dom.addEventListener('click', this.onClick);
return dom;
};
ClusterBubble.prototype.updateDOM = function () {
if (!this.map) {
return;
}
// 经纬度坐标转容器像素坐标
let pixel = this.map.projectToContainer(this.position);
// 使文本框中心点对齐经纬度坐标点
let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
let top = pixel.getY() - this.dom.clientHeight / 2 + 'px';
this.dom.style.transform = `translate(left, top)`;
this.emit('dom_updated');
};
window.ClusterBubble = ClusterBubble;
</script>
</body>
</html>
FILE:references/jsapigl/demos/应用工具_测量工具.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>测量工具</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
.toolControl {
width: 100px;
position: absolute;
top: 10px;
left: 10px;
z-index: 1001;
}
.toolControl div {
width: 60px;
height: 30px;
line-height: 30px;
font-size: 18px;
padding: 4px;
border-radius: 3px;
text-align: center;
margin: 2px;
cursor: pointer;
}
.toolControl .active {
background-color: #548efd;
color: #fff;
}
.toolControl .inactive {
background-color: #ffffff;
}
#distance,
#area {
width: 160px;
font-size: 16px;
margin-top: 20px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div class="toolControl">
<div onclick="enable(true)" id="enable" class="active">启用</div>
<div onclick="enable(false)" id="disable" class="inactive">禁用</div>
<div onclick="measure('distance')" id="distance" class="inactive">点击这里开始距离测量</div>
<div onclick="measure('area')" id="area" class="inactive">点击这里开始面积测量</div>
</div>
<script type="text/javascript">
var map; // 地图
var measureTool; // 测量工具
var enableButton = document.getElementById('enable'); // 启用
var disableButton = document.getElementById('disable'); // 禁用
var distanceButton = document.getElementById('distance'); // 距离测量
var areaButton = document.getElementById('area'); // 面积测量
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503), // 设置地图中心点坐标
});
// 创建测量工具
measureTool = new TMap.tools.MeasureTool({
// TMap.tools.MeasureTool文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor#5F
map: map,
});
}
function measure(type) {
if (disableButton.className === 'active') {
// 禁用的情况下提示并return
alert('请启用测量工具');
return;
}
enableButton.className = 'active';
// 区分距离或面积测量
switch (type) {
case 'distance':
// 距离测量
distanceButton.className = 'active';
distanceButton.innerText = '点击这里测量结束';
measureTool.measureDistance().then(() => {
enableButton.className = 'active';
distanceButton.className = 'inactive';
distanceButton.innerText = '点击这里开始距离测量';
});
break;
case 'area':
// 面积测量
areaButton.className = 'active';
areaButton.innerText = '点击这里测量结束';
measureTool.measureArea().then(() => {
enableButton.className = 'active';
areaButton.className = 'inactive';
areaButton.innerText = '点击这里开始面积测量';
});
break;
}
}
function enable(on) {
if (on) {
measureTool.enable();
enableButton.className = 'active';
disableButton.className = 'inactive';
} else {
// 禁用重置样式
measureTool.disable();
enableButton.className = 'inactive';
disableButton.className = 'active';
distanceButton.className = 'inactive';
areaButton.className = 'inactive';
}
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点聚合_点聚合跨图层碰撞.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>点聚合跨图层碰撞</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}
#mapContainer {
position: relative;
height: 100%;
width: 100%;
}
.clusterBubble {
border-radius: 50%;
color: #fff;
font-weight: 500;
text-align: center;
opacity: 0.88;
background-image: linear-gradient(139deg, #4294ff 0%, #295bff 100%);
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2);
position: absolute;
top: 0px;
left: 0px;
}
</style>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
></script>
<body onload="init()">
<div id="mapContainer"></div>
<script>
var map;
var ClusterBubbleClick;
function init() {
var drawContainer = document.getElementById('mapContainer');
var center = new TMap.LatLng(39.99712168854304, 116.27367664499832);
map = new TMap.Map('mapContainer', {
zoom: 9.674727565875209,
pitch: 0,
center: center
});
new TMap.MultiMarker({
map: map,
geometries: [
{
'position': new TMap.LatLng(39.92309750125607, 116.38199473668828), //标注点位置
rank: 4
}
],
collisionOptions: {
sameSource: true,
crossSource: true,
vectorBaseMapSource: true
},
zIndex: 30
});
// 创建点聚合
var markerCluster = new TMap.MarkerCluster({
id: 'cluster',
map: map,
enableDefaultStyle: false, // 关闭默认样式
minimumClusterSize: 3,
geometries: [
{
// 点数组
position: new TMap.LatLng(39.953416, 116.480945)
},
{
position: new TMap.LatLng(39.984104, 116.407503)
},
{
position: new TMap.LatLng(39.908802, 116.497502)
},
{
position: new TMap.LatLng(40.040417, 116.373514)
},
{
position: new TMap.LatLng(39.953416, 116.380945)
},
{
position: new TMap.LatLng(39.984104, 116.307503)
},
{
position: new TMap.LatLng(39.908802, 116.397502)
},
{
position: new TMap.LatLng(40.040417, 116.273514)
}
],
zoomOnClick: true,
gridSize: 60,
averageCenter: false
});
var clusterBubbleList = [];
var markerGeometries = [];
var marker = null;
// 监听聚合簇变化
markerCluster.on('cluster_changed', function (e) {
// 销毁旧聚合簇生成的覆盖物
if (clusterBubbleList.length) {
clusterBubbleList.forEach(function (item) {
item.destroy();
});
clusterBubbleList = [];
}
markerGeometries = [];
// 根据新的聚合簇数组生成新的覆盖物和点标记图层
var clusters = markerCluster.getClusters();
clusters.forEach(function (item) {
if (item.geometries.length > 1) {
let clusterBubble = new ClusterBubble({
map,
position: item.center,
content: item.geometries.length,
zIndex: 40,
collisionOptions: {
crossSource: true,
vectorBaseMapSource: true
}
});
clusterBubble.on('click', () => {
map.fitBounds(item.bounds);
});
clusterBubbleList.push(clusterBubble);
} else {
markerGeometries.push({
position: item.center
});
}
});
if (marker) {
// 已创建过点标记图层,直接更新数据
marker.setGeometries(markerGeometries);
} else {
// 创建点标记图层
marker = new TMap.MultiMarker({
map,
styles: {
default: new TMap.MarkerStyle({
'width': 34,
'height': 42,
'anchor': {
x: 17,
y: 21
},
'src': 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/marker_blue.png'
})
},
zIndex: 40,
collisionOptions: {
crossSource: true,
vectorBaseMapSource: true
},
geometries: markerGeometries
});
}
});
map.rotateTo(286,{duration: 5000})
}
// 以下代码为基于DOMOverlay实现聚合点气泡
function ClusterBubble(options) {
TMap.DOMOverlay.call(this, options);
}
ClusterBubble.prototype = new TMap.DOMOverlay();
ClusterBubble.prototype.onInit = function (options) {
this.content = options.content;
this.position = options.position;
this.zIndex = options.zIndex;
};
// 销毁时需要删除监听器
ClusterBubble.prototype.onDestroy = function () {
this.dom.removeEventListener('click', this.onClick);
this.removeAllListeners();
};
ClusterBubble.prototype.onClick = function () {
this.emit('click');
};
// 创建气泡DOM元素
ClusterBubble.prototype.createDOM = function () {
var dom = document.createElement('div');
dom.classList.add('clusterBubble');
dom.innerText = this.content;
dom.style.cssText = [
'width: ' + (40 + parseInt(this.content) * 2) + 'px;',
'height: ' + (40 + parseInt(this.content) * 2) + 'px;',
'line-height: ' + (40 + parseInt(this.content) * 2) + 'px;',
'z-index: ' + `this.zIndex`,
].join(' ');
// 监听点击事件,实现zoomOnClick
this.onClick = this.onClick.bind(this);
// pc端注册click事件,移动端注册touchend事件
dom.addEventListener('click', this.onClick);
return dom;
};
ClusterBubble.prototype.updateDOM = function () {
if (!this.map) {
return;
}
// 经纬度坐标转容器像素坐标
let pixel = this.map.projectToContainer(this.position);
// 使文本框中心点对齐经纬度坐标点
let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
let top = pixel.getY() - this.dom.clientHeight / 2 + 'px';
this.dom.style.transform = `translate(left, top)`;
this.emit('dom_updated');
};
window.ClusterBubble = ClusterBubble;
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义覆盖物_SVG图层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>自定义DOM图层-Marker</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
var SVG_NS = 'http://www.w3.org/2000/svg';
var colorList = ['#7AF4FF', '#67D7FF', '#52B5FF', '#295BFF'];
var mapContainer = document.getElementById('container');
// 自定义SVG图层 - 继承DOMOverlay
function SvgMarker(options) {
TMap.DOMOverlay.call(this, options);
}
SvgMarker.prototype = new TMap.DOMOverlay();
// 初始化
SvgMarker.prototype.onInit = function (options) {
this.options = options.options;
this.map = options.map;
};
// 销毁时需解绑事件监听
SvgMarker.prototype.onDestroy = function () {
if (this.onClick) {
this.dom.removeEventListener(this.onClick);
}
};
// 创建DOM元素,返回一个DOMElement,使用this.dom可以获取到这个元素
SvgMarker.prototype.createDOM = function () {
var svg = document.createElementNS(SVG_NS, 'svg');
svg.id = 'svgDom';
svg.setAttribute('width', mapContainer.offsetWidth);
svg.setAttribute('height', mapContainer.offsetHeight);
svg.style.cssText = 'position:absolute;top:0px;left:0px;';
var group = []; // 定义g元素数组
var circleShape = []; // 定义circle元素数组
var textShape = []; // 定义text元素数组
// 遍历传入参数,创建同等数量的svg下元素节点并绑定事件
for (var i = 0; i < this.options.length; i++) {
var createCenter = this.map.projectToContainer(this.options[i].position);
group[i] = document.createElementNS(SVG_NS, 'g');
// 在中心创建一个圆形
circleShape[i] = document.createElementNS(SVG_NS, 'circle');
circleShape[i].setAttribute('style', 'fill: blue;stroke:#FFFFFF;opacity:0.9;');
circleShape[i].setAttribute('cx', createCenter.x);
circleShape[i].setAttribute('cy', createCenter.y);
circleShape[i].setAttribute('r', 20);
group[i].appendChild(circleShape[i]);
// 绘制文字
textShape[i] = document.createElementNS(SVG_NS, 'text');
textShape[i].setAttribute('x', createCenter.x);
textShape[i].setAttribute('y', createCenter.y + 5); // +5是为了让文字向下偏移5像素,使文字居中
textShape[i].setAttribute('text-anchor', 'middle');
textShape[i].setAttribute('fill', '#FFFFFF');
textShape[i].innerHTML = this.options[i].id + 1;
svg.appendChild(group[i]);
group[i].appendChild(textShape[i]);
this.onMouseEnter = function(e) {
// DOMOverlay继承自EventEmitter,可以使用emit触发事件
// 动态修改circle颜色,所以选择传入circle节点
this.emit('mouseenter', e.target.firstChild);
}.bind(this);
this.onMouseLeave = function(e) {
this.emit('mouseleave', e.target.firstChild);
}.bind(this);
group[i].addEventListener('mouseenter', this.onMouseEnter);
group[i].addEventListener('mouseleave', this.onMouseLeave);
}
return svg;
};
// 更新DOM元素,在地图移动/缩放后执行
SvgMarker.prototype.updateDOM = function () {
if (!this.map) {
return;
}
// 经纬度坐标转容器像素坐标
for (var j = 0; j < this.options.length; j++) {
var pixel = this.map.projectToContainer(this.options[j].position);
var updateCenter = this.map.projectToContainer(this.options[j].position);
this.dom.children[j]
.getElementsByTagName('circle')[0]
.setAttribute('cx', updateCenter.x);
this.dom.children[j]
.getElementsByTagName('circle')[0]
.setAttribute('cy', updateCenter.y);
this.dom.children[j]
.getElementsByTagName('text')[0]
.setAttribute('x', updateCenter.x);
this.dom.children[j]
.getElementsByTagName('text')[0]
.setAttribute('y', updateCenter.y + 5);
}
};
window.SvgMarker = SvgMarker;
</script>
<script type="text/javascript">
function initMap() {
// 初始化地图
var map = new TMap.Map('container', {
zoom: 12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503) // 设置地图中心点坐标
});
// 初始化自定义marker类
var marker = new SvgMarker({
map,
options: [
{
position: new TMap.LatLng(39.96030543872138, 116.25809083213608),
id: 0,
},
{
position: new TMap.LatLng(39.9986945980902, 116.33598362780685),
id: 1
},
{
position: new TMap.LatLng(40.02906301748584, 116.25499991104516),
id: 2,
}
]
});
map.res
marker.on('mouseenter', function(e) {
e.setAttribute('style', 'fill: red;stroke:red; opacity:0.9;');
});
marker.on('mouseleave', function(e) {
e.setAttribute('style', 'fill: blue;stroke:#FFFFFF;opacity:0.9;');
});
// 监听地图容器变化,动态修改svg图层大小,防止更新时元素移出svg图区
map.on('resize', function() {
document.getElementById('svgDom').setAttribute('width', mapContainer.offsetWidth);
document.getElementById('svgDom').setAttribute('height', mapContainer.offsetHeight);
})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_图文标记.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>图文标记</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
var map;
function initMap() {
var center = new TMap.LatLng(39.96636559909425, 116.30113309988496);
// 初始化地图
map = new TMap.Map('container', {
zoom: 13, // 设置地图缩放级别
pitch: 30,
center: center, // 设置地图中心点坐标
// mapStyleId: "style1", //个性化样式
baseMap: {
type: 'vector',
features: ['base', 'building3d'], // 隐藏矢量文字
},
});
// marker文字在图片外 MultiMarker文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
new TMap.MultiMarker({
map: map, // 显示Marker图层的底图
styles: {
small: new TMap.MarkerStyle({
// 点标注的相关样式
width: 34, // 宽度
height: 46, // 高度
anchor: { x: 17, y: 23 }, // 标注点图片的锚点位置
src: 'https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/small.png', // 标注点图片url或base64地址
color: '#333', // 标注点文本颜色
size: 16, // 标注点文本文字大小
direction: 'bottom', // 标注点文本文字相对于标注点图片的方位
offset: { x: 0, y: 8 }, // 标注点文本文字基于direction方位的偏移属性
strokeColor: '#fff', // 标注点文本描边颜色
strokeWidth: 2, // 标注点文本描边宽度
}),
big: new TMap.MarkerStyle({
width: 58,
height: 76,
anchor: { x: 36, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/big.png',
color: '#333',
size: 22,
direction: 'bottom',
strokeColor: '#fff',
offset: { x: 0, y: 10 },
strokeWidth: 2,
}),
},
enableCollision: true, // 开启碰撞
geometries: [
{
styleId: 'small', // 对应中的样式id
position: new TMap.LatLng(39.9944, 116.33066), // 标注点位置
content: '全聚德', // 标注点文本
},
{
styleId: 'big',
position: new TMap.LatLng(39.978867, 116.371452),
content: '北平楼',
},
{
styleId: 'small',
position: new TMap.LatLng(39.92442, 116.29199),
content: '大董烤鸭店',
},
{
styleId: 'small',
position: new TMap.LatLng(39.97912, 116.30563),
content: '白家大院',
},
{
styleId: 'small',
position: new TMap.LatLng(39.93282, 116.34813),
content: '四世同堂',
},
{
styleId: 'small',
position: new TMap.LatLng(39.96305, 116.38794),
content: '便宜坊',
},
{
styleId: 'small',
position: new TMap.LatLng(39.993057, 116.209706),
content: '小吊梨汤',
},
{
styleId: 'small',
position: new TMap.LatLng(39.972919, 116.229874),
content: '那家山城',
},
],
});
// marker文字在图片内
new TMap.MultiMarker({
map: map,
styles: {
default: new TMap.MarkerStyle({
width: 92, // 宽度
height: 92, // 高度
anchor: { x: 46, y: 46 }, // 锚点位置
src: 'https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/circle.png', // 标注点图片url或base64地址
color: '#fff', // 标注点文本颜色
size: 22, // 标注点文本文字大小
offset: { x: 0, y: 16 }, // 标注点文本文字基于direction方位的偏移属性
}),
},
geometries: [
{
position: new TMap.LatLng(39.9543498091366, 116.3064286563349), // 标注点位置
content: '41', // 标注点文本
},
{
position: new TMap.LatLng(39.9891175495767, 116.28076993243667),
content: '34',
},
{
position: new TMap.LatLng(39.95959650879512, 116.26144674251236),
content: '9',
},
{
position: new TMap.LatLng(39.96950944017182, 116.34323407250122),
content: '7',
},
],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_简单多边形.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>简单多边形</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486); // 设置中心点坐标
// 初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 16,
});
var path = [
// 多边形的位置信息
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
// 初始化polygon
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', // 图层id
map: map, // 显示多边形图层的底图
styles: {
// 多边形的相关样式
polygon: new TMap.PolygonStyle({
color: '#3777FF', // 面填充色
showBorder: false, // 是否显示拔起面的边线
borderColor: '#00FFFF', // 边线颜色
}),
},
geometries: [
{
id: 'polygon', // 多边形图形数据的标志信息
styleId: 'polygon', // 样式id
paths: path, // 多边形的位置信息
properties: {
// 多边形的属性数据
title: 'polygon',
},
},
],
});
}
</script>
FILE:references/jsapigl/demos/地图操作示例_切换底图类型.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>切换底图类型</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btn {
position: absolute;
top: 30px;
z-index: 1001;
padding: 6px 8px;
}
.btn-left {
left: 30px;
}
.btn-right {
left: 150px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<input type="button" class="btn btn-left" onclick="changeBaseMap({type: 'satellite'})" value="设置为卫星图">
<input type="button" class="btn btn-right" onclick="changeBaseMap({type: 'vector'})" value="设置为矢量图">
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
map = new TMap.Map("container", {
zoom: 12,//设置地图缩放级别
center: center,//设置地图中心点坐标
});
}
// 动态设置底图类型
function changeBaseMap(baseMap) {
map.setBaseMap(baseMap);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_关键字输入提示.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>关键字输入提示</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
#suggestionList {
list-style-type: none;
padding: 0;
margin: 0;
}
#suggestionList li a {
margin-top: -1px;
background-color: #f6f6f6;
text-decoration: none;
font-size: 18px;
color: black;
display: block;
}
#suggestionList li .item_info{
font-size: 12px;
color:grey;
}
#suggestionList li a:hover:not(.header) {
background-color: #eee;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<p>输入关键字,将展示相关地点提示,点击提示可定位到该处。</p>
<input id='keyword' type="text" value='' oninput="getSuggestions()"><input id="search" type="button" class="btn" value="搜索" onclick="searchByKeyword()" />
<ul id="suggestionList">
</ul>
</div>
</body>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 14,
center: new TMap.LatLng(40.0402718, 116.2735831),
});
var suggestionList = [];
var search = new TMap.service.Search({ pageSize: 10 }); // 新建一个地点搜索类
var suggest = new TMap.service.Suggestion({
// 新建一个关键字输入提示类
pageSize: 10, // 返回结果每页条目数
region: '北京', // 限制城市范围
regionFix: true, // 搜索无结果时是否固定在当前城市
});
var markers = new TMap.MultiMarker({
map: map,
geometries: [],
});
var infoWindowList = Array(10);
function getSuggestions() {
// 使用者在搜索框中输入文字时触发
var suggestionListContainer = document.getElementById('suggestionList');
suggestionListContainer.innerHTML = '';
var keyword = document.getElementById('keyword').value;
if (keyword) {
suggest
.getSuggestions({ keyword: keyword, location: map.getCenter() })
.then((result) => {
// 以当前所输入关键字获取输入提示
suggestionListContainer.innerHTML = '';
suggestionList = result.data;
suggestionList.forEach((item, index) => {
suggestionListContainer.innerHTML += `<li><a href="#" onclick="setSuggestion(index)">item.title<span class="item_info">item.address</span></a></li>`;
});
})
.catch((error) => {
console.log(error);
});
}
}
function setSuggestion(index) {
// 点击输入提示后,于地图中用点标记绘制该地点,并显示信息窗体,包含其名称、地址等信息
infoWindowList.forEach((infoWindow) => {
infoWindow.close();
});
infoWindowList.length = 0;
document.getElementById('keyword').value = suggestionList[index].title;
document.getElementById('suggestionList').innerHTML = '';
markers.setGeometries([]);
markers.updateGeometries([
{
id: '0', // 点标注数据数组
position: suggestionList[index].location,
},
]);
var infoWindow = new TMap.InfoWindow({
map: map,
position: suggestionList[index].location,
content: `<h3>suggestionList[index].title</h3><p>地址:suggestionList[index].address</p>`,
offset: { x: 0, y: -50 },
});
infoWindowList.push(infoWindow);
map.setCenter(suggestionList[index].location);
markers.on('click', (e) => {
infoWindowList[Number(e.geometry.id)].open();
});
}
function searchByKeyword() {
// 关键字搜索功能
infoWindowList.forEach((infoWindow) => {
infoWindow.close();
});
infoWindowList.length = 0;
markers.setGeometries([]);
search
.searchRectangle({
keyword: document.getElementById('keyword').value,
bounds: map.getBounds(),
})
.then((result) => {
result.data.forEach((item, index) => {
var geometries = markers.getGeometries();
var infoWindow = new TMap.InfoWindow({
map: map,
position: item.location,
content: `<h3>item.title</h3><p>地址:item.address</p><p>电话:item.tel</p>`,
offset: { x: 0, y: -50 },
});
infoWindow.close();
infoWindowList[index] = infoWindow;
geometries.push({
id: String(index),
position: item.location,
});
markers.updateGeometries(geometries);
markers.on('click', (e) => {
infoWindowList[Number(e.geometry.id)].open();
});
});
});
}
</script>
</html>
FILE:references/jsapigl/demos/几何计算库_计算线上距离P点最近的点.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算线上距离P最近的点</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 80px;
width: 400px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
line-height: 80px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.02288398281758, 116.28905901239955);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 14,
baseMap:[ {
type: "vector",
features: ['base','label'], // 隐藏矢量文字
}]
});
var p = new TMap.LatLng(40.02797812361742, 116.30163420885106);
var path = [new TMap.LatLng(40.040452, 116.273486), new TMap.LatLng(40.006540, 116.303695)];
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer', //图层唯一标识
map: map,//绘制到目标地图
//折线数据定义
geometries: [
{
id: 'line',
paths: path
}
]
});
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
marker: new TMap.MarkerStyle({
size: 16, //文字大小属性
offset: {
x: 0,
y: 35
}
})
},
geometries: [
{
styleId: 'marker',
position: p,
content: 'P'
},
{
styleId: 'marker',
position: TMap.geometry.computeClosestPointOnLine(p, path),
content: '线上距离P最近的点'
},
],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线-彩虹线.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>彩虹线</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
position: relative;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
var map;
var paths = [
new TMap.LatLng(40.04151962242275, 116.27143444767171),
new TMap.LatLng(40.041798843759516, 116.27350477237314),
new TMap.LatLng(40.041950772690726, 116.2745721158808),
new TMap.LatLng(40.040443787455565, 116.27490465511983),
new TMap.LatLng(40.03896140705949, 116.27522646715988),
new TMap.LatLng(40.03910484067877, 116.27616723536175),
new TMap.LatLng(40.039495821815954, 116.27838901039286),
];
function initMap() {
var center = new TMap.LatLng(40.040452, 116.274586);
//初始化地图
map = new TMap.Map("container", {
zoom: 17,//设置地图缩放级别
center: center,//设置地图中心点坐标
});
var polylineLayer = new TMap.MultiPolyline({
map: map, // 绘制到目标地图
// 折线样式定义
styles: {
'style_blue': new TMap.PolylineStyle({
color: '#3777FF', //线填充色
width: 10, //折线宽度
borderWidth: 0, //边线宽度
showArrow: true,
arrowOptions: {
space: 70
},
lineCap: 'round',
}),
},
geometries: [{
styleId: 'style_blue',
rainbowPaths: [ // 彩虹线数组
{
path: [paths[0], paths[1]],
color: 'rgba(0, 180, 0, 1)',
},
{
path: [paths[1], paths[2]],
color: 'rgba(255, 0, 0, 1)',
},
{
path: [paths[2], paths[3]],
color: 'rgba(204,153, 0, 1)',
},
{
path: [paths[3], paths[4]],
color: 'rgba(255, 0, 0, 1)',
},
{
path: [paths[4], paths[5]],
color: 'rgba(204,153, 0, 1)',
},
{
path: [paths[5], paths[6]],
color: 'rgba(0, 180, 0, 1)',
},
],
}],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图叠加图层_静态图贴地图层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>静态图贴地图层</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.98185552901966, 116.31103277206421);
//初始化地图
var map = new TMap.Map("container", {
zoom: 16, //设置地图缩放级别
center: center //设置地图中心点坐标
});
var imageSW = new TMap.LatLng(39.97897813636327, 116.3060975074768);
var imageNE = new TMap.LatLng(39.98506162381882, 116.316397190094);
var imageLatLngBounds = new TMap.LatLngBounds(imageSW, imageNE);
var imageGroundLayer = new TMap.ImageGroundLayer({
bounds: imageLatLngBounds,
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/bg.jpg',
map: map
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_配置矢量底图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>配置矢量底图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.box {
position: absolute;
left: 10px;
top: 10px;
z-index: 10000;
background-color: #fff;
padding: 10px 10px;
}
.box p {
display: inline-block;
padding: 10px 14px;
background: #919aac;
border-radius: 2px;
border: none;
box-sizing: border-box;
font-size: 14px;
color: #fff;
line-height: 14px;
font-family: PingFangSC-Regular;
cursor: pointer;
margin-bottom: 10px;
}
.box p.active {
background: #3cab82;
}
.btnBox {
font-size: 15px;
margin-bottom: 10px;
}
.btnBox label {
margin: 0px 5px;
cursor: pointer;
}
</style>
<body onload="initMap()">
<div class="box">
<div>
<p class="active" data-type="singleChoice">单选模式</p>
<p data-type="multipleChoice">多选模式</p>
</div>
<div class="btnBox">
<label for="base">
<input type="radio" id="base" value="0" name="type" />
道路及地面
</label>
<label for="building3d">
<input type="radio" id="building3d" value="1" name="type" />
3D建筑物
</label>
<label for="building2d">
<input type="radio" id="building2d" value="2" name="type" />
建筑物平面
</label>
<label for="point">
<input type="radio" id="point" value="3" name="type" />
poi文字
</label>
<label for="label">
<input type="radio" id="label" value="4" name="type" />
道路文字
</label>
</div>
</div>
<div id="container"></div>
<script type="text/javascript">
var map;
var mode = 'singleChoice';
var box = document.querySelector('.box');
var modeList = document.querySelectorAll('.box p');
var inputList = document.querySelectorAll('input');
var typeList = ['base', 'building3d', 'building2d', 'point', 'label'];
function cacheContainer() {
// 多选数据
var cacheList = [];
return {
set: function (elementList) {
cacheList = Array.prototype.map.call(elementList, function (element) {
var num = element.value;
return typeList[num];
});
},
get: function () {
return cacheList.map(function (item) {
return item;
});
},
clear: function () {
cacheList = [];
},
};
}
var cache = cacheContainer();
function initMap() {
var center = new TMap.LatLng(39.90989976282665, 116.39701599688226);
// 初始化地图
map = new TMap.Map('container', {
zoom: 17, // 设置地图缩放级别
center: center, // 设置地图中心点坐标
// 矢量底图配置:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#5
baseMap: {
type: 'vector',
features: typeList,
},
});
box.addEventListener('click', function (e) {
var type = '';
var nodeName = e.target.nodeName;
// 切换选择模式
if (nodeName === 'P') {
if (mode !== e.target.dataset.type) reset();
mode = e.target.dataset.type;
e.target.className = 'active';
mode = e.target.dataset.type;
type = mode === 'singleChoice' ? 'radio' : 'checkbox';
// 切换按钮
switchAttribute(type);
}
var checkedList = document.querySelectorAll('input:checked');
var selectList = ['LABEL', 'INPUT'];
if (selectList.indexOf(nodeName) >= 0) {
setMap(checkedList);
}
});
}
function reset() {
// 重置classname
modeList.forEach(function (element) {
element.className = '';
});
// 选项重置
inputList.forEach(function (element) {
element.checked = false;
});
// 重置底图
map.setBaseMap({
type: 'vector',
features: typeList,
});
// 清空多选数据
cache.clear();
}
function switchAttribute(type) {
inputList.forEach(function (element) {
element.type = type;
});
}
function setMap(list) {
var num = list.length > 0 && list[0].value;
var features;
if (mode === 'multipleChoice') {
cache.set(list);
}
features = mode === 'multipleChoice' ? cache.get() : [typeList[num]];
map.setBaseMap({
type: 'vector',
features: features,
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图叠加图层_WMS标准图层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>WMS标准图层</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
// 初始化地图
var map = new TMap.Map('container', {
center: new TMap.LatLng(49.69137687941989, -104.06355092253557), // 地图中心点经纬度。
zoom: 3.5, // 地图缩放级别
});
// WMSLayer文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocLayer#WMSLayer
var WMSLayer = new TMap.WMSLayer({
url: 'https://ahocevar.com/geoserver/wms', // 地图服务地址
map: map, // 展示图层的地图对象
minZoom: 3, // 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3
maxZoom: 20, // 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20
visible: true, // 是否可见,默认为true
zIndex: 1, // 图层绘制顺序
opacity: 0.9, // 图层透明度,默认为1
params: {
// OGC标准的WMS地图服务的GetMap接口的参数
layers: 'topp:states', // 请求的图层名称
},
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/信息窗口_信息窗口偏移设置.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>信息窗口偏移设置</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="init()">
<div id="buttonContainer">
<input type="button" onclick="setOffset('offset')" value="设置偏移">
<input type="button" onclick="setOffset('origin')" value="清除偏移">
</div>
<div id="mapContainer"></div>
<script>
var map = null;
var marker = null;
var infoWindow = null;
var center = null;
function init() {
center = new TMap.LatLng(40.040074, 116.273519);//设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 17
});
//初始marker
marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
})
},
geometries: [{
"id": 'marker',
"styleId": 'marker',
"position": new TMap.LatLng(40.040074, 116.273519),
"properties": {
"title": "marker"
}
}]
});
//设置infoWindow
infoWindowOne = new TMap.InfoWindow({
map: map,
position: center,
zIndex: 10, //信息窗的z-index值
offset: { x: 0, y: 0 }, //无偏移
content: '无偏移的infoWindow'
});
infoWindowTwo = new TMap.InfoWindow({
map: map,
position: center,
zIndex: 10, //信息窗的z-index值
offset: { x: -3, y: -35 }, //向上偏移35像素坐标,向左偏移3像素坐标
content: '左上方偏移的infoWindow'
});
infoWindowTwo.setMap(null);
}
function setOffset(type) {
switch (type) {
case 'offset':
infoWindowTwo.setMap(map);
infoWindowOne.setMap(null);
break;
case 'origin':
infoWindowTwo.setMap(null);
infoWindowOne.setMap(map);
break;
default: ;
}
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_marker轨迹回放-全局模式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>marker轨迹回放-全局模式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.984104, 116.307503);
// 初始化地图
var map = new TMap.Map('container', {
zoom: 15,
center: center,
});
var path = [
new TMap.LatLng(39.98481500648338, 116.30571126937866),
new TMap.LatLng(39.982266575222155, 116.30596876144409),
new TMap.LatLng(39.982348784165886, 116.3111400604248),
new TMap.LatLng(39.978813710266024, 116.3111400604248),
new TMap.LatLng(39.978813710266024, 116.31699800491333),
];
var polylineLayer = new TMap.MultiPolyline({
map, // 绘制到目标地图
// 折线样式定义
styles: {
style_blue: new TMap.PolylineStyle({
color: '#3777FF', // 线填充色
width: 4, // 折线宽度
borderWidth: 2, // 边线宽度
borderColor: '#FFF', // 边线颜色
lineCap: 'round', // 线端头方式
eraseColor: 'rgba(190,188,188,1)',
}),
},
geometries: [
{
id: 'erasePath',
styleId: 'style_blue',
paths: path,
},
],
});
var marker = new TMap.MultiMarker({
map,
styles: {
'car-down': new TMap.MarkerStyle({
width: 40,
height: 40,
anchor: {
x: 20,
y: 20,
},
faceTo: 'map',
rotate: 180,
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/car.png',
}),
start: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png',
}),
end: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png',
}),
},
geometries: [
{
id: 'car',
styleId: 'car-down',
position: new TMap.LatLng(39.98481500648338, 116.30571126937866),
},
{
id: 'start',
styleId: 'start',
position: new TMap.LatLng(39.98481500648338, 116.30571126937866),
},
{
id: 'end',
styleId: 'end',
position: new TMap.LatLng(39.978813710266024, 116.31699800491333),
},
],
});
// 使用marker 移动接口, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
marker.moveAlong(
{
car: {
path,
speed: 250,
},
},
{
autoRotation: true,
}
);
marker.on('moving', (e) => {
var passedLatLngs = e.car && e.car.passedLatLngs;
if (passedLatLngs) {
// 使用路线擦除接口 eraseTo, https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector
polylineLayer.eraseTo(
'erasePath',
passedLatLngs.length - 1,
passedLatLngs[passedLatLngs.length - 1]
);
}
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_普通文本标记.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>普通文本标记</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1">label增加高度</button>
<button class="btn2">label去除高度</button>
</div>
<div id="mapContainer"></div>
<script>
var center = new TMap.LatLng(40.040074, 116.273519); // 设置中心点坐标
var centerHeight = new TMap.LatLng(40.040074, 116.273519, 100); // 带高度的坐标
// 初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 18,
});
// 初始化label
var label = new TMap.MultiLabel({
id: 'label-layer',
map: map,
styles: {
label: new TMap.LabelStyle({
color: '#3777FF', // 颜色属性
size: 20, // 文字大小属性
offset: { x: 0, y: 0 }, // 文字偏移属性单位为像素
angle: 0, // 文字旋转属性
alignment: 'center', // 文字水平对齐属性
verticalAlignment: 'middle', // 文字垂直对齐属性
}),
},
geometries: [
{
id: 'label', // 点图形数据的标志信息
styleId: 'label', // 样式id
position: center, // 标注点位置
content: '腾讯北京总部', // 标注文本
properties: {
// 标注点的属性数据
title: 'label',
},
},
],
});
document.querySelector('button.btn1').onclick = function () {
var data = label.getGeometryById('label');
Object.assign(data, {
position: centerHeight,
});
label.updateGeometries([data]);
map.easeTo(
{
pitch: 80,
},
600
);
};
document.querySelector('button.btn2').onclick = function () {
var data = label.getGeometryById('label');
Object.assign(data, {
position: center,
});
label.updateGeometries([data]);
};
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_2D_3D模式切换.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>2D/3D模式切换</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
input {
position: absolute;
top: 30px;
z-index: 9999;
}
#btn-2d {
left: 20px;
}
#btn-3d {
left: 80px;
}
</style>
<body>
<div id="container"></div>
<input type="button" id="btn-2d" onclick="change2D()" value="切换2D">
<input type="button" id="btn-3d" onclick="change3D()" value="切换3D">
<script type="text/javascript">
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
zoom:12,//设置地图缩放级别
center: center//设置地图中心点坐标
});
function change2D() {
map.setViewMode('2D');
}
function change3D() {
map.setViewMode('3D');
map.setPitch(70);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>折线点击事件</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#info {
display: none;
position: absolute;
left: 30px;
top: 90px;
background: #fff;
border-radius: 5px;
padding: 10px;
z-index: 9999;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="initMap()">
<div id="buttonContainer">
<input type="button" id = "bindClick" value="绑定点击事件">
<input type="button" id = "unbindClick" value="解绑点击事件">
</div>
<div id="info">
</div>
<div id="mapContainer"></div>
</body>
</html>
<script>
var map = null;
var polyline = null;
var infoDom = document.getElementById('info');
var bindBtn = document.getElementById('bindClick');
var unbindBtn = document.getElementById('unbindClick');
function initMap() {
var center = new TMap.LatLng(40.040452,116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 16
});
polyline = new TMap.MultiPolyline({
id: 'polyline-layer',
map: map,
styles: {
'green': new TMap.PolylineStyle({
'color': '#00FF00', //线填充色
'width': 6, //折线宽度
'borderWidth': 3, //边线宽度
'borderColor': '#CCC', //边线颜色
'lineCap': 'butt' //线端头方式
}),
'red': new TMap.PolylineStyle({
'color': '#FF0000', //线填充色
'width': 6,//折线宽度
'borderWidth': 3, //边线宽度
'borderColor': '#CCC', //边线颜色
'lineCap': 'butt' //线端头方式
})
},
geometries: [
{
'id': 'polyline1',
'styleId': 'green',
'paths': [new TMap.LatLng(40.038540, 116.272389), new TMap.LatLng(40.038844, 116.275210), new TMap.LatLng(40.041407, 116.274738)],
'properties': {
'title': 'smoothness'
}
},
{
'id': 'polyline2',
'styleId': 'red',
'paths': [new TMap.LatLng(40.041407, 116.274738), new TMap.LatLng(40.041431, 116.274716), new TMap.LatLng(40.041132, 116.272329)],
'properties': {
'title': 'congestion'
}
}
]
});
}
var eventClick = function (res) {
var res = res && res.geometry;
if (res) {
infoDom.style.display = 'block';
infoDom.innerHTML = '折线ID:' + res.id + '; 样式ID:' + res.styleId + '; 自定义字段:' + res.properties.title;
}
}
//绑定点击事件
bindBtn.addEventListener('click',bindClick,false);
function bindClick() {
polyline.on('click', eventClick);
bindBtn.removeEventListener('click', bindClick, false); //绑定polyline点击事件后,解绑绑定按钮(即只绑定一次polyline)
unbindBtn.addEventListener('click',unbindClick,false); //绑定解绑按钮点击事件
}
//解绑点击事件
function unbindClick() {
infoDom.style.display = 'none';
polyline.off('click', eventClick); //解绑polyline
unbindBtn.removeEventListener('click', unbindClick, false);
bindBtn.addEventListener('click',bindClick,false);
}
</script>
FILE:references/jsapigl/demos/地图操作示例_地图设置缩放焦点.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图设置缩放焦点</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#containerOne {
width: 100%;
height: 45%;
}
#containerTwo {
width: 100%;
height: 45%;
}
p{
margin: 0;
margin-bottom: 20px;
text-align: center;
}
</style>
<body>
<div id="containerOne"></div>
<p>地图一: 根据鼠标/双指位置缩放</p>
<div id="containerTwo"></div>
<p>地图二:根据地图中心点缩放</p>
<script type="text/javascript">
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图一
var mapOne = new TMap.Map("containerOne", {
center,
// 默认缩放,双指中心位置(移动端),鼠标的光标位置(PC端)
mapZoomType: TMap.constants.MAP_ZOOM_TYPE.DEFAULT
});
//初始化地图二
var mapTwo = new TMap.Map("containerTwo", {
center,
// 根据地图中心点缩放(移动端和PC端)
mapZoomType: TMap.constants.MAP_ZOOM_TYPE.CENTER
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置_获取地图中心.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>设置/获取地图中心点</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info{
position: absolute;
background: #FFF;
width:300px;
height: 25px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
#getcenter{
position: absolute;
left:20px;
top:10px;
}
#setcenter{
position: absolute;
left:110px;
top:10px;
}
</style>
<body>
<div id="container"></div>
<div id="info">
<input type="button" id="getcenter" onclick="getCenter()" value="getCenter">
<input type="button" id="setcenter" onclick="setCenter()" value="setCenter">
<p id="center-info">当前地图中心为: 39.984104, 116.307503</p>
</div>
<script>
var centerInfo = document.getElementById("center-info");
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//获取地图中心点事件
function getCenter() {
var mapCenter = map.getCenter(); //获取地图中心点坐标
centerInfo.innerHTML = "当前地图中心为: " + mapCenter.getLat().toFixed(6) + "," + mapCenter.getLng().toFixed(6);
}
//设置地图中心点事件
function setCenter() {
map.setCenter(new TMap.LatLng(39.908802,116.397502));//坐标为天安门
centerInfo.innerHTML = "当前地图中心为: 39.908802,116.397502";
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/模型库_GLTF模型沿线移动.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>GLTF模型沿线移动</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry,model"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.control {
position: absolute;
left: 0px;
top: 0px;
z-index: 9999;
padding: 10px;
}
.control button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #919aac;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
cursor: pointer;
}
.control .beginMove {
background: #3876ff;
}
.control .stopMove {
background: #ff0000;
}
.cc {
position: absolute;
color: #000000;
bottom: 70px;
left: 10px;
z-index: 9999;
font-size: 12px;
}
</style>
<body>
<div class="control">
<button class="beginMove" disabled="disabled">开始移动</button>
<button class="stopMove">终止移动</button>
<button class="pauseMove">暂停移动</button>
<button class="resumeMove">恢复移动</button>
</div>
<div class="cc">
car 3D models by thmacr /<a
target="_blank"
href="https://creativecommons.org/licenses/by/2.0/"
>CC BY
</a>
</div>
<div id="container"></div>
<script>
var map;
var center = new TMap.LatLng(39.98303045328632, 116.31203627649461); // 设置中心点坐标
var roation;
var path = [
// 路径
{
lat: 39.984608,
lng: 116.313765,
height: 0,
},
{
lat: 39.985155,
lng: 116.313737,
height: 0,
},
{
lat: 39.985211,
lng: 116.315639,
height: 0,
},
{
lat: 39.985241,
lng: 116.31618300000001,
height: 0,
},
{
lat: 39.985009000000005,
lng: 116.31623300000001,
height: 0,
},
{
lat: 39.98476000000001,
lng: 116.31626500000002,
height: 0,
},
{
lat: 39.98424500000001,
lng: 116.31638000000001,
height: 0,
},
{
lat: 39.98257900000001,
lng: 116.31652600000001,
height: 0,
},
{
lat: 39.98261000000001,
lng: 116.31593500000001,
height: 0,
},
{
lat: 39.98257800000001,
lng: 116.31516200000002,
height: 0,
},
{
lat: 39.98254200000001,
lng: 116.31358200000001,
height: 0,
},
{
lat: 39.98249300000001,
lng: 116.31252200000002,
height: 0,
},
{
lat: 39.982441000000016,
lng: 116.31104500000002,
height: 0,
},
{
lat: 39.982503000000015,
lng: 116.30931600000002,
height: 0,
},
{
lat: 39.982503000000015,
lng: 116.30871900000002,
height: 0,
},
{
lat: 39.98248700000001,
lng: 116.30821500000002,
height: 0,
},
{
lat: 39.98240100000001,
lng: 116.30811800000002,
height: 0,
},
{
lat: 39.98022500000001,
lng: 116.30821900000002,
height: 0,
},
{
lat: 39.98012100000001,
lng: 116.30821300000002,
height: 0,
},
{
lat: 39.98012400000001,
lng: 116.30815100000002,
height: 0,
},
];
var MoveButton = document.querySelector('.beginMove'); // 开始移动按钮
var stopButton = document.querySelector('.stopMove'); // 结束移动按钮
var pauseButton = document.querySelector('.pauseMove'); // 暂停移动按钮
var resumeButton = document.querySelector('.resumeMove'); // 恢复移动按钮
var startPosition = new TMap.LatLng(39.984607811550845, 116.31375617987158); // 路线规划起点
var endPosition = new TMap.LatLng(39.980242331179504, 116.30780690393772); // 路线规划终点
// 初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 17,
pitch: 0,
});
new TMap.MultiMarker({
// 创造MultiMarker显示起终点标记
id: 'marker-layer',
map: map,
styles: {
start: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png',
}),
end: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png',
}),
},
geometries: [
{
id: 'start',
styleId: 'start',
position: startPosition,
},
{
id: 'end',
styleId: 'end',
position: endPosition,
},
],
});
// 创建 MultiPolyline显示路径折线
new TMap.MultiPolyline({
id: 'polyline-layer',
map: map,
styles: {
style_blue: new TMap.PolylineStyle({
color: '#3777FF',
width: 8,
borderWidth: 5,
borderColor: '#FFF',
lineCap: 'round',
}),
},
geometries: [
{
id: 'pl_1',
styleId: 'style_blue',
paths: path,
},
],
});
// }
// 创建模型
// GLTFModel文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glModel
var model = new TMap.model.GLTFModel({
url: 'https://mapapi.qq.com/web/jsapi/jsapi-gl/assets/car.gltf',
map: map,
id: 'model',
position: startPosition, // 模型初始位置
rotation: [0, 180, 0], // 模型XYZ三轴上的旋转角度
scale: 10, // 模型在XYZ三轴上的缩放比例
});
// // model资源加载完成回调
model.on('loaded', () => {
console.log('模型加载成功');
// 启用模型沿线移动演示
MoveButton.disabled = false;
});
model.on('moving', function (e) {
if (!e.passedPath) return;
// geometry文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocGeometry
roation = TMap.geometry.computeHeading(
// 计算两点之间的航向
e.passedPath[e.passedPath.length - 2].position,
e.passedPath[e.passedPath.length - 1].position
);
center = TMap.geometry.computeDestination(
// 根据起点、朝向和距离计算终点
e.passedPath[e.passedPath.length - 1].position,
roation,
60
);
map.easeTo(
// 平滑过渡到指定状态
{
center: center,
rotation: e.rotation[1] + 180,
zoom: 20,
pitch: 70,
},
{
duration: 300,
}
);
});
function beginMove() {
// 模型开始移动
var paths = path.map((item) => {
return {
position: new TMap.LatLng(item.lat, item.lng),
};
});
model.moveAlong({
// 移动过程中每个节点的坐标
path: paths,
duration: 15000, // 完成移动所需的时间 单位毫秒
degreeToNorth: 180, // 把模型正方向旋转至正北方向所需的角度 默认为0
});
}
function stopMove() {
// 结束模型移动
model.stopMove();
}
function pauseMove() {
// 暂停移动
model.pauseMove();
}
function resumeMove() {
// 恢复移动
model.resumeMove();
}
MoveButton.addEventListener('click', beginMove);
stopButton.addEventListener('click', stopMove);
pauseButton.addEventListener('click', pauseMove);
resumeButton.addEventListener('click', resumeMove);
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_普通Marker.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>普通Marker</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1">marker增加高度</button>
<button class="btn2">marker去除高度</button>
</div>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.98210863924864, 116.31310899739151);
var centerHeight = new TMap.LatLng(39.98210863924864, 116.31310899739151, 134);
// 初始化地图
var map = new TMap.Map('container', {
zoom: 17, // 设置地图缩放
center: new TMap.LatLng(39.98210863924864, 116.31310899739151), // 设置地图中心点坐标,
pitch: 0, // 俯仰度
rotation: 0, // 旋转角度
});
// MultiMarker文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
var marker = new TMap.MultiMarker({
map: map,
styles: {
// 点标记样式
marker: new TMap.MarkerStyle({
width: 20, // 样式宽
height: 30, // 样式高
anchor: { x: 10, y: 30 }, // 描点位置
}),
},
geometries: [
// 点标记数据数组
{
// 标记位置(纬度,经度,高度)
position: center,
id: 'marker',
},
],
});
document.querySelector('button.btn1').onclick = function () {
var data = marker.getGeometryById('marker');
Object.assign(data, {
position: centerHeight,
});
marker.updateGeometries([data]);
map.easeTo(
{
pitch: 80,
},
{
duration: 600,
}
);
};
document.querySelector('button.btn2').onclick = function () {
var data = marker.getGeometryById('marker');
Object.assign(data, {
position: center,
});
marker.updateGeometries([data]);
};
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_创建地图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>创建地图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
rotation: 20,//设置地图旋转角度
pitch:30, //设置俯仰角度(0~45)
zoom:12,//设置地图缩放级别
center: center//设置地图中心点坐标
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_行政区划边界.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>行政区划边界</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<p><input id='adcode' type="text" placeholder='请输入行政区adcode(默认为110105)' size='30'><input id="findBorder" type="button" class="btn" value="找边界" onclick="findBorder()" /></p>
<p id="errorMsg"></p>
</div>
</body>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 8,
center: new TMap.LatLng(40, 116),
});
var district = new TMap.service.District({
// 新建一个行政区划类
polygon: 1, // 返回行政区划边界的类型
});
var polygons = new TMap.MultiPolygon({
map: map,
geometries: [],
});
function findBorder() {
polygons.remove(polygons.getGeometries().map((item) => item.id));
var keyword = document.getElementById('adcode').value;
district
.search({ keyword: keyword || '110105' })
.then((result) => {
// 搜索行政区划信息
result.result.forEach((level) => {
level.forEach((place) => {
var bounds = [];
var newGeometries = place.polygon.map((polygon, index) => {
bounds.push(fitBounds(polygon)); // 计算能完整呈现行政区边界的最小矩形范围
return {
id: `place.id_index`,
paths: polygon, // 将得到的行政区划边界用多边形标注在地图上
};
});
bounds = bounds.reduce((a, b) => {
return fitBounds([
a.getNorthEast(),
a.getSouthWest(),
b.getNorthEast(),
b.getSouthWest(),
]);
}); // 若一行政区有多个多边形边界,应计算能包含所有多边形边界的范围。
polygons.updateGeometries(newGeometries);
map.fitBounds(bounds);
});
});
})
.catch((error) => {
document.getElementById(
'errorMsg'
).innerText = `错误:error.status, error.message`;
});
}
function fitBounds(latLngList) {
// 由多边形顶点坐标数组计算能完整呈现该多边形的最小矩形范围
if (latLngList.length === 0) {
return null;
}
var boundsN = latLngList[0].getLat();
var boundsS = boundsN;
var boundsW = latLngList[0].getLng();
var boundsE = boundsW;
latLngList.forEach((point) => {
point.getLat() > boundsN && (boundsN = point.getLat());
point.getLat() < boundsS && (boundsS = point.getLat());
point.getLng() > boundsE && (boundsE = point.getLng());
point.getLng() < boundsW && (boundsW = point.getLng());
});
return new TMap.LatLngBounds(
new TMap.LatLng(boundsS, boundsW),
new TMap.LatLng(boundsN, boundsE)
);
}
findBorder();
</script>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_添加与移除marker.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>添加与移除marker</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#removeMarker{
position: absolute;
left: 150px;
top: 30px;
z-index: 9999;
}
#createMarker{
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
</style>
<body>
<div id="container"></div>
<input type="button" id="createMarker" onclick="createMarker()" value="createMarker">
<input type="button" id="removeMarker" onclick="removeMarker()" value="removeMarker">
<script>
var marker = null;
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//创建marker事件
function createMarker() {
if (!marker) {
marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
})
},
geometries: [{
"id": 'demo',
"styleId": 'marker',
"position": new TMap.LatLng(39.984104, 116.307503),
"properties": {
"title": "marker"
}
}]
});
}
}
//移除marker事件
function removeMarker() {
if (marker) {
marker.setMap(null);
marker = null;
}
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置地图边界范围.html
<!DOCTYPE html>
<html>
<head>
<title>设置地图边界范围</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
* {
padding: 0px;
margin: 0px;
}
#mapContainer {
position: relative;
width: 100vw;
height: 100vh;
}
input{
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
</style>
</head>
<body>
<div id='mapContainer'></div>
<input id="btn" type="button" value="点击设置boundary">
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<script>
window.onload = init;
var map, polygon;
function init() {
let sw = new TMap.LatLng(39.744154,116.124115);
let ne = new TMap.LatLng(40.104336,116.691284);
let boundary = new TMap.LatLngBounds(sw, ne);
map = new TMap.Map('mapContainer', {
zoom: 10,
center: new TMap.LatLng(39.919216,116.328735)
});
polygon = new TMap.MultiPolygon({
map,
geometries: [{
paths: [
boundary.getSouthWest(),
boundary.getSouthEast(),
boundary.getNorthEast(),
boundary.getNorthWest()
]
}]
});
let btn = document.getElementById('btn');
btn.addEventListener('click', () => {
map.setBoundary(boundary);
},false);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_地图平移.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图平移事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info{
position: absolute;
bottom:50px;
left:50%;
transform: translateX(-50%);
background: #FFF;
border-radius: 3px;
padding: 10px;
width: 440px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">
<h4 id="txt"></h4>
<p>当前中心点:<span id="position"></span></p>
</div>
<script>
function initMap() {
var position = document.getElementById("position");
var txt = document.getElementById("txt");
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
location.innerHTML = map.getCenter().toString();
//监听地图开始平移
map.on("panstart", function () {
txt.innerHTML = "地图开始平移"
})
//监听地图平移
map.on("pan",function(){
txt.innerHTML = "地图正在平移";
position.innerHTML = map.getCenter().toString();//获取地图中心点
})
//监听地图平移结束
map.on("panend",function(){
txt.innerHTML = "地图结束平移";
})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/几何计算库_判断点与多边形的关系.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算点与多边形的关系</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 17,
baseMap: {
type: 'vector',
features: ['base', 'building3d', 'label'],
}
});
var path = [
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
var position1 = new TMap.LatLng(40.04202067344373, 116.2706967910799);
var position2 = new TMap.LatLng(40.040443786954235, 116.2736683713456);
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map,
geometries: [
{
id: 'polygon', //多边形图形数据的标志信息
paths: path, //多边形的位置信息
}
]
});
var getLabelText = function(pos) {
return TMap.geometry.isPointInPolygon(pos, path) ? '在多边形内部' : '在多边形外部'
};
var label = new TMap.MultiLabel({
id: 'label-layer', //图层id
map: map,
styles: {
building: new TMap.LabelStyle({
size: 20, //文字大小属性
alignment: 'center', //文字水平对齐属性
verticalAlignment: 'middle' //文字垂直对齐属性
})
},
geometries: [
{
styleId: 'building',
position: position1,
content: getLabelText(position1),
},
{
styleId: 'building',
position: position2,
content: getLabelText(position2),
},
]
});
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
marker: new TMap.MarkerStyle({
anchor: { x: 20, y: 60 },
})
},
geometries: [
{
styleId: 'marker',
position: position1,
},
{
styleId: 'marker',
position: position2,
},
],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_点标记应用:周边检索.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>点标记应用:周边检索</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 90%;
}
#searchInput {
padding: 3px 4px;
}
#searchPart {
position: absolute;
top: 10px;
left: 10px;
z-index: 1001;
}
#information {
margin: 5px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="searchPart">
<input type="text" id="searchInput" placeholder="请输入">
<button id="searchBtn">搜索</button>
</div>
<div id="information">
*使用时需要把代码111行中的key修改为您申请了搜索服务的key。<br>
在左上角的输入框中输入搜索内容(如公园、超市),点击搜索按钮后,在圆形范围内搜索符合条件的前10个结果,
地图会以搜索结果为基准,自适应地图的边界,搜索结果会以marker形式呈现,点击marker会提示搜索结果的信息。
</div>
<script type="text/javascript">
var map, marker, infoWindow;
function initMap() {
// 设置地图中心点坐标
const center = new TMap.LatLng(39.984104, 116.307503);
map = new TMap.Map("container", {
zoom: 15, // 设置地图缩放级别
center // 设置地图中心
});
// 创建搜索范围圆
const circle = new TMap.MultiCircle({
id: 'circle',
map,
geometries: [{
center: center, // 设置圆的中心
radius: 1000 //设置圆的半径
}]
});
// 创建marker图层
marker = new TMap.MultiMarker({
id: 'marker',
map,
geometries: []
});
// 创建信息窗口
infoWindow = new TMap.InfoWindow({
map,
position: map.getCenter(),
offset: { // 设置信息弹窗的偏移量,否则会和marker重合
x: 0,
y: -48
}
}).close();
// 添加搜索按钮点击事件
const searchBtn = document.getElementById('searchBtn');
searchBtn.addEventListener('click', searchClick, false);
marker.on('click', evt => {
let content = `
<div>
<p>名称: evt.geometry.properties.title</p>
<p>地址: evt.geometry.properties.address</p>
<p>电话: evt.geometry.properties.tel</p>
</div>
`
infoWindow.open();
infoWindow.setPosition(evt.geometry.position); // 设置信息窗口的坐标
infoWindow.setContent(content); // 设置信息窗口的内容
});
}
function searchClick() {
let searchInput = document.getElementById('searchInput').value; // 获取搜索内容
let url = [
'https://apis.map.qq.com/ws/place/v1/search',
'?boundary=nearby(39.984104,116.307503,1000,0)',
`&keyword=searchInput`,
'&page_size=10&page_index=1&orderby=_distance',
'&output=jsonp&callback=cb',
'&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'
].join('');
jsonp_request(url);
infoWindow.close(); // 关闭信息窗口
}
// 使用jsonp调用接口
function jsonp_request(url) {
let script = document.createElement('script');
script.src = url;
document.body.appendChild(script);
}
// jsonp运行的回调函数
function cb(ret) {
let newBounds = new TMap.LatLngBounds();
let markerArr = [];
if(ret && ret.status===0 && ret.data.length>0) {
// 将搜索结果保存进数组中
ret.data.forEach( (item, index) => {
let position = new TMap.LatLng(item.location.lat, item.location.lng);
markerArr.push({
position: position,
properties: {
title: item.title,
address: item.address,
tel: item.tel!==' ' ? item.tel : '暂无'
}
});
// 寻找搜索结果的边界
newBounds.extend(position);
});
// 更新marker层,显示标记
marker.setGeometries(markerArr);
// 地图自适应边界
map.fitBounds(newBounds, {
padding: 100 // 边界与内容之间留的间距
});
}
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/应用工具_开关编辑器键盘事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>开关编辑器键盘事件</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
position: relative;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
display: flex;
flex-direction: column;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div class="btnContainer">
<button id="btn1" onclick="enableKeyBoard('Delete')">关闭默认的Delete事件</button>
<button id="btn2" onclick="enableKeyBoard('Ctrl')">关闭默认的Ctrl事件</button>
<button id="btn3" onclick="enableKeyBoard('Esc')">关闭默认的Esc事件</button>
</div>
<div>
单选:鼠标左键点击图形<br />
多选:按下ctrl键后点击多个图形, 禁用后将无法通过Ctrl键多选<br />
删除:选中图形后按下delete键可删除图形,禁用后将无法通过delete键删除<br />
中断:按下esc键可中断当前操作,点选的图形将取消选中,编辑过程将中断,禁用后将无法通过esc键中断
</div>
<script type="text/javascript">
var map, editor;
const btn1 = document.getElementById('btn1')
const btn2 = document.getElementById('btn2')
const btn3 = document.getElementById('btn3')
function enableKeyBoard(target) {
// 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor
if (target === 'Delete' || target === 'Ctrl' || target === 'Esc') {
let flag = !editor[`getKeyboardtargetEnable`]();
editor[`setKeyboardtargetEnable`](flag);
// 更新按钮文字
const btnMap = {
'Delete': btn1,
'Ctrl': btn2,
'Esc': btn3
};
btnMap[target].innerText = flag ?
`关闭默认的target事件` :
`开启默认的target事件`;
}
}
function initMap() {
// 初始化地图
map = new TMap.Map("container", {
zoom: 16, // 设置地图缩放级别
center: new TMap.LatLng(40.04162653200161, 116.27402993329179) // 设置地图中心点坐标
});
const polygonGeometries = [
{
"paths": [
new TMap.LatLng(40.041516, 116.271655),
new TMap.LatLng(40.044479, 116.271063),
new TMap.LatLng(40.045154, 116.275173),
new TMap.LatLng(40.045127, 116.275351),
new TMap.LatLng(40.045061, 116.275474),
new TMap.LatLng(40.044007, 116.276271),
new TMap.LatLng(40.043876, 116.276264),
new TMap.LatLng(40.043774, 116.276204),
new TMap.LatLng(40.043697, 116.276093),
new TMap.LatLng(40.043411, 116.274514),
new TMap.LatLng(40.043373, 116.274436),
new TMap.LatLng(40.043313, 116.274408),
new TMap.LatLng(40.042697, 116.274427),
new TMap.LatLng(40.042122, 116.27462),
new TMap.LatLng(40.04204, 116.274607),
new TMap.LatLng(40.042001, 116.274544),
new TMap.LatLng(40.041516, 116.271655),
],
},
{
"paths": [
new TMap.LatLng(40.043774, 116.276204),
new TMap.LatLng(40.043824, 116.276332),
new TMap.LatLng(40.04382, 116.27648),
new TMap.LatLng(40.043489, 116.277156),
new TMap.LatLng(40.043233, 116.277385),
new TMap.LatLng(40.042953, 116.277482),
new TMap.LatLng(40.04255, 116.277556),
new TMap.LatLng(40.042484, 116.277524),
new TMap.LatLng(40.042454, 116.277449),
new TMap.LatLng(40.042051, 116.274762),
new TMap.LatLng(40.042063, 116.274677),
new TMap.LatLng(40.042122, 116.27462),
new TMap.LatLng(40.042697, 116.274427),
new TMap.LatLng(40.043313, 116.274408),
new TMap.LatLng(40.043373, 116.274436),
new TMap.LatLng(40.043411, 116.274514),
new TMap.LatLng(40.043697, 116.276093),
new TMap.LatLng(40.043774, 116.276204),
],
},
{
"paths": [
new TMap.LatLng(40.041904, 116.274781),
new TMap.LatLng(40.041939, 116.274843),
new TMap.LatLng(40.04233, 116.27752),
new TMap.LatLng(40.04231, 116.277595),
new TMap.LatLng(40.042262, 116.277644),
new TMap.LatLng(40.041085, 116.277896),
new TMap.LatLng(40.041028, 116.277879),
new TMap.LatLng(40.040999, 116.277816),
new TMap.LatLng(40.040641, 116.275151),
new TMap.LatLng(40.040655, 116.275076),
new TMap.LatLng(40.040701, 116.275027),
new TMap.LatLng(40.041847, 116.274757),
new TMap.LatLng(40.041904, 116.274781),
],
},
{
"paths": [
new TMap.LatLng(40.041231, 116.271712),
new TMap.LatLng(40.0413, 116.271761),
new TMap.LatLng(40.041347, 116.271869),
new TMap.LatLng(40.041821, 116.27429),
new TMap.LatLng(40.041793, 116.274441),
new TMap.LatLng(40.041701, 116.274535),
new TMap.LatLng(40.040443, 116.274853),
new TMap.LatLng(40.040906, 116.27805),
new TMap.LatLng(40.039506, 116.278362),
new TMap.LatLng(40.03863, 116.272593),
new TMap.LatLng(40.03869, 116.27238),
new TMap.LatLng(40.038957, 116.272167),
new TMap.LatLng(40.041231, 116.271712),
],
}]
// 初始化几何图形及编辑器
editor = new TMap.tools.GeometryEditor({
map, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: new TMap.MultiPolygon({
map,
styles: {
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
},
geometries: polygonGeometries
}),
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
snappable: true // 开启吸附
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_粒子路况.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>粒子路况</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
function initMap() {
var center = new TMap.LatLng(39.960957089008026, 116.44548038611174);
// 初始化地图 MapOptions文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#2
new TMap.Map('container', {
zoom: 11, // 设置地图缩放级别
center: center, // 设置地图中心点坐标
mapStyleId: 'style1', // 个性化样式
baseMap: [
{ type: 'vector' }, // 设置矢量底图
{
// traffic底图文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#7
type: 'traffic',
features: ['base', 'flow'], // 路况图要素类型
opacity: 0.2, // 路况线网图的透明度
},
],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/信息窗口_点击标记弹出信息窗口.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>点击标记弹出信息窗口</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 24,
"height": 35,
"anchor": { x: 12, y: 35 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
})
},
geometries: [{
"id": 'demo1',
"styleId": 'marker',
"position": new TMap.LatLng(39.984104, 116.307503),
"properties": {
"title": "marker"
}
}, {
"id": 'demo2',
"styleId": 'marker',
"position": new TMap.LatLng(39.974104, 116.347503),
"properties": {
"title": "marker"
},
}]
});
//初始化infoWindow
var infoWindow = new TMap.InfoWindow({
map: map,
position: new TMap.LatLng(39.984104, 116.307503),
offset: { x: 0, y: -32 } //设置信息窗相对position偏移像素
});
infoWindow.close();//初始关闭信息窗关闭
//监听标注点击事件
marker.on("click", function (evt) {
//设置infoWindow
infoWindow.open(); //打开信息窗
infoWindow.setPosition(evt.geometry.position);//设置信息窗位置
infoWindow.setContent(evt.geometry.position.toString());//设置信息窗内容
})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置鼠标样式.html
<!DOCTYPE html>
<html>
<head>
<title>自定义鼠标样式</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.input-card {
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border-radius: .25rem;
border-width: 0;
border-radius: 0.4rem;
box-shadow: 0 2px 6px 0 rgba(114, 124, 245, .5);
position: fixed;
bottom: 1rem;
right: 1rem;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
padding: 0.75rem 1.25rem;
width: 110px;
z-index: 3000;
}
</style>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
</head>
<body>
<div id="container" class="map"></div>
<ul id="cursorList" class="input-card">
<li> 选择鼠标样式 </li>
<li>
<input name="cursor" value="default" type="radio" onclick="switchCursor(this)">
<span>default</span>
</li>
<li>
<input name="cursor" value="pointer" type="radio" onclick="switchCursor(this)" checked>
<span>pointer</span>
</li>
<li>
<input name="cursor" value="move" type="radio" onclick="switchCursor(this)">
<span>move</span>
</li>
<li>
<input name="cursor" value="progress" type="radio" onclick="switchCursor(this)">
<span>progress</span>
</li>
<li>
<input name="cursor" value="url('https://industry.map.qq.com/cloud/webDemoCente/gl/source/custom_cursor.png'),auto" type="radio" onclick="switchCursor(this)">
<span>自定义样式</span>
</li>
</ul>
<script type="text/javascript">
//初始化地图对象,加载地图
map = new TMap.Map('container', {
zoom: 18,
center: new TMap.LatLng(39.98527631559586, 116.37631948391004),
});
map.setCursorStyle("pointer");
//自定义鼠标样式图标
function switchCursor(target) {
var value = target.value;
map.setCursorStyle(value);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置_获取地图显示范围.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>设置/获取地图显示范围</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info{
height: 50px;
width: 600px;
padding: 10px;
background: #FFF;
position: absolute;
top:20px;
left: 10px;
border-radius: 5px;
font-size: 14px;
}
p{
margin: 0;
margin-bottom: 10px;
}
#fit-bounds{
position: absolute;
right: 40px;
top: 5px;
padding: 5px;
z-index: 9999;
}
</style>
<body>
<div id="container"></div>
<div id="info">
<p>当前地图中心点:<span id="position">39.984104, 116.307503</span></p>
<p><span id="bounds"></span></p>
<!-- 地图图层上的button需要设置z-index>1000 -->
<input type="button" id="fit-bounds" onclick="fitBounds()" value="fitBounds">
</div>
<script>
var position = document.getElementById("position");
var bounds = document.getElementById("bounds");
var ne = new TMap.LatLng(39.96693, 116.49369);//东北角坐标
var sw = new TMap.LatLng(39.86000, 116.28666);//西南角坐标
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//监听可视范围改变事件
map.on("bounds_changed",function(){
var mapCenter = map.getCenter(); //获取地图中心点坐标
position.innerHTML = mapCenter.getLat().toFixed(6) + "," + mapCenter.getLng().toFixed(6);
var mapBounds = map.getBounds(); //获取当前地图的视野范围
if(mapBounds){
var nothEast ="当前可视区域范围:东北/右上:[" + mapBounds.getNorthEast().getLat().toFixed(6) + "," + mapBounds.getNorthEast().getLng().toFixed(6)+"] ;";
var southWest ="西南/左下:[" + mapBounds.getSouthWest().getLat().toFixed(6) + "," + mapBounds.getSouthWest().getLng().toFixed(6)+"]";
bounds.innerHTML = nothEast + southWest;
}
})
function fitBounds(){
var latLngBounds = new TMap.LatLngBounds(sw, ne)
map.fitBounds(latLngBounds); //根据指定的范围调整地图视野
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_地图导出图片.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>地图导出为图片</title>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100vh;
}
button {
position: absolute;
left: 10px;
top: 10px;
z-index: 9999;
padding: 10px 14px;
box-sizing: border-box;
border: none;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
transition: .3s;
}
.disabled {
background-color: #979595;
}
.enable {
background-color: #3876ff;
}
</style>
</head>
<body>
<button onclick="screenshot()" disabled="disabled" id="export" class="disabled">
点击导出地图截图
</button>
<div id="container"></div>
<script src="https://mapapi.qq.com/web/jsapi/jsapi-gl/js/dom-to-image.min.js"></script>
<!-- dom-to-image 官方地址:https://github.com/tsayen/dom-to-image -->
<script src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<script>
var map = new TMap.Map('container', {
rotation: 20, // 设置地图旋转角度
pitch: 30, // 设置俯仰角度(0~45)
zoom: 17, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503), // 设置地图中心点坐标
baseMap: [
{ type: 'vector' }, // 设置矢量底图
],
renderOptions: {
// renderOptions文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap#10
preserveDrawingBuffer: true, // 保留地图的渲染缓冲 默认false
},
});
function screenshot() {
// 截图操作
var node = document.getElementById('container');
domtoimage
.toPng(node)
.then(function (dataUrl) {
// 导出图片
var link = document.createElement('a');
link.download = 'image.png';
link.href = dataUrl;
link.click();
})
.catch(function (error) {
console.error('domtoimage 出现问题! ', error);
});
}
map.on('tilesloaded', function () {
// 当地图容器中可见的瓦片加载完后会触发此事件。
var button = document.querySelector('#export');
button.disabled = false;
button.className = 'enable';
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线应用-路线规划.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>折线应用: 路线规划</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
//注:本例仅为说明流程,不保证严谨
var map;
function initMap(){
//初始化地图
map = new TMap.Map('mapContainer', {
center: new TMap.LatLng(39.980619,116.321277),//地图显示中心点
zoom:14 //缩放级别
});
//WebServiceAPI请求URL(驾车路线规划默认会参考实时路况进行计算)
var url="https://apis.map.qq.com/ws/direction/v1/driving/"; //请求路径
url+="?from=39.984039,116.307630"; //起点坐标
url+="&to=39.977263,116.337063"; //终点坐标
url+="&output=jsonp&callback=cb" ; //指定JSONP回调函数名,本例为cb
url+="&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"; //开发key,可在控制台自助创建
//发起JSONP请求,获取路线规划结果
jsonp_request(url);
}
//浏览器调用WebServiceAPI需要通过Jsonp的方式,此处定义了发送JOSNP请求的函数
function jsonp_request(url){
var script=document.createElement('script');
script.src=url;
document.body.appendChild(script);
}
//定义请求回调函数,在此拿到计算得到的路线,并进行绘制
function cb(ret){
var coords = ret.result.routes[0].polyline, pl = [];
//坐标解压(返回的点串坐标,通过前向差分进行压缩)
var kr = 1000000;
for (var i = 2; i < coords.length; i++) {
coords[i] = Number(coords[i - 2]) + Number(coords[i]) / kr;
}
//将解压后的坐标放入点串数组pl中
for (var i = 0; i < coords.length; i += 2) {
pl.push(new TMap.LatLng(coords[i], coords[i+1]));
}
display_polyline(pl)//显示路线
//标记起终点marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"start": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png'
}),
"end": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png'
})
},
geometries: [{
"id": 'start',
"styleId": 'start',
"position": new TMap.LatLng(39.984039,116.307630307503)
}, {
"id": 'end',
"styleId": 'end',
"position": new TMap.LatLng(39.977263,116.337063)
}]
});
}
function display_polyline(pl){
//创建 MultiPolyline显示折线
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer', //图层唯一标识
map: map,//绘制到目标地图
//折线样式定义
styles: {
'style_blue': new TMap.PolylineStyle({
'color': '#3777FF', //线填充色
'width': 8, //折线宽度
'borderWidth': 5, //边线宽度
'borderColor': '#FFF', //边线颜色
'lineCap': 'round', //线端头方式
})
},
//折线数据定义
geometries: [
{
'id': 'pl_1',//折线唯一标识,删除时使用
'styleId': 'style_blue',//绑定样式名
'paths': pl
}
]
});
}
</script>
FILE:references/jsapigl/demos/信息窗口_简单信息窗口.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>简单信息窗口</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始化infoWindow
var infoWindow = new TMap.InfoWindow({
map: map,
position: center, //设置信息框位置
content: 'Hello World!' //设置信息框内容
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/几何计算库_判断点是否在多边形边上.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>判断点是否在多边形的边上</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 17,
baseMap: {
type: 'vector',
features: ['base', 'building3d', 'label'],
}
});
var path = [
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
var position1 = new TMap.LatLng(40.04202067344373, 116.2706967910799);
var position2 = new TMap.LatLng(40.040443786954235, 116.2736683713456);
var position3 = new TMap.LatLng(40.03957540757015, 116.27386486303988);
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map,
geometries: [
{
id: 'polygon', //多边形图形数据的标志信息
paths: path, //多边形的位置信息
}
]
});
var getLabelText = function(pos) {
// isPointOnSegment(point: LatLng, segment: LatLng[], tolerance: Number) tolerance单位为米
return TMap.geometry.isPointOnPolygon(pos, path, 10) ? '在多边形边上' : '不在多边形边上'
};
var label = new TMap.MultiMarker({
id: 'label-layer', //图层id
map: map,
styles: {
building: new TMap.MarkerStyle({
size: 20, //文字大小属性
alignment: 'center', //文字水平对齐属性
verticalAlignment: 'bottom', //文字垂直对齐属性
offset:{
x: 0,
y: 35
}
})
},
geometries: [
{
styleId: 'building',
position: position1,
content: getLabelText(position1),
},
{
styleId: 'building',
position: position2,
content: getLabelText(position2),
},
{
styleId: 'building',
position: position3,
content: getLabelText(position3),
},
]
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_关键字搜索.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>关键字搜索</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<p>输入关键字,按下搜索,将在屏幕展示的地图范围中搜索相关地点。</p>
<input id='keyword' type="text" value='地铁站' ><input id="search" type="button" class="btn" value="搜索" onclick="searchByKeyword()" />
</div>
</body>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 14,
center: new TMap.LatLng(40.0402718, 116.2735831),
});
var search = new TMap.service.Search({ pageSize: 10 }); // 新建一个地点搜索类
var markers = new TMap.MultiMarker({
map: map,
geometries: [],
});
var infoWindowList = Array(10);
function searchByKeyword() {
infoWindowList.forEach((infoWindow) => {
infoWindow.close();
});
infoWindowList.length = 0;
markers.setGeometries([]);
// 在地图显示范围内以给定的关键字搜索地点
search
.searchRectangle({
keyword: document.getElementById('keyword').value,
bounds: map.getBounds(),
})
.then((result) => {
result.data.forEach((item, index) => {
var geometries = markers.getGeometries();
var infoWindow = new TMap.InfoWindow({
map: map,
position: item.location,
content: `<h3>item.title</h3><p>地址:item.address</p><p>电话:item.tel</p>`,
offset: { x: 0, y: -50 },
}); // 新增信息窗体显示地标的名称与地址、电话等信息
infoWindow.close();
infoWindowList[index] = infoWindow;
geometries.push({
id: String(index), // 点标注数据数组
position: item.location,
});
markers.updateGeometries(geometries); // 绘制地点标注
markers.on('click', (e) => {
infoWindowList[Number(e.geometry.id)].open();
}); // 点击标注显示信息窗体
});
});
}
</script>
</html>
FILE:references/jsapigl/demos/应用工具_编辑几何图形.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>编辑几何图形</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
position: relative;
}
#toolControl {
position: absolute;
top: 10px;
left: 0px;
right: 0px;
margin: auto;
width: 126px;
z-index: 1001;
}
.toolItem {
width: 30px;
height: 30px;
float: left;
margin: 1px;
padding: 4px;
border-radius: 3px;
background-size: 30px 30px;
background-position: 4px 4px;
background-repeat: no-repeat;
box-shadow: 0 1px 2px 0 #E4E7EF;
background-color: #ffffff;
border: 1px solid #ffffff;
}
.toolItem:hover {
border-color: #789CFF;
}
.active {
border-color: #D5DFF2;
background-color: #D5DFF2;
}
#delete {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/delete.png');
}
#split {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/split.png');
}
#union {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/union.png');
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="toolControl">
<div class="toolItem" id="delete" onclick="editor.delete();" title="删除"></div>
<div class="toolItem" id="split" onclick="editor.split();" title="拆分"></div>
<div class="toolItem" id="union" onclick="editor.union();" title="合并"></div>
</div>
<div>
单选:鼠标左键点击图形<br/>
多选:按下ctrl键后点击多个图形<br/>
删除:选中图形后按下delete键或点击删除按钮可删除图形<br/>
编辑:选中图形后出现编辑点,拖动编辑点可移动顶点位置,双击实心编辑点可删除顶点<br/>
拆分:选中单个多边形后可绘制拆分线,拆分线绘制完成后自动进行拆分<br/>
合并:选中多个相邻多边形后可进行合并,飞地形式的多边形不支持合并<br/>
中断:按下esc键可中断当前操作,点选的图形将取消选中,编辑过程将中断
</div>
<script type="text/javascript">
var map, editor;
function initMap() {
// 初始化地图
map = new TMap.Map("container", {
zoom: 17, // 设置地图缩放级别
center: new TMap.LatLng(40.04019000341765, 116.27446815226199) // 设置地图中心点坐标
});
var simplePath = [
new TMap.LatLng(40.04051164600918, 116.27488518619089),
new TMap.LatLng(40.040943635857445, 116.27804611629756),
new TMap.LatLng(40.03951759379146, 116.2783762087081),
new TMap.LatLng(40.03891287066983, 116.2752049655744)
];
var holeyPath = [
[
new TMap.LatLng(40.0415122389407, 116.27144427159294),
new TMap.LatLng(40.03854281391625, 116.27218697924695),
new TMap.LatLng(40.03891287066983, 116.2752049655744),
new TMap.LatLng(40.04191838125587, 116.27460372613496)
],
[
new TMap.LatLng(40.04108804322408, 116.27230486920075),
new TMap.LatLng(40.039472465232336, 116.27267032855252),
new TMap.LatLng(40.03972518377873, 116.2748159283218),
new TMap.LatLng(40.04137685760844, 116.2744386798397)
]
];
// 初始化几何图形及编辑器
editor = new TMap.tools.GeometryEditor({
map, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: new TMap.MultiPolygon({
map,
styles: {
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
},
geometries: [
{
paths: simplePath
},
{
paths: holeyPath
},
]
}),
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
snappable: true // 开启吸附
});
// 监听删除、修改、拆分、合并完成事件
let evtList = ['delete', 'adjust', 'split', 'union'];
evtList.forEach(evtName => {
editor.on(evtName + '_complete', evtResult => {
console.log(evtName, evtResult);
});
});
// 监听拆分失败事件,获取拆分失败原因
editor.on('split_fail', (res) => {
alert(res.message);
});
// 监听合并失败事件,获取合并失败原因
editor.on('union_fail', (res) => {
alert(res.message);
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/个性化地图_切换地图个性化样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>切换地图个性化样式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btn {
position: absolute;
top: 30px;
z-index: 1001;
padding: 6px 8px;
}
.btn-left {
left: 30px;
}
.btn-right {
left: 150px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<input type="button" class="btn btn-left" onclick="changeMapStyle('style2')" value="设置为样式2">
<input type="button" class="btn btn-right" onclick="changeMapStyle('style3')" value="设置为样式3">
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
map = new TMap.Map("container", {
zoom: 12,//设置地图缩放级别
center: center,//设置地图中心点坐标
});
}
// 动态设置个性化样式
function changeMapStyle(styleId) {
map.setMapStyleId(styleId);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/城市漫游_地面漫游.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>地面漫游</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=view"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.info {
width: 300px;
background-color: white;
padding: 10px;
font-size: 14px;
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
</style>
<body>
<div class="info">
<p></p>
<p></p>
<p></p>
<p></p>
</div>
<div id="container"></div>
<script type="text/javascript">
// 初始化地图
var map = new TMap.Map('container', {
center: new TMap.LatLng(40.04374524409231, 116.2865218786128), // 设置中心点
zoom: 17, // 设置地图缩放比例
});
var lookFromList = [
{
position: new TMap.LatLng(40.04374524409231, 116.2865218786128, 50),
rotation: [80, 0, 0], // 地图在水平面上的旋转角度
percentage: 0, // 动画过程中该关键帧的百分比
},
{
position: new TMap.LatLng(40.049407036771356, 116.28522908966829, 50),
rotation: [80, 0, 0],
percentage: 0.25,
},
{
position: new TMap.LatLng(40.04970116881027, 116.28443717763798, 50),
rotation: [80, 0, 90],
percentage: 0.3,
},
{
position: new TMap.LatLng(40.04908656312387, 116.28076901740587, 50),
rotation: [80, 0, 90],
percentage: 0.45,
},
{
position: new TMap.LatLng(40.04876101655684, 116.28052871173372, 50),
rotation: [80, 0, 180],
percentage: 0.5,
},
{
position: new TMap.LatLng(40.04336691175791, 116.28170569825716, 50),
rotation: [80, 0, 180],
percentage: 0.75,
},
{
position: new TMap.LatLng(40.04298529869431, 116.28218813579929, 50),
rotation: [80, 0, -90],
percentage: 0.8,
},
{
position: new TMap.LatLng(40.04347544510598, 116.28571716461806, 50),
rotation: [80, 0, -90],
percentage: 0.95,
},
{
position: new TMap.LatLng(40.04374524409231, 116.2865218786128, 50),
rotation: [80, 0, 0],
percentage: 1,
},
];
var keyFrames = lookFromList.map(function (lookFrom) {
var position = lookFrom.position;
var rotation = lookFrom.rotation;
var percentage = lookFrom.percentage;
// 地图视角附加库文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glMapView
// getMapViewWhenLookFrom指定观察者所在位置及三轴方向上的旋转角度。
// position为观察者位置,需明确高度;rotation为XYZ三轴上的旋转角度,格式为[x, y, z],默认为[0, 0, 0]。
var keyFrame = map.getMapViewWhenLookFrom(position, rotation);
keyFrame.percentage = percentage;
return keyFrame;
});
// map.startAnimation 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap
// 开始动画,通过keyFrames定义关键帧
map.startAnimation(keyFrames, {
duration: 30 * 1000, // 动画周期时长,单位为ms
loop: Infinity, // 动画周期循环次数,若为Infinity则无限循环,默认为1
});
var infoList = document.querySelectorAll('.info p');
map.on('animation_playing', function (event) {
var lat = event.frame.center.lat;
var lng = event.frame.center.lng;
var pitch = event.frame.pitch;
var zoom = event.frame.zoom;
var rotation = event.frame.rotation;
infoList[0].innerText = '当前地图中心点:' + lat.toFixed(6) + ',' + lng.toFixed(6);
infoList[1].innerText = '当前地图俯仰角角度:' + Math.round(pitch);
infoList[2].innerText = '当前地图缩放等级:' + Math.round(zoom);
infoList[3].innerText = '当前地图旋转角度:' + Math.round(rotation);
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_异步加载地图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>异步加载地图</title>
</head>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script>
function initMap() {
var map = new TMap.Map("container", {
pitch: 45,
zoom: 12,
center: new TMap.LatLng(39.984104, 116.307503)
});
}
function loadScript() {
//创建script标签,并设置src属性添加到body中
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&callback=initMap";
document.body.appendChild(script);
}
window.onload = loadScript;
</script>
</body>
</html>
FILE:references/jsapigl/demos/几何计算库_计算路径距离.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>计算路径距离</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 80px;
width: 400px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
line-height: 80px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.02288398281758, 116.28905901239955);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 14,
});
var tencent = new TMap.LatLng(40.040452, 116.273486);
var oldSummerPalace = new TMap.LatLng(40.006540, 116.303695);
var path = [tencent, oldSummerPalace];
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer', //图层唯一标识
map: map,//绘制到目标地图
//折线数据定义
geometries: [
{
id: 'line',
paths: path
}
]
});
// 计算路径的实际距离
var distance = TMap.geometry.computeDistance(path);
var infoDom = document.getElementById('info');
infoDom.innerText = `腾讯北京总部到圆明园的直线距离为distance.toFixed(2)米`;
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/矢量数据图层_编辑GeojsonLayer数据.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>编辑GeojsonLayer数据</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=vector,tools"></script>
<script charset="utf-8" src="https://mapapi.qq.com/web/jsapi/jsapi-gl/assets/geojson-demo.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
position: relative;
}
#toolControl {
position: absolute;
top: 10px;
left: 0px;
right: 0px;
margin: auto;
width: 126px;
z-index: 1001;
}
.toolItem {
width: 30px;
height: 30px;
float: left;
margin: 1px;
padding: 4px;
border-radius: 3px;
background-size: 30px 30px;
background-position: 4px 4px;
background-repeat: no-repeat;
box-shadow: 0 1px 2px 0 #E4E7EF;
background-color: #ffffff;
border: 1px solid #ffffff;
}
.toolItem:hover {
border-color: #789CFF;
}
.active {
border-color: #D5DFF2;
background-color: #D5DFF2;
}
#delete {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/delete.png');
}
#split {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/split.png');
}
#union {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/union.png');
}
</style>
<body>
<div id="container"></div>
<div id="toolControl">
<div class="toolItem" id="delete" onclick="editor.delete();" title="删除"></div>
<div class="toolItem" id="split" onclick="editor.split();" title="拆分"></div>
<div class="toolItem" id="union" onclick="editor.union();" title="合并"></div>
</div>
<div>
单选:鼠标左键点击图形<br />
多选:按下ctrl键后点击多个图形<br />
删除:选中图形后按下delete键或点击删除按钮可删除图形<br />
编辑:选中图形后出现编辑点,拖动编辑点可移动顶点位置,双击实心编辑点可删除顶点<br />
拆分:选中单个多边形后可绘制拆分线,拆分线绘制完成后自动进行拆分<br />
合并:选中多个相邻多边形后可进行合并,飞地形式的多边形不支持合并<br />
中断:按下esc键可中断当前操作,点选的图形将取消选中,编辑过程将中断
</div>
<script>
var center = new TMap.LatLng(40.13659649977259, 116.51136018387479);
//初始化地图
var map = new TMap.Map("container", {
zoom: 9, // 设置地图缩放级别
center, // 设置地图中心点坐标
});
var geoLayer = new TMap.vector.GeoJSONLayer({
map: map,
data: geojsonData,
polygonStyle: new TMap.PolygonStyle({
'showBorder': true, //是否显示边线
'borderColor': '#fff', //边线颜色
'borderWidth': 1 //边线宽度
})
});
// 获取geojson生成的polygon图层
var polygonLayer = geoLayer.getGeometryOverlay('polygon');
polygonLayer.setStyles({
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
},
);
// 初始化几何图形及编辑器
var editor = new TMap.tools.GeometryEditor({
map, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: polygonLayer,
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
modifierShapeMode: TMap.tools.constants.MODIFIER_SHAPE_MODE.GL, // 大数据量下的编辑点使用GL绘制模式编辑性能更佳
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
});
// 监听删除、修改、拆分、合并完成事件
let evtList = ['delete', 'adjust', 'split', 'union'];
evtList.forEach(evtName => {
editor.on(evtName + '_complete', evtResult => {
console.log(evtName, evtResult);
});
});
// 监听拆分失败事件,获取拆分失败原因
editor.on('split_fail', (res) => {
alert(res.message);
});
// 监听合并失败事件,获取合并失败原因
editor.on('union_fail', (res) => {
alert(res.message);
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/信息窗口_自定义信息窗体.html
<!DOCTYPE html>
<html>
<head>
<title>CustomInfoWindow</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src='https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77'></script>
<style>
* {
padding: 0px;
margin: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100vw;
height: 100vh;
}
.info_card {
display: inline-block;
margin: 50px auto;
position: absolute;
width: 200px;
height:100px;
background-color: #c7c9c8;
border: 5px solid #ffffff;
color: #000000;
}
.info_card .title{
width: 100%;
height:40px;
background-color: #000000;
color: #ffffff;
}
.title span.title_name{
position: relative;
top: 7px;
left: 10px;
font-size: 18px;
}
.info_card .title .close_img{
position: absolute;
top:10px;
right: 10px;
width: 20px;
height: 20px;
background-color: #ffffff;
}
.info_card .title .close_img .min {
width: 0;
height: 0;
font-size:0;
overflow:hidden;
position:absolute;
border-width:10px;
}
.info_card .title .close_img .top_img {
border-style:solid dashed dashed;
border-color:#000000 transparent transparent transparent;
top:-2px;
}
.info_card .title .close_img .right_img {
border-style:solid dashed dashed;
border-color:transparent #000000 transparent transparent;
left:2px;
}
.info_card .title .close_img .bottom_img{
border-style:solid dashed dashed;
border-color:transparent transparent #000000 transparent;
top:2px;
}
.info_card .title .close_img .left_img{
border-style:solid dashed dashed;
border-color:transparent transparent transparent #000000;
left:-2px;
}
.info_card span.cancle{
width:0;
height:0;
font-size:0;
overflow:hidden;
position:absolute;
}
.info_card span.bot{
border-width:20px;
border-style:solid dashed dashed;
border-color:#ffffff transparent transparent;
left:80px;
bottom:-40px;
}
.info_card span.top{
border-width:20px;
border-style:solid dashed dashed;
border-color:#c7c9c8 transparent transparent;
left:80px;
bottom:-33px;
}
.info_card .content{
margin-top: 10px;
margin-left: 10px;
}
</style>
</head>
<body>
<div id='mapContainer'></div>
<script>
window.onload = init;
var map;
var infoWindow;
var marker;
var rightClicked = false;
function closeInfoWindow() {
infoWindow.close();
}
function openInfoWindow() {
infoWindow.open();
}
function init() {
map = new TMap.Map('mapContainer', {
zoom: 16,
pitch: 30,
rotation: 10,
center: new TMap.LatLng(40.040422, 116.273521),
});
marker = new TMap.MultiMarker({
id: 'marker-layer', // 图层id
map: map,
styles: {
// 点标注的相关样式
marker: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src:
'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png',
}),
},
geometries: [
{
// 点标注数据数组
id: 'demo',
styleId: 'marker',
position: new TMap.LatLng(40.040422, 116.273521),
},
],
});
marker.on('click', openInfoWindow);
infoWindow = new TMap.InfoWindow({
map: map,
enableCustom: true,
position: new TMap.LatLng(40.040422, 116.273521),
offset: { y: -70, x: -5 },
content:
'<div class="info_card"><div class="title"><span class="title_name">腾讯北京总部大楼</span><div class="close_img" onclick="closeInfoWindow()"><span class="min top_img"></span><span class="min right_img"></span><span class="min bottom_img"></span><span class="min left_img"></span></div></div><div align="left" class="content">点击右键变更内容</div><span class="cancle bot"></span><span class="cancle top"></span></div>',
});
map.on('rightclick', function () {
// infoWindow的内容可以动态更新
rightClicked = !rightClicked;
if (rightClicked) {
infoWindow.setContent(
'<div class="info_card"><div class="title"><span class="title_name">腾讯北京总部大楼</span><div class="close_img" onclick="closeInfoWindow()"><span class="min top_img"></span><span class="min right_img"></span><span class="min bottom_img"></span><span class="min left_img"></span></div></div><div align="left" class="content">Tencent Beijing <br /> 点击右键变更内容</div><span class="cancle bot"></span><span class="cancle top"></span></div>'
);
} else {
infoWindow.setContent(
'<div class="info_card"><div class="title"><span class="title_name">腾讯北京总部大楼</span><div class="close_img" onclick="closeInfoWindow()"><span class="min top_img"></span><span class="min right_img"></span><span class="min bottom_img"></span><span class="min left_img"></span></div></div><div align="left" class="content">点击右键变更内容</div><span class="cancle bot"></span><span class="cancle top"></span></div>'
);
}
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_点击地图拾取坐标.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>点击地图拾取坐标</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info{
position: absolute;
left: 20px;
top: 20px;
font-size: 14px;
background: #FFF;
width: 270px;
padding: 10px;
border-radius: 3px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">当前点击坐标为:<span id="position"></span></div>
<script>
function initMap() {
var position = document.getElementById("position");
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//绑定点击事件
map.on("click",function(evt){
var lat = evt.latLng.getLat().toFixed(6);
var lng = evt.latLng.getLng().toFixed(6);
position.innerHTML = lat + "," + lng;
})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_设置控件位置及样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>地图控件设置</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
}
#container {
width: 100%;
height: 100%;
}
input[type="button"] {
position: absolute;
z-index: 1001;
width: 160px;
padding: 6px 0;
font-size: 14px;
}
#btn-position-default {
top: 30px;
left: 30px;
}
#btn-position-right-bottom {
top: 30px;
left: 200px;
}
#btn-remove {
top: 80px;
left: 30px;
}
#btn-add {
top: 80px;
left: 200px;
}
#btn-add-class {
top: 130px;
left: 30px;
}
#btn-remove-class {
top: 130px;
left: 200px;
}
#btn-display-zoom-number {
top: 180px;
left: 30px;
}
#btn-hide-zoom-number {
top: 180px;
left: 200px;
}
.control .tmap-zoom-control{
background-color:#F2F2F2;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<input type="button" id="btn-position-default" onclick="setPosition('topRight')" value="缩放控件设为右上角">
<input type="button" id="btn-position-right-bottom" onclick="setPosition('bottomRight')" value="缩放控件设为右下角">
<input type="button" id="btn-remove" onclick="removeControl()" value="移除缩放控件">
<input type="button" id="btn-add" onclick="addControl()" value="添加缩放控件">
<input type="button" id="btn-add-class" onclick="setClassName('control')" value="缩放控件背景变灰">
<input type="button" id="btn-remove-class" onclick="setClassName('')" value="缩放控件恢复默认">
<input type="button" id="btn-display-zoom-number" onclick="displayZoomNumber()" value="缩放控件显示缩放级别">
<input type="button" id="btn-hide-zoom-number" onclick="hideZoomNumber()" value="缩放控件不显示缩放级别">
<script type="text/javascript">
var map, control;
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
map = new TMap.Map("container", {
zoom: 12,//设置地图缩放级别
center: center,//设置地图中心点坐标
});
// 获取缩放控件实例
control = map.getControl(TMap.constants.DEFAULT_CONTROL_ID.ZOOM);
// 监听控件添加和移除事件
map.on('control_added', (e) => {
console.log(e);
});
map.on('control_removed', (e) => {
console.log(e);
});
}
// 设置控件位置
function setPosition(position) {
if (!control) {
return;
}
switch(position) {
case 'topRight':
control.setPosition(TMap.constants.CONTROL_POSITION.TOP_RIGHT);
break;
case 'bottomRight':
control.setPosition(TMap.constants.CONTROL_POSITION.BOTTOM_RIGHT);
break;
}
}
// 移除缩放控件
function removeControl() {
if (!map.getControl(TMap.constants.DEFAULT_CONTROL_ID.ZOOM)) { // 如果map上不存在该控件则直接返回
return;
}
map.removeControl(TMap.constants.DEFAULT_CONTROL_ID.ZOOM);
}
// 把存储的缩放控件添加到地图上
function addControl() {
map.addControl(control);
}
// 为缩放控件添加class,控制样式
function setClassName(name) {
if (!control) {
return;
}
control.setClassName(name);
}
// 缩放控件显示缩放级别
function displayZoomNumber() {
control.setNumVisible(true);
}
// 缩放控件不显示缩放级别
function hideZoomNumber() {
control.setNumVisible(false);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线样式自定义.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>折线样式自定义</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
#buttonContainer {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
#buttonContainer input {
background: #fff;
padding: 10px;
width: 120xp;
outline-style: none;
margin-right: 10px;
border-radius: 10px;
}
</style>
<body onload="initMap()">
<div id="buttonContainer">
<input type="button" onclick="setStyle('styleOne')" value="设置样式1">
<input type="button" onclick="setStyle('styleTwo')" value="设置样式2">
</div>
<div id="mapContainer"></div>
</body>
</html>
<script>
var map = null;
var polyline = null;
function initMap() {
var center = new TMap.LatLng(40.040452,116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('mapContainer', {
center: center,
zoom: 16
});
polyline = new TMap.MultiPolyline({
id: 'polyline-layer',
map: map,
styles: {
'polyline': new TMap.PolylineStyle({
'color': '#00FF00', //线填充色
'width': 4, //折线宽度
'borderWidth': 5, //边线宽度
'borderColor': 'rgba(0,125,255,0.5)', //边线颜色
'lineCap': 'round' //线端头方式
}),
},
geometries: [
{
'id': 'polyline', //折线图形数据的标志信息
'styleId': 'polyline', //样式id
'paths': [new TMap.LatLng(40.038540, 116.272389), new TMap.LatLng(40.038844, 116.275210), new TMap.LatLng(40.041407, 116.274738)], //折线的位置信息
'properties': { //折线的属性数据
'title': 'customStyle'
}
},
]
});
}
function setStyle(type) {
switch (type) {
case 'styleOne':
polyline.setStyles({
'polyline': new TMap.PolylineStyle({
'color': '#00FF00', //线填充色
'width': 6, //折线宽度
'borderWidth': 5, //边线宽度
'borderColor': 'rgba(0,125,255,0.5)', //边线颜色
'lineCap': 'round' //线端头方式
})
});
break;
case 'styleTwo':
polyline.setStyles({
'polyline': new TMap.PolylineStyle({
'color': 'rgba(255,0,0,0.3)', //线填充色
'width': 10,//折线宽度
'borderWidth': 4, //边线宽度
'borderColor': '#999', //边线颜色
'lineCap': 'square' //线端头方式
})
});
break;
default: ;
}
}
</script>
FILE:references/jsapigl/demos/自定义覆盖物_DomOverlay阻止事件冒泡.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>DOMOverlay阻止鼠标事件冒泡</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.infoDom {
width: 200px;
height: 100px;
background-color: #fff;
border: 5px rgba(154, 241, 253, 0.7) solid;
border-radius: 20px;
display: flex;
flex-direction: column;
font-family: Arial, sans-serif;
position:absolute;
top:0px;
left:0px;
visibility: hidden;
}
.title {
width: 100%;
height: 35%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #fff;
border-radius: 15px 15px 0 0;
background-color: #3876ff;
}
.content{
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 65%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div class="infoDom" id="infoDom">
<div class="title"></div>
<div class="content"></div>
</div>
<script>
// 自定义信息窗口 - 继承DOMOverlay
function InfoDom(options) {
TMap.DOMOverlay.call(this, options);
}
InfoDom.prototype = new TMap.DOMOverlay();
// 初始化
InfoDom.prototype.onInit = function(options) {
this.position = options.position;
this.title = options.title;
this.content = options.content;
this.width = options.width || 200;
this.height = options.height || 100;
};
// 销毁时需解绑事件监听
InfoDom.prototype.onDestroy = function() {
if (this.onClick) {
this.dom.removeEventListener(this.onClick);
}
};
// 创建DOM元素,返回一个DOMElement,使用this.dom可以获取到这个元素
InfoDom.prototype.createDOM = function() {
// 复制infoDom
var infoDom = document.getElementById('infoDom');
var clonedInfoDom = infoDom.cloneNode(true);
clonedInfoDom.style.width = this.width + 'px';
clonedInfoDom.style.height = this.height + 'px';
clonedInfoDom.style.visibility = 'visible';
clonedInfoDom.children[0].textContent = this.title;
clonedInfoDom.children[1].innerText = this.content;
// click事件监听
this.onClick = () => {
// DOMOverlay继承自EventEmitter,可以使用emit触发事件
this.emit('click');
};
// pc端注册click事件,移动端注册touchend事件
clonedInfoDom.addEventListener('click', this.onClick);
return clonedInfoDom;
};
// 更新DOM元素,在地图移动/缩放后执行
InfoDom.prototype.updateDOM = function() {
if (!this.map) {
return;
}
// 经纬度坐标转容器像素坐标
let pixel = this.map.projectToContainer(this.position);
// 使饼图中心点对齐经纬度坐标点
let left = pixel.getX() - this.dom.clientWidth / 2 + 'px';
let top = pixel.getY() - this.dom.clientHeight / 2 + 'px';
this.dom.style.transform = `translate(left, top)`;
};
window.InfoDom = InfoDom;
</script>
<script type="text/javascript">
var map;
function initMap() {
// 初始化地图
map = new TMap.Map("container", {
zoom:12, // 设置地图缩放级别
center: new TMap.LatLng(39.984104, 116.307503) // 设置地图中心点坐标
});
let infoDomList = [
new InfoDom({
map,
position: new TMap.LatLng(39.96030543872138, 116.25809083213608),
title: '设置了阻止鼠标事件冒泡',
content:'点我不会触发map监听事件',
width: 200,
height: 100,
isStopPropagation: true, // 是否阻止鼠标事件冒泡,默认为false
}),
new InfoDom({
map,
position: new TMap.LatLng(39.9986945980902, 116.3998362780685),
title: '未设置阻止鼠标事件冒泡',
content:'点我会触发map监听事件,\n弹出弹窗',
width: 200,
height: 100,
})
];
infoDomList.forEach((infoDom, index) => {
infoDom.on('click', () => {
console.log(`第index个信息DOM被点击,位置为infoDom.position`);
});
});
map.on('click',()=>{
alert(`地图被点击了!`);
})
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/自定义栅格图层_自定义栅格图层.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自定义栅格图层</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="mapContainer"></div>
<script>
var center = new TMap.LatLng(26.870355,100.239704);//设置中心点坐标
//初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 15,
maxZoom:16
});
//初始化imageTileLayer
var imageTileLayer = new TMap.ImageTileLayer({
getTileUrl: function (x, y, z) {
//拼接瓦片URL
var url='https://3gimg.qq.com/visual/lbs_gl_demo/image_tiles_layers/' + z + '/' + x + '_' + y +'.png' ;
return url;
},
tileSize: 256, //瓦片像素尺寸
minZoom: 14, //显示自定义瓦片的最小级别
maxZoom: 16, //显示自定义瓦片的最大级别
visible: true, //是否可见
zIndex: 5000, //层级高度(z轴)
opacity: 0.95, //图层透明度:1不透明,0为全透明
map: map, //设置图层显示到哪个地图实例中
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_点击地图添加marker.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>点击地图添加marker</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script>
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始化marker图层
var markerLayer = new TMap.MultiMarker({
id: 'marker-layer',
map: map
});
//监听点击事件添加marker
map.on("click", (evt) => {
markerLayer.add({
position: evt.latLng
});
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_点击地图拾取POI.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>点击地图拾取POI</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}
#mapContainer {
position: relative;
height: 100%;
width: 100%;
}
</style>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<body onload="init()">
<div id='mapContainer'></div>
<script>
var map;
function init() {
var drawContainer = document.getElementById('mapContainer');
var center = new TMap.LatLng(39.953416, 116.380945);
map = new TMap.Map(drawContainer, {
zoom: 13,
pitch: 40,
center: center
});
// 创建信息窗
let info = new TMap.InfoWindow({
map,
position: map.getCenter()
}).close();
map.on('click', (evt) => {
// 获取click事件返回的poi信息
let poi = evt.poi;
if (poi) {
// 拾取到POI
info.setContent(poi.name).setPosition(poi.latLng).open();
} else {
// 没有拾取到POI
info.close();
}
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线-虚线样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>折线-虚线样式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.98183908712013, 116.31021738052368);
//初始化地图
var map = new TMap.Map("container", {
zoom: 16,
center: center
});
var paths = [
new TMap.LatLng(39.98481500648338, 116.30571126937866),
new TMap.LatLng(39.982266575222155, 116.30596876144409),
new TMap.LatLng(39.982348784165886, 116.3111400604248),
new TMap.LatLng(39.978813710266024, 116.3111400604248),
new TMap.LatLng(39.978813710266024, 116.31699800491333)
];
var dashPaths = [
new TMap.LatLng(39.98481500648338, 116.30571126937866),
new TMap.LatLng(39.97899457895122, 116.30601167678833),
new TMap.LatLng(39.97864928377309, 116.31277084350586),
new TMap.LatLng(39.978813710266024, 116.31699800491333)
];
var polylineLayer = new TMap.MultiPolyline({
map, // 绘制到目标地图
// 折线样式定义
styles: {
'style_blue': new TMap.PolylineStyle({
'color': '#295BFF', // 线填充色
'width': 6, // 折线宽度
'lineCap': 'round', // 线端头方式
}),
'style_dash': new TMap.PolylineStyle({
color: '#224BCF', // 线填充色
width: 5, // 折线宽度
lineCap: 'round', // 线端头方式
dashArray: [10, 10] // 虚线展示方式
})
},
geometries: [{
styleId: 'style_blue',
paths: paths
},{
styleId: 'style_dash',
paths: dashPaths
}],
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_为多个marker添加事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>为多个marker添加事件</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info {
position: absolute;
left: 30px;
top: 30px;
background: #FFF;
width: 350px;
height: 120px;
padding: 10px;
border-radius: 3px;
}
p,
h4 {
margin: 0;
padding: 0;
margin-bottom: 10px;
}
h4{
margin-top: 30px;
}
#add {
position: absolute;
left:10px;
top: 10px;
z-index: 9999;
}
#remove{
position: absolute;
left: 120px;
top: 10px;
z-index: 9999;
}
</style>
<body>
<div id="container"></div>
<div id="info">
<input type="button" id="add" value="绑定点击事件" onclick="addClick()">
<input type="button" id="remove" value="解绑点击事件" onclick="removeClick()">
<h4 id="txt"></h4>
<p id="markerid">markerID:marker1</p>
<p id="position">当前marker位置:39.984104,116.407503</p>
</div>
<script>
var txt = document.getElementById("txt");
var markerID = document.getElementById("markerid");
var position = document.getElementById("position");
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始化marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
})
},
geometries: [{
"id": "marker1",
"styleId": 'marker',
"position": new TMap.LatLng(39.954104, 116.357503),
"properties": {
"title": "marker1"
}
}, {
"id": "marker2",
"styleId": 'marker',
"position": new TMap.LatLng(39.994104, 116.287503),
"properties": {
"title": "marker2"
}
}, {
"id": "marker3",
"styleId": 'marker',
"position": new TMap.LatLng(39.984104, 116.307503),
"properties": {
"title": "marker3"
}
}
]
});
//监听回调函数(非匿名函数)
var eventClick = function (evt) {
markerID.innerHTML = "markerID:" + evt.geometry.id;
position.innerHTML = "当前marker位置:" + evt.geometry.position.toString();
}
function addClick() {
txt.innerHTML = "绑定点击事件";
//监听marker点击事件
marker.on("click", eventClick)
}
function removeClick() {
txt.innerHTML = "解绑点击事件";
//移除绑定事件
marker.off("click", eventClick);
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线-大圆航线.html
<!DOCTYPE html>
<html>
<head>
<title>map</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<style>
* {
padding: 0px;
margin: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100vw;
height: 100vh;
}
</style>
</head>
<body>
<div id='mapContainer'></div>
<div id="text"></div>
<script>
</script>
<script src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<script>
window.onload = initMap;
var myMap;
function initMap() {
myMap = new TMap.Map('mapContainer', {
zoom: 3,
mapStyleId: 'DARK',
center: new TMap.LatLng(34.791861, 179.399422),
});
var path = [
new TMap.LatLng(40.08032, 116.602789),
new TMap.LatLng(35.277722, -119.627338),
];
var polylineTmp1 = new TMap.MultiPolyline({
map: myMap,
styles: {
style1: new TMap.PolylineStyle({
color: 'rgba(0,0,255,1)',
width: 5,
}),
},
// 是否开启大圆航线接口
enableGeodesic: true,
geometries: [
{
styleId: 'style1',
paths: path,
},
],
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_环形(带洞)多边形.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>环形(带洞)多边形</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style>
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#mapContainer {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="mapContainer"></div>
</body>
</html>
<script>
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486); // 设置中心点坐标
// 初始化地图
var map = new TMap.Map('mapContainer', {
center: center,
zoom: 16,
});
var path = [
// 环形多边形的位置信息
[
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
],
[
new TMap.LatLng(40.040852, 116.273756),
new TMap.LatLng(40.040458, 116.273804),
new TMap.LatLng(40.040544, 116.274464),
new TMap.LatLng(40.040926, 116.274282),
],
];
// 初始化polygon
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', // 图层id
map: map, // 显示多边形图层的底图
styles: {
// 多边形的相关样式
polygon: new TMap.PolygonStyle({
color: '#00F0CC', // 面填充色
showBorder: false, // 是否显示拔起面的边线
borderColor: '#999', // 边线颜色
}),
},
geometries: [
{
id: 'polygon', // 多边形图形数据的标志信息
styleId: 'polygon', // 样式id
paths: path, // 多边形的位置信息
properties: {
// 多边形的属性数据
title: 'polygon',
},
},
],
});
}
</script>
FILE:references/jsapigl/demos/地图操作示例_区域高亮.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>区域高亮</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=service"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
</body>
<script>
var map;
var district;
function initMap() {
// 初始化地图
map = new TMap.Map('container', {
// Map文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap
zoom: 9,
center: new TMap.LatLng(40.15739276079734, 116.43926381582764), // 设置地图中心点坐标
pitch: 20, // 俯仰度
rotation: 0, // 旋转角度,
mapStyleId: 'style1',
baseMap: [
{ type: 'vector', features: ['base'] },
{
type: 'traffic',
opacity: 1,
},
],
renderOptions: {
enableBloom: true, // 是否启用泛光效果 注:为true才会有效果
},
});
highlight();
}
function highlight() {
// 服务类库文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocService
district = new TMap.service.District({
// 新建一个行政区划类
polygon: 2, // 返回行政区划边界的类型
});
// 关键字搜索行政区划,110000 = 北京行政区划代码
district.search({ keyword: 110000 }).then((result) => {
if (!result && !result.result) return;
// 获取省市区列表及其边界信息
var paths = result.result[0][0].polygon;
map.enableAreaHighlight({
paths: paths,
highlightColor: 'rgba(0,0,0,0)', // 高亮颜色
shadeColor: 'rgba(4,8,14,1)', // 其余阴影部分
});
// MultiPolyline文档地址:https://lbs.qq.com/javascript_gl/doc/multiPolylineOptions.html
new TMap.MultiPolyline({
map: map,
styles: {
polyline: new TMap.PolylineStyle({
color: '#017cf7', // 线条填充色,
width: 8,
lineCap: 'round',
enableBloom: true, // 是否启用泛光 注:为true才会有效果
}),
},
geometries: [
{
styleId: 'polyline', // 样式id
paths: paths,
},
],
});
});
}
</script>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_自适应显示多个标记.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>自适应显示多个标记</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info{
position: absolute;
left: 30px;
top:30px;
background: #FFF;
width:350px;
height: 80px;
padding: 10px;
border-radius: 3px;
}
input{
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
}
</style>
<body>
<div id="container"></div>
<input type="button" value="自适应显示多个标注" onclick="showMarker()">
<script>
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
var markerArr = [{
"id": "marker1",
"styleId": 'marker',
"position": new TMap.LatLng(39.954104, 116.457503),
"properties": {
"title": "marker1"
}
}, {
"id": "marker2",
"styleId": 'marker',
"position": new TMap.LatLng(39.794104, 116.287503),
"properties": {
"title": "marker2"
}
},{
"id": "marker3",
"styleId": 'marker',
"position": new TMap.LatLng(39.984104, 116.307503),
"properties": {
"title": "marker3"
}
}
];
//初始化marker
var marker = new TMap.MultiMarker({
id: 'marker-layer',
map: map,
styles: {
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/markerDefault.png'
})
},
geometries: markerArr
});
//初始化
var bounds = new TMap.LatLngBounds();
//设置自适应显示marker
function showMarker(){
//判断标注点是否在范围内
markerArr.forEach(function(item){
//若坐标点不在范围内,扩大bounds范围
if(bounds.isEmpty() || !bounds.contains(item.position)){
bounds.extend(item.position);
}
})
//设置地图可视范围
map.fitBounds(bounds, {
padding: 100 // 自适应边距
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/几何计算库_计算线与多边形是否相交.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>判断线是否与多边形相交</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 100px;
width: 420px;
line-height: 50px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 18,
baseMap: {
type: 'vector',
features: ['base', 'building3d', 'label'],
}
});
var lineRed = [
new TMap.LatLng(40.04154035247704, 116.27144748316823),
new TMap.LatLng(40.04194283332685, 116.27456963249642),
];
var lineBlue = [
new TMap.LatLng(40.04016040071088, 116.27174789647142),
new TMap.LatLng(40.040587531642345, 116.2749236907166),
];
var path = [
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map,
geometries: [
{
id: 'polygon', //多边形图形数据的标志信息
paths: path, //多边形的位置信息
}
]
});
var polylineLayer = new TMap.MultiPolyline({
id: 'polyline-layer', //图层唯一标识
map: map,//绘制到目标地图
//折线数据定义
styles: {
red: new TMap.PolylineStyle({
color: 'rgba(255,0,0,0.8)', // 面填充色
}),
blue: new TMap.PolylineStyle({
color: 'rgba(0,0,255,0.8)', // 面填充色
}),
},
geometries: [
{
styleId: 'red',
paths: lineRed
},
{
styleId: 'blue',
paths: lineBlue
}
]
});
var infoDom = document.getElementById('info');
infoDom.innerText =
`'红线与多边形不相交'` + '\n' +
`'蓝线与多边形不相交'`;
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/应用工具_编辑器的禁用和启用.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>编辑器的禁用和启用</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?libraries=tools&v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
position: relative;
}
#toolControl {
position: absolute;
top: 10px;
left: 0px;
right: 0px;
margin: auto;
width: 120px;
text-align: center;
z-index: 1001;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="toolControl">
<button onclick="editor.enable();">启用</button>
<button onclick="editor.disable();">禁用</button>
</div>
<div>
编辑器启用后地图进入2D视图模式,无法调整俯仰角及旋转角度<br/>
编辑器禁用后地图恢复原状,无法进行绘制和交互。
</div>
<script type="text/javascript">
var map, editor;
function initMap() {
// 初始化地图
map = new TMap.Map("container", {
zoom: 17, // 设置地图缩放级别
center: new TMap.LatLng(40.04019000341765, 116.27446815226199) // 设置地图中心点坐标
});
var simplePath = [
new TMap.LatLng(40.04051164600918, 116.27488518619089),
new TMap.LatLng(40.040943635857445, 116.27804611629756),
new TMap.LatLng(40.03951759379146, 116.2783762087081),
new TMap.LatLng(40.03891287066983, 116.2752049655744)
];
// 初始化几何图形及编辑器
editor = new TMap.tools.GeometryEditor({
map, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: new TMap.MultiPolygon({
map,
styles: {
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
},
geometries: [
{
paths: simplePath
}
]
}),
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
snappable: true // 开启吸附
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_下属行政区查询.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>下属行政区查询</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
.options {
position: relative;
display: flex;
align-items: center;
width: 100%;
height: 2rem;
}
.title {
width: 3rem;
text-align: justify;
display: inline-block;
text-align-last: justify;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<h4>下属行政区查询</h4>
<div class="input-item">
<div class="title">省市区</div>
<select id='province' style="width:200px" onchange='search(this)'></select>
</div>
<div class="input-item">
<div class="title">地级市</div>
<select id='city' style="width:200px" onchange='search(this)'></select>
</div>
<div class="input-item">
<div class="title">区县</div>
<select id='district' style="width:200px" onchange='search(this)'></select>
</div>
<div class="input-item">
<div class="title">街道</div>
<select id='street' style="width:200px" onchange='search(this)'></select>
</div>
</div>
</body>
</html>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 8,
center: new TMap.LatLng(40, 116),
});
var provinceSelect = document.getElementById('province');
var citySelect = document.getElementById('city');
var districtSelect = document.getElementById('district');
var areaSelect = document.getElementById('street');
var provinceList = [];
var cityList = [];
var districtList = [];
var areaList = [];
var district = new TMap.service.District({
// 新建一个行政区划类
polygon: 2, // 返回行政区划边界的类型
});
var polygons = new TMap.MultiPolygon({
map: map,
geometries: [],
});
district.getChildren().then((result) => {
// 获取省市区列表及其边界信息
provinceList = result.result[0];
provinceSelect.add(new Option('---请选择---', null));
provinceList.forEach((province, index) => {
provinceSelect.add(new Option(province.fullname, index));
});
citySelect.innerHTML = '';
districtSelect.innerHTML = '';
areaSelect.innerHTML = '';
});
function search(selector) {
if (selector.id === 'province' && selector.value) {
citySelect.innerHTML = '';
districtSelect.innerHTML = '';
areaSelect.innerHTML = '';
citySelect.add(new Option('---请选择---', null));
district
.getChildren({ id: provinceList[selector.value].id })
.then((result) => {
// 根据选择的省市区获取其下级行政区划及其边界
cityList = result.result[0];
cityList.forEach((city, index) => {
citySelect.add(new Option(city.fullname, index));
});
});
drawPolygon(
provinceList[selector.value].id,
provinceList[selector.value].polygon
); // 根据所选区域绘制边界
} else if (selector.id === 'city' && selector.value) {
districtSelect.innerHTML = '';
areaSelect.innerHTML = '';
districtSelect.add(new Option('---请选择---', null));
district.getChildren({ id: cityList[selector.value].id }).then((result) => {
// 根据选择的地级市或直辖市区获取其下级行政区划及其边界
if (result.result[0].length > 0 && result.result[0][0].id.length > 6) {
// 直辖市的区的下级即为街道级,故略过一级
districtList = [];
districtSelect.innerHTML = '';
districtSelect.add(new Option('---------', null));
areaList = result.result[0];
areaSelect.add(new Option('---请选择---', null));
areaList.forEach((district, index) => {
areaSelect.add(new Option(district.fullname, index));
}); // 根据所选区域绘制边界
} else {
// 非直辖市的地级市之下有区县级
districtList = result.result[0];
districtList.forEach((district, index) => {
districtSelect.add(new Option(district.fullname, index));
});
}
});
drawPolygon(cityList[selector.value].id, cityList[selector.value].polygon);
// 根据所选区域绘制边界
} else if (selector.id === 'district' && selector.value) {
areaSelect.innerHTML = '';
district
.getChildren({ id: districtList[selector.value].id })
.then((result) => {
// 根据选择的区县获取其下级行政区划及位置
areaList = result.result[0];
areaList.forEach((area, index) => {
areaSelect.add(new Option(area.fullname, index));
});
});
areaSelect.add(new Option('---请选择---', null));
drawPolygon(
districtList[selector.value].id,
districtList[selector.value].polygon
);
} else if (selector.id === 'street' && selector.value) {
map.setCenter(areaList[selector.value].location);
// 街道级仅提供位置信息不提供边界信息,故以设置地图中心代替边界绘制
}
}
function drawPolygon(placeId, polygonArray) {
// 根据多边形顶点坐标数组绘制多边形
polygons.remove(polygons.getGeometries().map((item) => item.id));
var bounds = [];
var newGeometries = polygonArray.map((polygon, index) => {
bounds.push(fitBounds(polygon));
return {
id: `placeId_index`,
paths: polygon,
};
});
bounds = bounds.reduce((a, b) => {
return fitBounds([
a.getNorthEast(),
a.getSouthWest(),
b.getNorthEast(),
b.getSouthWest(),
]);
});
polygons.updateGeometries(newGeometries);
map.fitBounds(bounds);
}
function fitBounds(latLngList) {
// 由多边形顶点坐标数组计算能完整呈现该多边形的最小矩形范围
if (latLngList.length === 0) {
return null;
}
var boundsN = latLngList[0].getLat();
var boundsS = boundsN;
var boundsW = latLngList[0].getLng();
var boundsE = boundsW;
latLngList.forEach((point) => {
point.getLat() > boundsN && (boundsN = point.getLat());
point.getLat() < boundsS && (boundsS = point.getLat());
point.getLng() > boundsE && (boundsE = point.getLng());
point.getLng() < boundsW && (boundsW = point.getLng());
});
return new TMap.LatLngBounds(
new TMap.LatLng(boundsS, boundsW),
new TMap.LatLng(boundsN, boundsE)
);
}
</script>
FILE:references/jsapigl/demos/点标记与文本标记_Marker随地图放大缩小.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Marker自动缩放</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1" onclick="onOff()">开关Marker自动缩放功能</button>
</div>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.98210863924864, 116.31310899739151);
// 初始化地图
var map = new TMap.Map('container', {
zoom: 17, // 设置地图缩放
center: new TMap.LatLng(39.98210863924864, 116.31310899739151), // 设置地图中心点坐标,
pitch: 0, // 俯仰度
rotation: 0, // 旋转角度
});
// MultiMarker文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
var marker = new TMap.MultiMarker({
map: map,
styles: {
// 点标记样式
'marker_enableScale': new TMap.MarkerStyle({
width: 20, // 样式宽
height: 30, // 样式高
anchor: { x: 10, y: 30 }, // 描点位置
enableRelativeScale: true, // 是否开启相对缩放
relativeScaleOptions: {
scaleZoom: 17, // 设置marker图片宽高像素单位与zoom级别的瓦片像素相同的层级 如当设置为18时,zoom小于18marker会被缩小直至达到minScale设置的最小缩放倍数,大于时marker直至达到maxScale设置的最大缩放倍数;enableRelativeScale为true时生效,默认18
minScale: 0.5, // 设置marker最小缩放倍数,enableRelativeScale为true时生效,默认0.5
maxScale: 1, // 设置marker最大缩放倍数,enableRelativeScale为true时生效,默认1
}
}),
'marker_disabledScale': new TMap.MarkerStyle({
width: 20, // 样式宽
height: 30, // 样式高
anchor: { x: 10, y: 30 }, // 描点位置
enableRelativeScale: false, // 是否开启相对缩放
})
},
geometries: [
// 点标记数据数组
{
// 标记位置(纬度,经度,高度)
position: center,
id: 'marker',
styleId: 'marker_enableScale'
},
],
});
// 开关Marker自动缩放功能
function onOff() {
marker.updateGeometries({
id: 'marker',
position: center,
styleId: marker.getGeometries()[0].styleId === 'marker_enableScale' ? 'marker_disabledScale' : 'marker_enableScale'
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_IP定位.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>IP定位</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<p><input type='text' id='ipInput' placeholder="输入IP地址(默认为请求端的IP)" size='30'/><input type='button' id='locate' value='搜索所在位置' onclick='locate()' /></p>
<p id="ipLocationResult"></p>
</div>
</body>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 14,
center: new TMap.LatLng(40.0402718, 116.2735831),
});
var ipLocation = new TMap.service.IPLocation(); // 新建一个IP定位类
var markers = new TMap.MultiMarker({
map: map,
geometries: [],
});
function locate() {
var ipInput = document.getElementById('ipInput').value;
var params = ipInput ? { ip: ipInput } : {};
ipLocation
.locate(params)
.then((result2) => {
// 未给定ip地址则默认使用请求端的ip
var { result } = result2;
markers.updateGeometries([
{
id: 'main',
position: result.location, // 将所得位置绘制在地图上
},
]);
map.setCenter(result.location);
document.getElementById(
'ipLocationResult'
).innerText = `您的IP/您输入的IP所在位置:result.ad_info.nation,result.ad_info.province`;
})
.catch((error) => {
document.getElementById(
'ipLocationResult'
).innerText = `错误:error.status,error.message`;
});
}
locate();
</script>
</html>
FILE:references/jsapigl/demos/点标记与文本标记_marker轨迹回放-跟随模式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>marker轨迹回放-跟随模式</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #919aac;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
}
.btnContainer button:focus {
outline: none;
}
.btnContainer .btn1 {
padding: 10px 14px;
background: #3876ff;
border-radius: 2px;
border: none;
box-sizing: border-box;
font-size: 14px;
color: #fff;
line-height: 14px;
font-family: PingFangSC-Regular;
}
input {
height: 25px;
}
.info {
background-color: white;
padding: 10px;
font-size: 14px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn1">点击小车开始移动</button>
<button class="btn2">重置</button>
<button class="btn3">暂停</button>
<button class="btn4">恢复</button>
<p>
<input type="text" placeholder="请输入限制移动距离" />
<button class="btn5">点击限制移动距离</button>
</p>
<div class="info">
<p></p>
<p>当前限制小车最大移动距离:0米</p>
<p>当前小车行驶距离:0米</p>
<p></p>
</div>
</div>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.983860482192014, 116.30891946495672);
// 初始化地图
var map = new TMap.Map('container', {
zoom: 16,
center
});
var isMoving = false;
var roation;
var position;
var path = [
new TMap.LatLng(39.98481500648338, 116.30571126937866),
new TMap.LatLng(39.982266575222155, 116.30596876144409),
new TMap.LatLng(39.982348784165886, 116.3111400604248)
];
var limitDistance = 0; // 限制距离
// MultiPolyline文档:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector#1
var polylineLayer = new TMap.MultiPolyline({
map, // 绘制到目标地图
// 折线样式定义
styles: {
style_blue: new TMap.PolylineStyle({
color: '#2A88FF', // 线填充色
width: 8, // 折线宽度
borderWidth: 3, // 边线宽度
borderColor: '#0569FF', // 边线颜色
lineCap: 'round', // 线端头方式
showArrow: true
}),
style_gray: new TMap.PolylineStyle({
color: '#ccc', // 线填充色
width: 8, // 折线宽度
borderWidth: 3, // 边线宽度
borderColor: '#FFF', // 边线颜色
lineCap: 'round' // 线端头方式
})
},
geometries: [
{
id: 'path1',
styleId: 'style_blue',
paths: path
},
{
id: 'path2',
styleId: 'style_blue',
paths: path
}
]
});
// marker文档:https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker
var marker = new TMap.MultiMarker({
id: 'car',
map,
styles: {
'car-down': new TMap.MarkerStyle({
width: 48,
height: 72,
anchor: {
x: 24,
y: 36
},
faceTo: 'map',
rotate: 180,
src: 'https://mapapi.qq.com/web/mapComponents/componentsTest/zyTest/static/model_taxi.png?a=1'
}),
start: new TMap.MarkerStyle({
anchor: {
x: 16,
y: 32
},
src: 'https://mapapi.qq.com/web/miniprogram/demoCenter/images/marker-start.png'
}),
end: new TMap.MarkerStyle({
src: 'https://mapapi.qq.com/web/miniprogram/demoCenter/images/marker-end.png'
})
},
geometries: [
{
id: 'car',
styleId: 'car-down',
position: path[0]
},
{
id: 'start',
styleId: 'start',
position: path[0]
},
{
id: 'end',
styleId: 'end',
position: path[2]
}
]
});
function initInfo() {
// 初始化全程长度及小车起始坐标
var distance = TMap.geometry.computeDistance(path);
var fullLength = document.querySelectorAll('.info p')[0];
fullLength.innerHTML = '路线全程长度:' + parseNumber(distance, 2) + '米';
var currentLatLng = document.querySelectorAll('.info p')[3];
currentLatLng.innerHTML =
'当前小车坐标:' +
parseNumber(path[0].lat, 6) +
',' +
parseNumber(path[0].lng, 6);
}
initInfo();
function parseNumber(value, num) {
// 解析数字
return parseFloat(value).toFixed(num);
}
function carMove() {
map.off('idle', carMove);
marker.moveAlong(
{
car: {
path,
speed: 200
}
},
{
autoRotation: true
}
);
isMoving = true;
}
marker.on('move_ended', function () {
isMoving = false;
});
marker.on('move_stopped', function (e) {
isMoving = false;
});
marker.on('moving', function (e) {
if (!e.car) return;
roation = TMap.geometry.computeHeading(
e.car.passedLatLngs[e.car.passedLatLngs.length - 2],
e.car.passedLatLngs[e.car.passedLatLngs.length - 1]
);
position = TMap.geometry.computeDestination(
marker.getGeometryById('car').position,
roation,
60
);
map.easeTo(
{
center: position,
rotation: e.car.angle && e.car.angle,
zoom: 20,
pitch: 70
},
{
duration: 300
}
);
// 移动路线置灰
polylineLayer.updateGeometries([
{
id: 'path2',
styleId: 'style_gray',
paths: e.car.passedLatLngs
}
]);
// 计算当前移动距离
var currentDistance = TMap.geometry.computeDistance(e.car.passedLatLngs);
if (limitDistance && currentDistance > limitDistance) {
// 大于限制距离 停止移动
marker.stopMove();
}
// 移动过程中更新小车已行驶距离
document.querySelectorAll('.info p')[2].innerHTML =
'当前小车行驶距离:' + parseNumber(currentDistance, 2) + '米';
// 移动过程中更新小车当前坐标
var movingLatLng = e.car.passedLatLngs[e.car.passedLatLngs.length - 1];
document.querySelectorAll('.info p')[3].innerHTML =
'当前小车坐标:' +
parseNumber(movingLatLng.lat, 6) +
',' +
parseNumber(movingLatLng.lng, 6);
});
document.querySelector('.btn1').onclick = function () {
if (isMoving) return;
map.easeTo(
{
zoom: 20,
rotation: 180,
pitch: 80
},
{
duration: 1000
}
);
map.on('idle', carMove);
};
document.querySelector('.btn2').onclick = function () {
marker.stopMove();
isMoving = false;
polylineLayer.setGeometries([
{
id: 'path1',
styleId: 'style_blue',
paths: path
},
{
id: 'path2',
styleId: 'style_blue',
paths: path
}
]);
marker.setGeometries([
{
id: 'car',
styleId: 'car-down',
position: new TMap.LatLng(39.98481500648338, 116.30571126937866)
},
{
id: 'start',
styleId: 'start',
position: new TMap.LatLng(39.98481500648338, 116.30571126937866)
},
{
id: 'end',
styleId: 'end',
position: new TMap.LatLng(39.982348784165886, 116.3111400604248)
}
]);
map.easeTo({
center,
zoom: 16,
rotation: 0,
pitch: 0
});
};
document.querySelector('.btn3').onclick = function () {
marker.pauseMove();
};
document.querySelector('.btn4').onclick = function () {
marker.resumeMove();
};
document.querySelector('.btn5').onclick = function () {
// 限制小车移动距离,0||NAN 不限制
if (isMoving) return;
var value = parseFloat(document.querySelector('input').value);
value ? (limitDistance = value) : (limitDistance = 0);
document.querySelectorAll('.info p')[1].innerHTML =
'当前限制小车最大移动距离:' + limitDistance + '米';
};
</script>
</body>
</html>
FILE:references/jsapigl/demos/地图操作示例_卫星地图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>卫星地图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
zoom:12,//设置地图缩放级别
center: center,//设置地图中心点坐标
baseMap: { // 设置卫星地图
type: 'satellite'
}
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/折线_折线-动态箭头.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>折线-动态箭头</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(39.98183908712013, 116.31021738052368);
//初始化地图
var map = new TMap.Map("container", {
zoom: 16,
center: center
});
var paths = [
new TMap.LatLng(39.98481500648338, 116.30571126937866),
new TMap.LatLng(39.982266575222155, 116.30596876144409),
new TMap.LatLng(39.982348784165886, 116.3111400604248),
new TMap.LatLng(39.978813710266024, 116.3111400604248),
new TMap.LatLng(39.978813710266024, 116.31699800491333)
];
var polylineLayer = new TMap.MultiPolyline({
map, // 绘制到目标地图
styles: { // 折线样式定义
'arrow_animation': new TMap.PolylineStyle({
'color': 'rgba(255,0,0,0.6)', //线填充色
'borderWidth': 2, //边线宽度
'borderColor': '#008FFF', //边线颜色
'width': 10, //折线宽度
showArrow: true,
arrowOptions: {
width: 8, // 箭头图标宽度
height: 5, // 箭头图标高度
space: 50, // 箭头图标之间的孔隙长度
animSpeed: 50 // 箭头动态移动的速度 单位(像素/秒)
},
})
},
geometries: [{
styleId: 'arrow_animation',
paths: paths
}],
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_公交路线规划.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>公交路线规划</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=您的key&libraries=service"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<script type="text/javascript">
var map;
var startPosition = new TMap.LatLng(40.04134911365523, 116.27137911901514); // 路线规划起点
var endPosition = new TMap.LatLng(39.94174786983313, 116.353209380218); // 路线规划终点
function initMap() {
map = new TMap.Map("container", {
zoom: 13,
center: new TMap.LatLng(39.987683461261014, 116.27571820516232),
});
var marker = new TMap.MultiMarker({ // 创造MultiMarker显示起终点标记
id: 'marker-layer',
map: map,
styles: {
start: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/start.png',
}),
end: new TMap.MarkerStyle({
width: 25,
height: 35,
anchor: { x: 16, y: 32 },
src: 'https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/end.png',
}),
},
geometries: [
{
id: 'start',
styleId: 'start',
position: startPosition,
},
{
id: 'end',
styleId: 'end',
position: endPosition,
},
],
});
var polylineLayer = new TMap.MultiPolyline({ // 创建 MultiPolyline显示路径折线
map: map,
styles: {
'style_blue': new TMap.PolylineStyle({
color: '#3777FF',
width: 10,
borderWidth: 0,
showArrow: true,
arrowOptions: {
space: 70
},
lineCap: 'round',
}),
},
geometries:[],
});
var transit = new TMap.service.Transit({ // 新建一个公交路线规划类
policy:"LEAST_WALKING", // 规划策略
});
transit.search({from:startPosition, to:endPosition}).then((result)=>{ //搜索路径
var route = result.result.routes[0];
var instructionContainer = document.getElementById('instruction');
var rainbowPaths = [];
route.steps.forEach((step)=>{
if(step.mode === 'WALKING') {
rainbowPaths.push({path: step.polyline, color: 'rgba(51, 51, 255, 1)',}); //绘制步行路线
instructionContainer.innerHTML += `<h5>步行(step.duration分钟)</h5>`;
step.steps && step.steps.forEach((subStep, index)=>{
instructionContainer.innerHTML += `<p>index+1. subStep.instruction</p>` // 显示步行指引
});
}
else {
step.lines.forEach((line)=>{
if(line.vehicle === 'SUBWAY') {
rainbowPaths.push({path: line.polyline, color: 'rgba(245, 185, 23, 1)'}); //绘制地铁路线
} else {
rainbowPaths.push({path: line.polyline, color: 'rgba(28, 204, 108, 1)'}); //绘制非地铁公交路线
}
instructionContainer.innerHTML += `<h5>搭乘line.title(line.duration分钟)</h5>`; // 显示搭乘指引
instructionContainer.innerHTML += `<p>上车站:line.geton.title</p>`;
instructionContainer.innerHTML += `<p>下车站:line.getoff.title</p>`;
});
}
});
polylineLayer.updateGeometries([{
styleId: 'style_blue',
rainbowPaths: rainbowPaths,
}])
});
}
</script>
<body onload="initMap()">
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<h4>公交路线规划</h4><div id="instruction"></div></div>
</body>
</html>
FILE:references/jsapigl/demos/模型库_使用Three.js加载模型.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>GLCustomLayer-加载gltf模型</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=model"></script>
<script src="https://mapapi.qq.com/web/jsapiGL/three-138/[email protected]"></script>
<script src="https://mapapi.qq.com/web/jsapiGL/three-138/GLTFLoader.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px
}
#container {
width: 100%;
height: 100%;
}
</style>
<body>
<div id="container"></div>
<div id="info"></div>
<script>
const styles = {
//设置区域图样式
styel1: {
fillColor: "#7DF4FF", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel2: {
fillColor: "#17D5DC", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel3: {
fillColor: "#0EB2E7", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel4: {
fillColor: "#0896EF", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel5: {
fillColor: "#017CF7", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
};
const center = new TMap.LatLng(39.994104, 116.307503);
// 初始化地图
var map = new TMap.Map('container', {
center: center,
zoom: 19,
pitch: 50,
rotation: -20,
});
const coord = map.glLatLngToPosition(center);
let renderer, camera, scene, group, raycaster;
let mapCamera; // 地图相机
var glCustomLayer = new TMap.GLCustomLayer(
{
id: 'glCustomLayer',
map,
keepFps: false, // 是否保持恒定帧率渲染,当渲染存在动画时设置为true
// visible: false,
init: (gl) => {
initThree(gl);
loadGltf(); // 加载gltf模型
// 需将构建的Three相关项返回
return { renderer, camera, scene, group };
},
render: () => {
renderer.render(scene, camera);
}
}
);
function initThree(gl) {
renderer = new THREE.WebGLRenderer({
context: gl,
antialias: true
});
renderer.autoClear = false;
renderer.outputEncoding = THREE.sRGBEncoding;
mapCamera = map.getCamera(); // 获取地图相机
const { fovy, view, near, far, distance } = mapCamera;
const aspect = (view.right - view.left) / (view.top - view.bottom);
camera = new THREE.PerspectiveCamera(fovy, aspect, near, far); // 创建threejs中的相机
camera.position.z = distance;
scene = new THREE.Scene()
group = new THREE.Group(); // 涉及内部坐标转换,所有新增THREE.Object3D需添加入group
// 添加环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 1.0);
group.add(ambientLight);
scene.add(group);
raycaster = new THREE.Raycaster();
}
function degreeToRadian(deg) {
return deg * (Math.PI / 180);
};
function loadGltf() {
loader = new THREE.GLTFLoader();
loader.load('https://mapapi.qq.com/web/visualization/demo-asset/airplane.glb', function (gltf) {
gltf.scene.scale.set(20, 20, 30); // 模型在XYZ三轴上的缩放比例
gltf.scene.position.set(coord.x, coord.y, coord.z);
const rotation = [0,-90,0]; // 模型XYZ三轴上的旋转角度
const euler = rotation.map((angle, index) => {
return !!index ? degreeToRadian(angle) : degreeToRadian(angle + 90);
});
gltf.scene.rotation.set(...euler);
group.add(gltf.scene);
}, undefined, function (error) {
console.error(error);
});
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/城市漫游_空中漫游.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>空中漫游</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=view"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
.info {
width: 300px;
background-color: white;
padding: 10px;
font-size: 14px;
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
</style>
<body>
<div class="info">
<p></p>
<p></p>
<p></p>
<p></p>
</div>
<div id="container"></div>
<script type="text/javascript">
var center = new TMap.LatLng(40.04549968987281, 116.30132245232517); // 设置地图中心点
// 初始化地图
var map = new TMap.Map('container', {
center: center,
zoom: 17, // 地图缩放
});
var lookFromList = [
{
position: new TMap.LatLng(40.04549968987281, 116.30132245232517, 2000),
rotation: [30, 0, 0], // 地图在水平面上的旋转角度
percentage: 0, // 动画过程中该关键帧的百分比
},
{
position: new TMap.LatLng(40.05244881434931, 116.29810240088455, 2000),
rotation: [30, 0, 0],
percentage: 0.15,
},
{
position: new TMap.LatLng(40.05388372230792, 116.29581192862429, 2000),
rotation: [30, 0, 90],
percentage: 0.2,
},
{
position: new TMap.LatLng(40.04940045681701, 116.2727587248047, 2000),
rotation: [30, 0, 90],
percentage: 0.45,
pitch: 180,
},
{
position: new TMap.LatLng(40.04786914340286, 116.27002196548278, 2000),
rotation: [30, 0, 180],
percentage: 0.5,
},
{
position: new TMap.LatLng(40.040462003575115, 116.27165202999174, 2000),
rotation: [30, 0, 180],
percentage: 0.65,
},
{
position: new TMap.LatLng(40.03864934630857, 116.27347417456406, 2000),
rotation: [30, 0, -90],
percentage: 0.7,
},
{
position: new TMap.LatLng(40.04274037584355, 116.29890777376886, 2000),
rotation: [30, 0, -90],
percentage: 0.95,
},
{
position: new TMap.LatLng(40.04549968987281, 116.30132245232517, 2000),
rotation: [30, 0, 0],
percentage: 1,
},
];
var keyFrames = lookFromList.map(function (lookFrom) {
var position = lookFrom.position;
var rotation = lookFrom.rotation;
var percentage = lookFrom.percentage;
// 地图视角附加库文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/glMapView
// getMapViewWhenLookFrom指定观察者所在位置及三轴方向上的旋转角度。
// position为观察者位置,需明确高度;rotation为XYZ三轴上的旋转角度,格式为[x, y, z],默认为[0, 0, 0]。
var keyFrame = map.getMapViewWhenLookFrom(position, rotation);
keyFrame.percentage = percentage;
return keyFrame;
});
// map.startAnimation 文档地址:https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap
// 开始动画,通过keyFrames定义关键帧
map.startAnimation(keyFrames, {
duration: 60 * 1000, // 动画周期时长,单位为ms
loop: Infinity, // 动画周期循环次数,若为Infinity则无限循环,默认为1
});
var infoList = document.querySelectorAll('.info p');
map.on('animation_playing', function (event) {
var lat = event.frame.center.lat;
var lng = event.frame.center.lng;
var pitch = event.frame.pitch;
var zoom = event.frame.zoom;
var rotation = event.frame.rotation;
infoList[0].innerText = '当前地图中心点:' + lat.toFixed(6) + ',' + lng.toFixed(6);
infoList[1].innerText = '当前地图俯仰角角度:' + Math.round(pitch);
infoList[2].innerText = '当前地图缩放等级:' + Math.round(zoom);
infoList[3].innerText = '当前地图旋转角度:' + Math.round(rotation);
});
</script>
</body>
</html>
FILE:references/jsapigl/demos/服务类库_坐标至地址转换.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>坐标至地址转换</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&libraries=service&key=您的key"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<body>
<div id="container"></div>
<div id="panel">
<p>请在右侧示例代码第10行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
<p><label>经纬度</label><input id='location' type="text" value='39.982915, 116.307015' ><input id="convert" type="button" class="btn" value="转换为地址" onclick="convert()" /></p>
<p><label>地址</label><input id='address' type='text' disabled value='' /></p>
</div>
</body>
<script type="text/javascript">
var map = new TMap.Map('container', {
zoom: 14,
center: new TMap.LatLng(39.986785, 116.301012),
});
var geocoder = new TMap.service.Geocoder(); // 新建一个正逆地址解析类
var markers = new TMap.MultiMarker({
map: map,
geometries: [],
});
function convert() {
markers.setGeometries([]);
var input = document.getElementById('location').value.split(',');
var location = new TMap.LatLng(Number(input[0]), Number(input[1]));
map.setCenter(location);
markers.updateGeometries([
{
id: 'main', // 点标注数据数组
position: location,
},
]);
geocoder
.getAddress({ location: location }) // 将给定的坐标位置转换为地址
.then((result) => {
document.getElementById('address').value = result.result.address;
// 显示搜索到的地址
});
}
</script>
</html>
FILE:references/jsapigl/demos/几何计算库_判断多边形是否在另一个多边形内.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>判断多边形是否在另一个多边形内</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
overflow: hidden;
}
#container {
position: relative;
width: 100%;
height: 100%;
}
#info {
position: absolute;
left: 0;
right: 0;
margin: auto;
bottom: 60px;
height: 100px;
width: 420px;
line-height: 33px;
background-color: #fff;
border-radius: 10px;
font-size: 18px;
text-align: center;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script type="text/javascript">
var map;
function initMap() {
var center = new TMap.LatLng(40.040452, 116.273486);//设置中心点坐标
//初始化地图
map = new TMap.Map('container', {
center: center,
zoom: 17,
baseMap: {
type: 'vector',
features: ['base', 'building3d', 'label'],
}
});
var path0 = [
new TMap.LatLng(40.041117253378246, 116.2722415837743),
new TMap.LatLng(40.03942536171407, 116.2726277820093),
new TMap.LatLng(40.03970460886076, 116.27483769345417),
new TMap.LatLng(40.041404706498625, 116.27443003983899),
];
var path1 = [
new TMap.LatLng(40.04073540132186, 116.27298706942383),
new TMap.LatLng(40.04002894945164, 116.27307290694716),
new TMap.LatLng(40.04021788498498, 116.27441411815198),
new TMap.LatLng(40.04085861888776, 116.27390982274846)
];
var path2 = [
new TMap.LatLng(40.04329006723823, 116.2720643160992),
new TMap.LatLng(40.042181140299455, 116.27236474741039),
new TMap.LatLng(40.042320784222156, 116.27390982274846),
new TMap.LatLng(40.0434214942257, 116.27365231017848)
];
var path3 = [
new TMap.LatLng(40.039692150139594, 116.27183899269767),
new TMap.LatLng(40.03990573041772, 116.27289050220497),
new TMap.LatLng(40.03903497572191, 116.27313728512036),
new TMap.LatLng(40.03898568742912, 116.2719033706253)
]
var position1 = new TMap.LatLng(40.04202067344373, 116.2706967910799);
var position2 = new TMap.LatLng(40.040443786954235, 116.2736683713456);
var polygon = new TMap.MultiPolygon({
id: 'polygon-layer', //图层id
map: map,
styles: {
// 多边形的相关样式
base: new TMap.PolygonStyle({
color: 'rgba(255,255,255,0.9)', // 面填充色
}),
red: new TMap.PolygonStyle({
color: 'rgba(255,0,0,0.5)', // 面填充色
}),
green: new TMap.PolygonStyle({
color: 'rgba(0,255,0,0.5)', // 面填充色
}),
blue: new TMap.PolygonStyle({
color: 'rgba(0,0,255,0.5)', // 面填充色
}),
},
geometries: [
{
styleId:'base',
id: 'polygon0',
paths: path0,
},
{
id: 'polygon1',
styleId:'red',
paths: path1,
},
{
id: 'polygon2',
styleId:'green',
paths: path2,
},
{
id: 'polygon3',
styleId:'blue',
paths: path3,
}
]
});
var infoDom = document.getElementById('info');
infoDom.innerText =
`'红色多边形不在白色多边形内'`+'\n'+
`'绿色多边形不在白色多边形内'`+'\n'+
`'蓝色多边形不在白色多边形内'`+'\n';
}
</script>
</body>
</html>
FILE:references/jsapigl/demos/多边形与3D棱柱_多边形选中高亮显示.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>MultiPolygon选中高亮显示</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info {
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">
</div>
<script>
var map;
function initMap() {
var info = document.getElementById("info");
var center = new TMap.LatLng( 40.04162653200161, 116.27402993329179);
//初始化地图
map = new TMap.Map("container", {
zoom: 16, //设置地图缩放级别
center: center, //设置地图中心点坐标
});
// 多边形数据
const polygonGeometries = [
{
"paths": [
{
"lat": 40.041516,
"lng": 116.271655,
"height": 0
},
{
"lat": 40.044479,
"lng": 116.271063,
"height": 0
},
{
"lat": 40.045154,
"lng": 116.275173,
"height": 0
},
{
"lat": 40.045127,
"lng": 116.275351,
"height": 0
},
{
"lat": 40.045061,
"lng": 116.275474,
"height": 0
},
{
"lat": 40.044007,
"lng": 116.276271,
"height": 0
},
{
"lat": 40.043876,
"lng": 116.276264,
"height": 0
},
{
"lat": 40.043774,
"lng": 116.276204,
"height": 0
},
{
"lat": 40.043697,
"lng": 116.276093,
"height": 0
},
{
"lat": 40.043411,
"lng": 116.274514,
"height": 0
},
{
"lat": 40.043373,
"lng": 116.274436,
"height": 0
},
{
"lat": 40.043313,
"lng": 116.274408,
"height": 0
},
{
"lat": 40.042697,
"lng": 116.274427,
"height": 0
},
{
"lat": 40.042122,
"lng": 116.27462,
"height": 0
},
{
"lat": 40.04204,
"lng": 116.274607,
"height": 0
},
{
"lat": 40.042001,
"lng": 116.274544,
"height": 0
},
{
"lat": 40.041516,
"lng": 116.271655,
"height": 0
},
],
},
{
"paths": [
{
"lat": 40.043774,
"lng": 116.276204,
"height": 0
},
{
"lat": 40.043824,
"lng": 116.276332,
"height": 0
},
{
"lat": 40.04382,
"lng": 116.27648,
"height": 0
},
{
"lat": 40.043489,
"lng": 116.277156,
"height": 0
},
{
"lat": 40.043233,
"lng": 116.277385,
"height": 0
},
{
"lat": 40.042953,
"lng": 116.277482,
"height": 0
},
{
"lat": 40.04255,
"lng": 116.277556,
"height": 0
},
{
"lat": 40.042484,
"lng": 116.277524,
"height": 0
},
{
"lat": 40.042454,
"lng": 116.277449,
"height": 0
},
{
"lat": 40.042051,
"lng": 116.274762,
"height": 0
},
{
"lat": 40.042063,
"lng": 116.274677,
"height": 0
},
{
"lat": 40.042122,
"lng": 116.27462,
"height": 0
},
{
"lat": 40.042697,
"lng": 116.274427,
"height": 0
},
{
"lat": 40.043313,
"lng": 116.274408,
"height": 0
},
{
"lat": 40.043373,
"lng": 116.274436,
"height": 0
},
{
"lat": 40.043411,
"lng": 116.274514,
"height": 0
},
{
"lat": 40.043697,
"lng": 116.276093,
"height": 0
},
{
"lat": 40.043774,
"lng": 116.276204,
"height": 0
},
],
},
{
"paths": [
{
"lat": 40.041904,
"lng": 116.274781,
"height": 0
},
{
"lat": 40.041939,
"lng": 116.274843,
"height": 0
},
{
"lat": 40.04233,
"lng": 116.27752,
"height": 0
},
{
"lat": 40.04231,
"lng": 116.277595,
"height": 0
},
{
"lat": 40.042262,
"lng": 116.277644,
"height": 0
},
{
"lat": 40.041085,
"lng": 116.277896,
"height": 0
},
{
"lat": 40.041028,
"lng": 116.277879,
"height": 0
},
{
"lat": 40.040999,
"lng": 116.277816,
"height": 0
},
{
"lat": 40.040641,
"lng": 116.275151,
"height": 0
},
{
"lat": 40.040655,
"lng": 116.275076,
"height": 0
},
{
"lat": 40.040701,
"lng": 116.275027,
"height": 0
},
{
"lat": 40.041847,
"lng": 116.274757,
"height": 0
},
{
"lat": 40.041904,
"lng": 116.274781,
"height": 0
},
],
},
{
"paths": [
{
"lat": 40.041231,
"lng": 116.271712,
"height": 0
},
{
"lat": 40.0413,
"lng": 116.271761,
"height": 0
},
{
"lat": 40.041347,
"lng": 116.271869,
"height": 0
},
{
"lat": 40.041821,
"lng": 116.27429,
"height": 0
},
{
"lat": 40.041793,
"lng": 116.274441,
"height": 0
},
{
"lat": 40.041701,
"lng": 116.274535,
"height": 0
},
{
"lat": 40.040443,
"lng": 116.274853,
"height": 0
},
{
"lat": 40.040906,
"lng": 116.27805,
"height": 0
},
{
"lat": 40.039506,
"lng": 116.278362,
"height": 0
},
{
"lat": 40.03863,
"lng": 116.272593,
"height": 0
},
{
"lat": 40.03869,
"lng": 116.27238,
"height": 0
},
{
"lat": 40.038957,
"lng": 116.272167,
"height": 0
},
{
"lat": 40.041231,
"lng": 116.271712,
"height": 0
},
],
},
]
let polygonLayer = new TMap.MultiPolygon({
map,
geometries: polygonGeometries,
})
// 高亮选中图层
var highlightLayer = new TMap.MultiPolyline({
map,
zIndex: 1,
styles: {
highlight: new TMap.PolylineStyle({
'color': 'rgba(188,200,0,0.8)', //填充色
'width': 4, // 线宽
'borderColor': '#fff', //边线颜色
'borderWidth': 1 //边线宽度
})
}
});
// 高亮区域
let highlightGeometry = {
id: 'highlightGeo',
styleId: 'highlight'
}
polygonLayer.on('hover', e => {
if (e.geometry) {
// 鼠标选中时高亮区域覆盖
highlightGeometry.paths = e.geometry.paths;
highlightLayer.updateGeometries([highlightGeometry]);
} else {
// 鼠标移出时取消高亮区域覆盖
highlightLayer.setGeometries([]);
}
})
}
</script>
</body>
</html>
FILE:references/jsapigl/docs/基础类.md
## LatLng {#1}
----
用于创建经纬度坐标实例。
| 构造函数 |
| :- |
|TMap.LatLng(lat:Number, lng:Number, height: Number)|
| 参数说明 | 类型 | 说明 |
| :- | :- | :- |
|lat |Number |纬度值。|
|lng |Number |经度值。|
| height |Number |高度值,非必填,单位为米,默认为0。|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|getLat() |Number |获取纬度值。|
|getLng() |Number |返回经度值。|
|getHeight() |Number |获取高度值。|
</br></br>
## LatLngBounds {#2}
----
描述一个矩形的地理坐标范围。
| 构造函数 |
| :- |
|TMap.LatLngBounds(sw:LatLng, ne:LatLng)|
| 参数说明 | 类型 | 说明 |
| :- | :- | :- |
|sw |LatLng |西南角位置经纬度。|
|ne |LatLng |东北角位置经纬度。|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|getCenter() |LatLng |获取该范围的中心点坐标。|
|getNorthEast() |LatLng |获取该范围的东北角坐标。|
|getSouthWest() |LatLng |获取该范围的西南角坐标。|
|extend(latlng:LatLng) |this |扩展该范围边界,以包含指定的坐标点。|
|union(other:LatLngBounds) |this |扩展该范围边界,以包含指定的一个矩形范围。|
|equals(other:LatLngBounds) |Boolean |比较两个矩形范围是否完全相等。|
|intersects(other:LatLngBounds) |Boolean |判断该范围是否与另一矩形范围相交。|
|isEmpty() |Boolean |判断该范围是否为空。|
|contains(latlng:LatLng) |Boolean |判断指定的坐标是否在这个范围内。|
|toString() |String |转换为字符串表示。|
</br></br>
## Point {#3}
----
用于创建二维坐标点。
| 构造函数 |
| :- |
|new TMap.Point(x, y);|
| 参数说明 | 类型 | 说明 |
| :- | :- | :- |
|x |Number |x坐标值|
|y |Number |y坐标值|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|getX() |Number |获取x坐标值|
|getY() |Number |获取y坐标值|
|equals(other: Point) |Boolean |判断两个点是否相等|
|clone() |Point |创建一个坐标值相同的点|
|toString() |String |转换为字符串表示|
</br></br>
## GradientColor {#4}
----
用于创建渐变色实例。
| 构造函数 |
| :- |
|new TMap.GradientColor(options);|
| 参数说明 | 类型 | 说明 |
| :- | :- | :- |
|options |GradientColorOptions |渐变色配置参数。|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|addColorStop(offset: Number, color: String) |this |添加一个由偏移(offset)和颜色(color)定义的断点,offset取值范围为0~1,颜色支持rgb(), rgba(),#RRGGBB格式。|
|setAngle(angle: Number) |this |设置水平线和渐变线之间的角度,逆时针方向旋转,0度从左到右,90度从下到上。|
|getAngle() |Number |获取水平线和渐变线之间的角度。|
| 静态方法 | 返回值 | 说明 |
| :- | :- |:- |
|createDoubleColorGradient(color1: String, color2: String, angle: Number) |GradientColor |创建双色渐变色,color1为起点颜色,color2为终点颜色,颜色支持rgb(), rgba(),#RRGGBB格式,angle为水平线和渐变线之间的角度,逆时针方向旋转,0度从左到右,90度从下到上,默认值0度。|
|convert(colors: Object) |GradientColor |将字面量对象和双色值数组,比如{0:'#FFFFFF', 1:'#000000'}或['#FFFFFF', '#000000']转换为GradientColor对象,颜色支持rgb(), rgba(),#RRGGBB格式,只支持双色,第一个颜色为起点颜色,默认offset为0,第二个颜色为终点颜色,默认offset为1,angle默认为0。|
</br></br>
## GradientColorOptions {#5}
----
GradientColor 配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|stops |Object |渐变色中的断点集合,key-value形式。key为偏移(offset),value为颜色(color),比如{0:'#FFFFFF', 1:'#000000'}。|
|angle |Number |水平线和渐变线之间的角度,逆时针方向旋转,0度从左到右,90度从下到上,默认值0度。|
FILE:references/jsapigl/docs/地图.md
## Map {#1}
----
API中的核心类,用于创建地图实例。
| 创建Map类的语法 | 参数 |
| :- | :- |
| new {#606}(TMap.Map)(domId, options); | {#007000}(domId) : {{#007000}(string)}</br> (必填) 地图DOM容器的id,创建地图需要在页面中创建一个空div元素,传入该div元素的id</br>{#007000}(options) : {{#007000}(MapOptions)}</br> 地图参数,对象规范详见 MapOptions |
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setCenter(center:LatLng)|this |设置地图中心点。|
|setZoom(zoom:Number)|this |设置地图缩放级别。|
|setRotation(rotation:Number)|this |设置地图水平面上的旋转角度。|
|setPitch(pitch:Number) |this |设置地图俯仰角。|
|setScale(scale:Number) |this |设置地图显示比例。|
|setOffset(offset: Object) |this |设置地图与容器偏移量,Object的格式为{x:Number, y:Number},x方向向右偏移为正值,y方向向下偏移为正值;**中心点偏移功能不支持与MVTLayer共同使用**。|
| setCursorStyle(style:String) | this | 设置地图鼠标样式,需要符合css cursor规范 |
|setDraggable(draggable:Boolean) |this |设置地图是否支持拖拽。|
|setScrollable(scrollable:Boolean) |this |设置地图是否支持滚轮缩放。|
| setMaxZoom(maxZoom:Number) |this |设置地图最大缩放级别,支持3~20。|
| setMinZoom(minZoom:Number) |this |设置地图最小缩放级别,支持3~20。|
| setPitchable(pitchable:Boolean) |this |设置地图是否支持改变俯仰角度。在2D视图下,此方法无效。|
| setRotatable(rotatable:Boolean) |this |设置地图是否支持改变旋转角度。在2D视图下,此方法无效。|
| setTouchZoomable(touchZoomable:Boolean) |this |设置地图是否支持手势缩放。|
|setDoubleClickZoom(doubleClickZoom: Boolean) |this |设置地图是否支持双击缩放。|
|setBoundary(boundary:LatLngBounds) |this |设置地图限制边界,拖拽、缩放等操作无法将地图移动至边界外。|
| setSkyOptions(options: SkyOptions ) | this | 设置地图天空背景 |
|setViewMode(viewMode: String) |this |设置地图视图模式。|
|setBaseMap(baseMap:BaseMap I BaseMap[]) |this |动态设置地图底图,BaseMap目前支持矢量底图(VectorBaseMap)、卫星底图(SatelliteBaseMap)、路况底图(TrafficBaseMap),可以使用数组形式实现多种底图叠加。|
|setMapStyleId(mapStyleId: String) |this |动态设置个性化地图样式。|
|panTo(latLng:LatLng, opts:EaseOptions) |this |将地图中心平滑移动到指定的经纬度坐标。|
|zoomTo(zoom:Number, opts:EaseOptions) |this |平滑缩放到指定级别。|
|rotateTo(rotation:Number, opts:EaseOptions |this |平滑旋转到指定角度。|
|pitchTo(pitch:Number, opts:EaseOptions) |this |平滑变化到指定俯仰角度。|
|easeTo(mapStatus:Object, opts:EaseOptions) |this |平滑过渡到指定状态,mapStatus为key-value格式,可以设定center、zoom、rotation、pitch。|
|fitBounds(bounds:LatLngBounds,options:FitBoundsOptions) |this |根据指定的地理范围调整地图视野。|
|getCenter() |LatLng |获取地图中心。|
|getZoom() |Number |获取地图缩放级别。|
|getRotation() |Number |获取地图水平面上的旋转角度。|
|getPitch() |Number |获取地图俯仰角度。|
|getBounds() |LatLngBounds |返回当前地图的视野范围,该视野范围实际会大于等于地图的可视区域范围,尤其是当地图发生旋转和俯仰变化时,得到的是一个当前视野范围的最小外包矩形。|
|getScale() |Number |获取地图显示比例。|
|getOffset() |Object |获取地图与容器的偏移量Object的格式为 {x:Number, y:Number},x方向向右偏移为正值,y方向向下偏移为正值。|
| getCursorStyle() | String | 获取地图鼠标样式 |
|getDraggable() |Boolean |获取地图是否支持拖拽。|
|getScrollable() |Boolean |获取地图是否支持滚轮缩放。|
| getTouchZoomable() |Boolean |获取地图是否支持手势缩放。|
|getDoubleClickZoom() |Boolean |获取地图是否支持双击缩放。|
|getBoundary() |LatLngBounds |获取地图限制边界。|
|addControl(control:Control) |this |添加控件到地图,传入控件对象。|
|removeControl(id: String) |this |从地图容器移出控件,默认控件的id列表参考 DEFAULT_CONTROL_ID|
|getControl(id: String) |Control |根据控件id获取对应的控件对象,默认控件的id列表参考 DEFAULT_CONTROL_ID。|
|getViewMode() |String |获取地图视图模式。|
|getBaseMap() |BaseMap I BaseMap[] |获取当前的底图类型。|
|getIndoorManager() |IndoorManager |获取室内地图管理器。|
|destroy() | | 销毁地图。|
|projectToContainer(latLng: LatLng) |Point |经纬度坐标转换为容器像素坐标,容器像素坐标系以地图容器左上角点为原点。|
|unprojectFromContainer(pixel: Point) |LatLng |容器像素坐标转换为经纬度坐标。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| once(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中,一次性监听,listener执行一次后自动移除监听器|
| moveLayer(layerId: String, level: LAYER_LEVEL) | this | 修改图层层级顺序,根据输入 LAYER_LEVEL 常量调整 layerId 对应图层的渲染层级 ,其中layerId可以通过图层getId方法获取。注: 设置ZIndex 可调整同一大类层级下的不同图层顺序,此方法则是调整目标图层的大类层级。
| startAnimation(keyFrames: MapKeyFrame[], opts: AnimationOptions) | none | 开始动画,通过keyFrames定义关键帧 ,查看示例 |
| stopAnimation() | none | 停止动画,停止后无法通过resumeAnimation恢复 |
| pauseAnimation() | none | 暂停动画 |
| resumeAnimation() | none | 恢复动画 |
| enableAreaHighlight(opts: highlightOptions) | this | 启用地图区域高亮功能, 查看示例 |
| disableAreaHighlight() | this | 禁用地图区域高亮功能 |
| enableAreaClip(opts: ClipOptions) | this | 启用地图区域掩膜功能 |
| disableAreaClip() | this | 停用地图区域掩膜功能 |
**事件:**</br>
监听事件通过on、off方法绑定与解绑。 查看示例
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|idle |none |地图进入空闲状态时触发。|
|tilesloaded |none |当地图容器中可见的瓦片加载完后会触发此事件。|
|click |evt:MapEvent |鼠标左键单击地图时触发,移动与桌面web都触发。|
|rightclick |evt:MapEvent |鼠标右键单击地图时触发,只在桌面浏览器触发。|
|dblclick |evt:MapEvent |鼠标左键双击地图时触发,移动与桌面web都触发。|
|mousedown |evt:MapEvent |鼠标在地图区域中按下时触发,只在桌面浏览器中触发。|
|mouseup |evt:MapEvent |鼠标在地图区域中按下又弹起时触发,只在桌面浏览器中触发。|
|mousemove |evt:MapEvent |鼠标在地图上移动时触发,只在桌面浏览器中触发。|
|touchstart |evt:MapEvent |在地图区域触摸开始时触发,只在移动浏览器中触发。|
|touchmove |evt:MapEvent |在地图区域触摸移动时触发,只在移动浏览器中触发。|
|touchend |evt:MapEvent |在地图区域触摸结束时触发,只在移动浏览器中触发。|
|dragstart |none |地图开始发生拖拽交互时触发。|
|drag |none |地图发生拖拽交互时触发,拖拽交互可能产生pan,rotate, picth事件。|
|dragend |none |地图拖拽交互结束时触发。|
|panstart |none |地图开始平移时触发。|
|pan |none |地图移动时触发。|
|panend |none |地图平移结束时触发。|
|rotatestart |none |地图开始旋转时触发。|
|rotate |none |地图旋转时触发。|
|rotateend |none |地图旋转结束时触发。|
|pitchstart |none |地图开始发生俯仰角变化时触发。|
|pitch |none |地图俯仰角变化时触发。|
|pitchend |none |地图俯仰角变化结束时触发。|
|zoom |none |地图缩放时触发。|
|resize |none |地图容器大小发生变化时触发。|
|center_changed |none |当地图中心点变化时会触发此事件。|
|bounds_changed |none |当可视区域范围更改时会触发此事件。|
|scale_changed |none |地图显示比例变化时触发此事件。|
|control_added |none |给map添加控件的时候触发此事件。|
|control_removed |none |从map移出控件的时候触发此事件。|
| animation_playing | AnimationEvent | 动画进行中 |
| animation_looped | Number | 动画进入下一循环,返回循环序号 |
| animation_ended | none | 动画结束 |
| animation_stopped | none | 动画停止 |
| context_lost | | 地图渲染上下文丢失时触发此事件;地图渲染依赖浏览器硬件加速(GPU),浏览器对硬件加速的使用存在限制,当整个浏览器使用硬件加速的元素过多时,未防止整个GPU崩溃导致设备黑屏,浏览器会自动会将之前元素使用的硬件资源回收,导致该元素无法继续渲染(浏览器丢车保帅的策略);当触发此事件时,需要检查业务代码是否使用了过多的硬件加速资源(如创建太多**WebGLRenderingContext** 、 **WebGL2RenderingContext** 、 **GPUCanvasContext** ) |
| destroyed | | 地图被完全销毁时触发此事件 |
**示例1:**</br>
本示例中,介绍如何在div容器中创建地图
html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>简单地图</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px
}
#container {
width: 100%;
height: 100%
}
</style>
</head>
<body onload="initMap()">
<div id="container"></div>
<script type="text/javascript">
function initMap() {
var container = document.getElementById("container");
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map(container, {
rotation: 20,//设置地图旋转角度
pitch:30, //设置俯仰角度(0~45)
zoom:12,//设置地图缩放级别
center: center//设置地图中心点坐标
});
}
</script>
</body>
</html>
新窗口打开 在线试一试
**示例2:**</br>
本示例中,介绍如何异步加载api展现地图
html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>异步加载地图</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px
}
#container {
width: 100%;
height: 100%
}
</style>
</head>
<body>
<div id="container"></div>
<script>
function initMap() {
var map = new TMap.Map("container", {
pitch:45,
zoom:12,
center: new TMap.LatLng(39.984104, 116.307503)
});
}
function loadScript() {
//创建script标签,并设置src属性添加到body中
var script = document.createElement("script");
script.type = "text/javascript";
script.src = "https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&callback=initMap";
document.body.appendChild(script);
}
window.onload = loadScript;
</script>
</body>
</html>
新窗口打开 在线试一试
</br></br>
## MapOptions {#2}
----
地图配置参数,通过这个参数来控制初始化地图的中心点、缩放级别、俯仰角度等。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|center |LatLng |地图中心点经纬度。|
|zoom |Number |地图缩放级别,支持3~22。|
|minZoom |Number |地图最小缩放级别,默认为3。|
|maxZoom |Number |地图最大缩放级别,默认为20。|
|rotation |Number |地图在水平面上的旋转角度,顺时针方向为正,默认为0。|
|pitch |Number |地图俯仰角度,取值范围为0~80,默认为0。|
|scale |Number |地图显示比例,默认为1。|
|offset |Object |地图中心与容器的偏移量,Object的格式为 {x:Number, y:Number}(右方下方为正,单位为像素);**中心点偏移功能不支持与MVTLayer共同使用**。|
| cursor | String | 地图鼠标样式,需要符合css cursor属性规范,默认为'default' |
|draggable |Boolean |是否支持拖拽移动地图,默认为true。|
|scrollable |Boolean |是否支持鼠标滚轮缩放地图,默认为true。|
| touchZoomable |Boolean |是否允许手势捏合缩放;默认为true。|
|pitchable |Boolean |是否允许设置俯仰角度;默认为true。在2D视图下,此属性无效。|
|rotatable |Boolean |是否允许设置旋转角度;默认为true。在2D视图下,此属性无效。|
|doubleClickZoom |Boolean |是否支持双击缩放地图,默认为true。|
|mapZoomType |MAP_ZOOM_TYPE |地图缩放焦点控制。|
|boundary |LatLngBounds |地图边界,设置后拖拽、缩放等操作无法将地图移动至边界外,默认为null。|
|mapStyleId |String |地图样式ID,有效值为”style[编号]”,与key绑定,详见 个性化地图配置页面。|
|baseMap |BaseMap I BaseMap[] |地图底图,BaseMap目前只支持矢量底图 (VectorBaseMap) 、卫星底图 (SatelliteBaseMap) 、路况底图 (TrafficBaseMap) ,可以使用数组形式实现多种底图叠加。默认为 VectorBaseMap ,如果传入null地图不显示任何地物。|
|viewMode |String |地图视图模式,支持2D和3D,默认为3D。2D模式下不可对地图进行拖拽旋转,pitch和rotation始终保持为0。|
|showControl |Boolean |是否显示地图上的控件,默认true。|
renderOptions|MapRenderOptions | 地图渲染配置参数
clip |ClipOptions | clip区域掩膜配置参数
</br></br>
## MapRenderOptions {#10}
----
地图渲染配置参数属性
| 名称 | 类型 | 说明 |
| :-| :- | :- |
| <br>preserveDrawingBuffer | <br>Boolean | 保留地图的渲染缓冲,在一些结合开源库需要导出图片的场景下(如 dom-to-image html2canvas),需要设置这个参数为true;默认为false,查看示例
| enableBloom | Boolean | 是否启用泛光效果(请确认浏览器支持WebGL2) |
| fogOptions |fogOptions | 边际雾化设置 |
| skyOptions |SkyOptions | 天空背景设置 |
</br></br>
## SkyOptions 对象规范 {#SkyOptions}
----
天空背景设置,可控制天空颜色、纹理和动态效果。
| 名称 | 类型 | 说明 |
| :-| :- | :- |
| color | String | 天空颜色,若使用纯色天空背景,可通过此参数设置天空颜色,支持#RRGGBB、rgb()、rgba()格式 |
| src | String | 天空纹理,若使用图片作为天空背景,可通过此参数设置天空纹理,可传入图片url或者base64编码。纹理图将头尾相接形成360度环形,跟随地图旋转。</br> 图片支持png、jpeg、jpg格式。 </br>建议图片分辨率 4096 * 700,如果天空出现黑色背景请降低图片分辨率直至图片显示。 |
| width | Number | 纹理宽度,不大于图片真实宽度 |
| height | Number | 纹理高度,不大于图片真实高度 |
| horizontal | IMAGE_DISPLAY | 纹理水平方向呈现方式,默认为IMAGE_DISPLAY.SCALE,拉伸铺满 |
| vertical | IMAGE_DISPLAY | 纹理纵向呈现方式,默认为IMAGE_DISPLAY.ORIGIN,保持原图片高度 |
| brightness | Number | 天空亮度,默认为1 |
| animated | Boolean | 是否开启云雾动画效果,默认为false |
</br></br>
## FogOptions 对象规范 {#fogOptions}
----
雾化参数。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
| color | String | 雾气颜色,支持#RRGGBB和rgb()格式,若不设置则使用矢量地图地面颜色 |
</br></br>
## FitBoundsOptions {#3}
----
地图自适应地理范围配置参数,可控制边距以及限制缩放等级。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|padding |Number I Object |设定的地理范围与可视窗口之间的距离,可以通过{top:Number, bottom:Number, left:Number, right:Number}的格式明确各方向的边距,或仅传入一个数字统一各方向的边距,不可为负数。|
|minZoom |Number |调整视野时的最小缩放等级,默认值且最小值为地图的最小缩放等级。|
|maxZoom |Number |调整视野时的最大缩放等级,默认值且最大值为地图的最大缩放等级。|
|ease |EaseOptions |缓动配置,可设置地图视野变化过程的动画效果。|
</br></br>
## AnimationOptions 对象规范{#animationOptions}
----
动画参数。
| 名称 | 类型 | 说明 |
| -------- | ------ | --------------------------------------------------- |
| duration | Number | 动画周期时长,单位为ms,默认为1000 |
| loop | Number | 动画周期循环次数,若为Infinity则无限循环,默认为1 |
</br></br>
## AnimationEvent 对象规范{#animationEvent}
----
动画事件回调参数规范。
| 名称 | 类型 | 说明 |
| ---------- | ------ | -------------- |
| percentage | Number | 动画进行百分比 |
| frame | Object | 当前帧状态 |
</br></br>
## MapKeyFrame 对象规范{#mapKeyFrame}
----
地图动画关键帧对象。
| 名称 | 类型 | 说明 |
| ---------- | ------ | ----------------------------------------- |
| percentage | Number | 动画过程中该关键帧的百分比,取值范围为0~1 |
| center | LatLng| 地图中心点经纬度 |
| zoom | Number | 地图缩放级别 |
| rotation | Number | 地图在水平面上的旋转角度 |
| pitch | Number | 地图俯仰角度,取值范围为0~80 |
</br></br>
## HighlightOptions 对象规范{#highlightOptions}
地图高亮区域设置。
| 名称 | 类型 | 说明 |
| -------------- | -------- | ------------------------- |
| paths | LatLng[] | 高亮区域轮廓坐标点串 |
| highlightColor | String | 高亮色,区域内地图元素将与该色进行混合,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0, 0, 0, 0) |
| shadeColor | String | 阴影色,区域外地图元素将与该色进行混合,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0, 0, 0, 0.4) |
</br></br>
## ClipOptions 对象规范{#ClipOptions}
地图掩膜区域设置。
| 名称 | 类型 | 说明 |
| :- |:- | :- |
| paths | LatLng[] | 掩膜区域轮廓坐标点串
</br></br>
## EaseOptions {#4}
----
地图缓动变化配置参数,可控制动画时长等。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|duration |Number |缓动动画时长,单位为ms,默认为500。|
</br></br>
## VectorBaseMap {#5}
----
VectorBaseMap 对象规范
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|type |String |必须为vector。|
|features |String[] |矢量底图要素类型,通过控制features可以控制矢量底图中不同类型要素的显示与否,目前支持道路及底面(base)、建筑物(building3d)、建筑物平面(building2d)、poi文字(point)、道路文字(label)、道路箭头(arrow);若features为非数组则默认为全部显示,若features为空数组不显示任何地物。|
| buildingRange | Number[] | [min, max], 用于设置建筑物显示层级区间。**min默认值为16.5**, 最小值不低于14.5, 低于14.5则按照min=14.5 进行建筑渲染。**max 默认值为20**,若地图实例启用了enableExtendZoom则默认值为25。buildingRange仅当features中包含building2d或者building3d时生效。|
</br></br>
## SatelliteBaseMap {#6}
----
SatelliteBaseMap 对象规范
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|type |String |必须为satellite。|
|features |String[] |卫星图要素类型,目前支持卫星影像图(base)、路网图(road);若features为非数组则默认为全部显示,若features为空数组不显示任何地物。|
</br></br>
## TrafficBaseMap {#7}
----
TrafficBaseMap 对象规范
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|type |String |须为traffic。|
|features |String[] |路况图要素类型,目前支持路况线网(base)、流动粒子(flow),默认为只显示路况线网图。|
| filter| String[] | 指定特定类型的道路才显示路况,对线网和粒子图同时生效,可选值高速"highway",快速路 "motorway" ,国道"national",省道"provincial",县道"county",乡道"country",其他道路"other"。默认不传或者传入非数组对象都显示,空数组都所有道路路况都不显示。 |
|opacity |Number |路况线网图的透明度,默认为1。|
| baseColor | String[] | 路况线网图颜色组合,长度为4的数组,0-3位分别对应流畅、缓行、拥堵、极度拥堵,颜色支持rgb(),rgba(),#RRGGBB等形式 |
| baseWidth | number | 路况线网图的宽度,必须是正整数 |
|flowColor |String[] |路况流动粒子颜色组合,长度为4的数组,0-3位分别对应流畅、缓行、拥堵、极度拥堵,颜色支持rgb(),rgba(),#RRGGBB等形式。|
|flowSpeed |Number[] |路况流动粒子速度组合,长度为4的数组,0-3位分别对应流畅、缓行、拥堵、极度拥堵,单位为pixel/s,默认为[80, 30, 10, 5] 。|
</br></br>
## TMap.constants.MAP_ZOOM_TYPE 常量说明{#8}
----
地图缩放焦点常量。
| 常量 | 说明 |
| :- | :- |
|MAP_ZOOM_TYPE.DEFAULT |默认缩放,双指中心位置(移动端),鼠标的光标位置(PC端)|
|MAP_ZOOM_TYPE.CENTER |根据地图中心点缩放(移动端和PC端)|
<br></br>
## TMap.constants.LAYER_LEVEL 常量说明{#9}
----
图层渲染层级常量。
| 常量 | 说明 | 包含的图层类型 |
| :---------------------- | :----------------------------------- | :-------------------------------------------------------- |
| LAYER_LEVEL.UNDERGROUND | 地下层,底图下方,默认无图层在此层级 | 无 |
| LAYER_LEVEL.BASE | 基础底图层,底图平面元素所在层级 | 矢量底图(卫星图、路况图) |
| LAYER_LEVEL.GROUND | 贴地层,用户创建的平面图层所在层级 | ImageTileLayer,ImageGroundLayer、CanvasGroundLayer |
| LAYER_LEVEL.BUILDING | 建筑层 | 矢量底图2D、3D建筑,个性化图层 |
| LAYER_LEVEL.OVERLAY_AA | 需抗锯齿的覆盖物层 | 包括矢量图形中的线、面、体、模型以及通过可视化API创建图层 |
| LAYER_LEVEL.TEXT | 文字层 | 矢量底图(POI文字、道路文字) |
| LAYER_LEVEL.OVERLAY_NAA | 不需抗锯齿的覆盖物层 | MultiMarker、MultiLabel |
</br></br>
## TMap.constants.IMAGE_DISPLAY 常量说明 {#IMAGE_DISPLAY}
----
| 常量 | 说明 |
| -- | -- |
| IMAGE_DISPLAY.ORIGIN | 保持图片原状态,不改变图片大小 |
| IMAGE_DISPLAY.SCALE | 拉伸图片以适应显示区域 |
| IMAGE_DISPLAY.REPEAT | 重复图片以铺满显示区域 |
FILE:references/jsapigl/docs/附加库:地图工具.md
## GeometryEditor {#1}
----
几何图形编辑器。
|编辑器交互操作|说明|
| :- | :- |
|绘制|鼠标左键点击及移动即可绘制图形|
|结束绘制|鼠标左键双击即可结束绘制折线、多边形、圆形,多边形会自动闭合|
|单选|鼠标左键点击图形|
|多选|按下ctrl键后点击多个图形|
|删除|选中图形后按下delete键可删除图形|
|拆分|选中单个多边形后可绘制拆分线,拆分线绘制完成后自动进行拆分|
|中断|绘制或编辑过程中按下esc键可中断该过程|
|构造函数|
| :- |
|new TMap.tools.GeometryEditor(options)|
|参数名称|类型|说明|
| :- | :- |
|options |GeometryEditorOptions |几何编辑器配置参数|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMap(map: Map) |this |设置地图对象,若为null则移除编辑器|
|setActiveOverlay(id: String) |this |设置指定图层处于编辑状态|
|setActionMode(mode: EDITOR_ACTION) |this |设置编辑器的操作状态|
| setKeyboardDeleteEnable(enable: Boolean) | this | 设置编辑器Delete按键(删除)事件是否开启 |
| setKeyboardCtrlEnable(enable: Boolean) | this | 设置编辑器Ctrl按键(多选)事件是否开启 |
| setKeyboardEscEnable(enable: Boolean) | this | 设置编辑器Esc按键(中断操作)事件是否开启 |
| setUndoDrawEnable(enable: Boolean) | this | 设置编辑器绘制撤销功能是否开启 |
| setSnapPolyStartPointOnDrawing | this | 设置编辑器绘制过程中是否允许线、面吸附自身第一个点 |
|getMap() |Map |获取绑定的地图对象|
|getActiveOverlay() |EditableOverlay |获取处于编辑状态的图层|
|getActionMode() |EDITOR_ACTION |获取编辑器的操作状态|
|getSelectedList() |Geometry[] |获取已选中的几何图形数组,Geometry可能是PointGeometry/PolylineGeometry/PolygonGeometry/CircleGeometry等|
| getKeyboardDeleteEnable() | Boolean | 获取编辑器Delete按键(删除)事件开关状态 |
| getKeyboardCtrlEnable() | Boolean | 获取编辑器Ctrl按键(多选)事件开关状态 |
| getKeyboardEscEnable() | Boolean | 获取编辑器Esc按键(中断操作)事件开关状态 |
| getUndoDrawEnable() | Boolean | 获取编辑器绘制撤销功能开关状态 |
| getSnapPolyStartPointOnDrawing | Boolean | 获取编辑器绘制过程中是否允许线、面吸附自身第一个点 |
|addOverlay(overlay: EditableOverlay) |this |添加用于编辑的几何图层|
|removeOverlay(id: String) |this |删除用于编辑的几何图层|
|getOverlayList() |EditableOverlay[] |获取图层列表|
|setSnappable(snappable: Boolean) |this |开启或关闭吸附功能,吸附功能开启时绘制或编辑图形会自动吸附到临近的点或线段上|
|setSelectable(selectable: Boolean) |this |开启或关闭点选功能,点选功能开启时用户可点击图形进行单选和多选,选中图形后会自动将其所属图层设置为编辑状态|
|select(idList: String[]) |this |选中属于激活状态的图层内的几何图形,若传入空数组则清空|
|stop() |this |停止绘制或编辑过程|
|split() |this |拆分已选中多边形,用户可绘制拆分线进行拆分,若无选中图形则无效|
|union() |this |合并已选中多边形,若无选中图形则无效|
|delete() |this |删除已选中图形|
|destroy() |this |销毁编辑器|
|on(eventName:String, listener:Function)|this |添加listener到eventName事件的监听器数组中|
|off(eventName:String, listener:Function)|this |从eventName事件的监听器数组中移除指定的listener器|
**事件:**</br>
监听事件通过on、off方法绑定与解绑。 查看示例
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|active_changed | |切换编辑图层,调用setActiveOverlay接口、选中其他图层元素或删除一个处于编辑状态的图层都会引发此事件|
|select | |点选图形|
|drawing |EditingEvent |绘制中,返回绘制中信息|
|draw_complete |Geometry |绘制完成,返回几何图形|
| draw_error | DrawErrorMessage| 绘制失败,返回失败信息 |
|adjusting|EditingEvent |修改中,返回修改中的信息|
|adjust_complete |Geometry |修改完成,返回修改后的几何图形|
| adjust_error | AdjustErrorMessage | 编辑失败,返回失败信息 |
|delete_complete |Geometry[] |删除完成,返回被删除的几何图形|
|split_complete |PolygonGeometry[] |拆分完成,返回拆分后的多边形数组|
|union_complete |PolygonGeometry |合并完成,返回合并后的多边形|
|split_fail |Object |拆分失败,返回对象中的message属性说明了失败原因|
|union_fail |Object |合并失败,返回对象中的message属性说明了失败原因|
</br></br>
## GeometryEditorOptions {#3}
----
几何编辑器配置参数对象规范。
|属性名称 |类型 |说明|
| :- | :- |:- |
|map |Map |编辑几何图形的地图对象|
|overlayList |EditableOverlay[] |用于编辑的几何图层|
|activeOverlayId |String |处于编辑状态的图层id,编辑状态下的图层可以新增图形、选中图形进行修改和删除|
|actionMode |EDITOR_ACTION[] |编辑器的操作状态|
|snappable |Boolean |是否开启吸附功能,默认为false|
|selectable |Boolean |是否开启点选功能,默认为false,开启后可以点选图形进行修改和删除操作|
|iconInfo |EditorIconInfo |编辑器的icon样式|
| modifierShapeMode | MODIFIER_SHAPE_MODE | 图形被选中编辑时的渲染模式,默认为SVG模式 |
| enableKeyboardDelete | Boolean | 是否启用默认的Delete按键(删除)事件,默认为true |
| enableKeyboardCtrl | Boolean | 是否启用默认的Ctrl按键(多选)事件,默认为true |
| enableKeyboardEsc | Boolean | 是否启用默认的Esc按键(中断操作)事件,默认为true |
| enableUndoDraw | Boolean | 是否启用编辑器绘制撤销功能,默认为true |
| snapPolyStartPointOnDrawing | Boolean | 编辑器在开启吸附后,绘制过程中是否允许线、面吸附自身的第一个点,默认为true |
</br></br>
## EditableOverlay {#4}
----
可编辑几何图层对象规范。
|属性名称 |类型 |说明|
| :- | :- |:- |
|id |String |标识,默认为图层id|
|name |String |在控件下拉列表中显示的图层名称|
|overlay |GeometryOverlay |几何图层,支持MultiMarker/MultiPolyline/MultiPolygon/MultiRectangle/MultiCircle/MultiEllipse|
|drawingStyleId |String |绘制图形的样式id,对应图层styles样式表中的id,默认为default|
|selectedStyleId |String |选中图形的样式id,对应图层styles样式表中的id,若不设置则使用默认样式|
| checkShape | Function | 对于该图层图形绘制或编辑时的即时校验方法, 传入参数为EditingEvent,通过自定义该校验方法返回boolean值来处理当前编辑行为是否生效,该方法形式如下:Function(shape:EditingEvent): boolean |
</br></br>
## TMap.tools.constants.EDITOR_ACTION {#2}
----
编辑器操作模式。
|常量 |说明|
| :- | :- |
|EDITOR_ACTION.INTERACT |交互模式,该模式下用户可选中图形进行删除、修改|
|EDITOR_ACTION.DRAW |绘制模式,该模式下用户可绘制新图形|
</br></br>
## MODIFIER_SHAPE_MODE 常量说明 {#MODIFIER_SHAPE_MODE}
---
选中编辑时图形的渲染模式。
| 常量 | 说明 |
| - | - |
| MODIFIER_SHAPE_MODE.SVG | 采用SVG模式来渲染被选中编辑的图形,当数据形点较少时建议使用该模式;默认为该模式 |
| MODIFIER_SHAPE_MODE.GL | 采用webgl模式来渲染被选中编辑的图形,当编辑的数据形点量级较大时使用该模式性能较好,但图形的渲染质量可能受机器配置影响 |
</br></br>
## EditorIconInfo 对象规范 {#EditorIconInfo}
----
编辑器所包含的icon信息对象。
| 属性名 | 类型 | 说明 |
| :------ | :-------- | :----------------- |
| virtual |IconStyle | 改变虚点icon的样式 |
| actual | IconStyle | 改变实点icon的样式 |
!
</br></br>
## IconStyle 对象规范 {#IconStyle}
----
编辑器icon的样式对象,可以设置src、宽高等属性。
| 属性名 | 类型 | 说明 |
| :------ | :-------- | :----------------- |
| src | String | 编辑点图片url或base64地址,若为url地址图片一定要在服务器端配置允许跨域 |
| width | Number | 图片的宽度,默认为10px |
| height | Number | 图片的高度,默认为10px |
| anchor | Object | 图片的锚点位置,对象字面量{x:Number, y:Number}形式,在地图各种操作中,锚点的位置与标注位置点是永远对应的;若没有锚点默认为{ x: width / 2, y: height / 2 };锚点以图片左上角点为原点 |
</br></br>
## EditingEvent 对象规范 {#EditingEvent}
----
编辑器所包含的icon信息对象。
| 属性名 | 类型 | 说明 |
| :------ | :-------- | :----------------- |
| position | LatLng | 鼠标当前所在的经纬度 |
| geometry | Geometry | 几何图形信息,在最开始绘制时值为null |
</br></br>
## DrawErrorMessage 对象规范 {#9}
编辑器绘制失败错误信息。
| 属性名 | 类型 | 说明 |
| - | - | - |
| errorDesc | String | 错误描述 |
| errorType | Number | 错误类型 |
| errorGeometry | Geometry | 错误几何图形信息(errorType为2时提供) |
现有绘制错误类型说明:
- 多边形自相交错误信息:{ errorDesc: 'geometry illegals', errorType: 1 }
- 矩形、圆形、椭圆未通过校验方法信息:{ errorDesc: 'geometry check failed', errorType: 2 }
</br></br>
## AdjustErrorMessage 对象规范 {#AdjustErrorMessage}
编辑器编辑失败错误信息。
| 属性名 | 类型 | 说明 |
| - | - | - |
| errorDesc | String | 错误描述 |
| errorType | Number | 错误类型 |
| errorGeometry | Geometry | 错误几何图形信息(errorType为2时提供) |
现有编辑错误类型说明:
- 删除多边形坐标点数量小于3时错误信息:{ errorDesc: 'geometry illegals', errorType: 1 }
- 矩形、圆形、椭圆未通过校验方法信息:{ errorDesc: 'geometry check failed', errorType: 2 }
</br></br>
## MeasureTool {#5}
----
测量工具用于地图测量,继承自Tool抽象类。
|构造函数|
| :- |
|new TMap.tools.MeasureTool(options);|
|参数名称|类型|说明|
| :- | :- |
|options |MeasureToolOptions[] |测量工具参数|
|方法 |返回值 |说明|
| :- | :- | :- |
|isMeasuring() |Boolean |判断用户是否在进行测量|
|measureDistance() |Promise(DistanceMeasurementInfo) |距离测量,调用方法后用户点击地图进行距离测量,双击结束测量;返回Promise,resolve状态下返回 DistanceMeasurementInfo 对象;如果用户在测量过程中该方法被调用,之前的测量过程将会被中断,并开始新的测量|
|measureArea() |Promise(AreaMeasurementInfo) |面积测量,调用方法后用户点击地图进行面积测量,双击结束测量;返回Promise,测量完成时resolve状态下返回 AreaMeasurementInfo 对象,顶点数不足3个测量失败时reject状态下返回错误信息;如果用户在测量过程中该方法被调用,之前的测量过程将会被强制结束,并开始新的测量|
</br></br>
## MeasureToolOptions {#6}
----
测量工具用于地图测量,继承自Tool抽象类。
|属性名称 |类型 |说明|
| :- | :- |:- |
|map |Map |展示图层的地图对象|
</br></br>
## DistanceMeasurementInfo {#7}
----
距离测量结果对象规范
|属性名称 |类型 |说明|
| :- | :- |:- |
|path |LatLng[] |线段节点数组|
|totalDistance |Number |总距离长度,单位为米|
|segmentDistanceList |Number[] |线段距离长度数组,数组元素代表每段距离长度,单位为米|
**方法**
| 方法名 | 返回值 | 说明 |Lite版本是否支持 |
| - | - | - |
| remove() | null | 将此次测量结果从地图上删除 |
</br></br>
## AreaMeasurementInfo {#8}
----
面积测量结果对象规范
|属性名称 |类型 |说明|
| :- | :- |:- |
|polygon |LatLng[] |多边形数组|
|area |Number |面积,单位为平方米
**方法**
| 方法名 | 返回值 | 说明 |Lite版本是否支持 |
| - | - | - |
| remove() | null | 将此次测量结果从地图上删除 |
FILE:references/jsapigl/docs/点聚合.md
## MarkerCluster {#1}
----
点聚合,可以对地图上的点进行聚合。
|构造函数|
|:- |
|new TMap.MarkerCluster(options);|
|参数名 |类型 |说明|
| :- | :- |:- |
|options |MarkerClusterOptions |点聚合的参数对象|
| 方法名 | 返回值 | 说明 |
| :- | :- |:- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将点聚合图层从地图中移除|
|setGeometries(geometries:PointGeometry[]) |this |更新点数据,如果参数为null或undefined不会做任何处理|
|getMap() |Map |获取地图对象,若为空返回null|
|getClusters() |ClusterInfo[] |获取当前地图视野范围内,聚合后的聚合簇数据;聚合是异步操作,可以绑定cluster_changed事件获取每次地图上最新的聚合簇|
|getGeometries() |PointGeometry[] |获取点数据|
|getGeometryById(id:String) |PointGeometry |根据点数据id来获取点数据|
|updateGeometries(geometry: PointGeometry[]) |this |更新标注点数据,如果geometry的id存在于聚合点的集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的点标注添加到集合中进行聚合;如果参数为null或undefined不会做任何处理|
|add(geometries: PointGeometry[]) |this |向图层中添加标注点,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合中进行聚合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的标注存在重复id,这些标注点会被重新分配id;如果参数为null或undefined不会做任何处理|
|remove(ids: String[]) |this |移除指定id的标注点,如果参数为null或undefined不会做任何处理|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener|
|destroy() | | 销毁图层对象 |
**事件:**</br>
监听事件通过on、off方法绑定与解绑。
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |Object |点击事件,如果有选中的聚合簇,事件对象中的cluster属性会指向选中的 ClusterInfo[];如果有选中的单个标注点,事件对象中的geometry指向被选中的PointGeometry;只针对启用默认样式的点聚合适用|
|cluster_changed |null |聚合簇发生变化时触发|
</br></br>
## MarkerClusterOptions {#2}
----
MarkerCluster配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|id |String |图层id,若没有会自动分配一个|
|map |Map |显示点聚合图层的底图|
|enableDefaultStyle |Boolean |是否启用默认的聚合样式; 默认样式是聚合点的数量在1-10,11-100,101-1000的样式图标; 默认为true; 如果用户想实现更加炫酷的聚合效果,可以关闭默认样式,使用getClusters获取到聚合簇数据后通过继承DOMOverlay来实现自定义聚合样式 查看示例|
|minimumClusterSize |Number |形成聚合簇的最小个数;默认为2|
|geometries |PointGeometry[] |点标记数据数组,注:该点标记不支持通过style更改样式|
|zoomOnClick |Boolean |点击已经聚合的标记点时是否实现聚合分离;默认为true; 但只对默认样式启用,自定义样式需要开发者监听相应元素的点击事件后使用Map.fitBounds方法实现|
|gridSize |Number |聚合算法的可聚合距离,即距离小于该值的点会聚合至一起,默认为60,以像素为单位,指的是地图pitch为0时的屏幕像素大小|
|averageCenter |Boolean |每个聚和簇的中心是否应该是聚类中所有标记的平均值,默认为false|
|maxZoom |Number |采用聚合策略的最大缩放级别,若地图缩放级别大于该值,则不进行聚合。默认为20|
| zIndex | Number | 图层绘制顺序 |
| collisionOptions | CollisionOptions | 图层碰撞配置参数 |
</br></br>
## ClusterInfo{#3}
----
clusterInfo对象规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|center |LatLng |聚合簇的位置点|
|geometries |PointGeometry[] |该聚合簇内的点标记数据数组,注:该点标记不支持通过style更改样式|
|bounds |LatLngBounds |聚合簇的边界范围|
</br></br>
## CollisionOptions 对象规范{#4}
----
图层碰撞配置参数。
| 属性名称 | 类型 | 说明 |
| -- | -- | -- |
| crossSource | Boolean | 默认聚合样式下,是否开启聚合簇跨图层碰撞,所有开启的图层间进行碰撞,默认为false。</br> 开启后优先级按zIndex进行碰撞,zIndex默认0,如marker的rank = 10,MarkerCluster的zIndex = 11,MarkerCluster具有更高的优先级碰撞后marker消失。 |
| vectorBaseMapSource | Boolean | 默认聚合样式下,是否允许聚合簇碰撞底图元素(道路名、poi、icon),默认为false |
FILE:references/jsapigl/docs/DOM覆盖物.md
## DOMOverlay {#1}
----
DOM覆盖物抽象类,用户可以此作为基类实现自定义的DOM覆盖物类。
|构造函数|
| :- |
|new TMap.DOMOverlay(options);|
|参数名 |类型 |说明|
| :- | :- |:- |
|options |DOMOverlayOptions |自定义DOM覆盖物参数对象|
|方法名 |返回值 |说明|
| :- | :- |:- |
|setMap(map: Map) |this |设置覆盖物所在的map对象,传入null则代表将其从Map中移除|
|getMap() |Map |获取覆盖物所在的Map对象|
|destroy() |this |销毁覆盖物对象|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener|
**抽象方法:**</br>
需要实现如下接口来构建自定义的DOM覆盖物。 查看示例
|抽象方法名 |返回值 |说明|
| :- | :- |:- |
|onInit(options) |None |实现这个接口来定义构造阶段的初始化过程,此方法在构造函数中被调用,接收构造函数的options参数作为输入|
|onDestroy() |None |实现这个接口来定义销毁阶段的资源释放过程,此方法在destroy函数中被调用|
|createDOM()| HTMLElement |实现这个接口来创建自定义的DOM元素,此方法在构造函数中被调用(在初始化过程之后)|
|updateDOM() |None |实现这个接口来更新DOM元素的内容及样式,此方法在地图发生平移、缩放等变化时被调用|
</br></br>
## DOMOverlayOptions {#2}
----
自定义DOM覆盖物配置参数。
|名称 |类型 |说明|
| :- | :- |:- |
|map |Map |(必需)显示自定义DOM覆盖物的地图|
| collisionOptions | CollisionOptions | 碰撞配置参数 |
| isStopPropagation | Boolean | 是否阻止鼠标事件冒泡,默认为false |
</br></br>
## CollisionOptions 对象规范 {#3}
----
图层碰撞配置参数。
|名称 |类型 |说明|
| :- | :- |:- |
| sameSource | Boolean | 是否开启DOMOverlay碰撞,默认为false。</br>开启后优先级按css style的zIndex进行碰撞,zIndex默认0 |
| crossSource | Boolean | 是否开启跨图层间碰撞,所有开启的图层间进行碰撞,默认为false。</br> 开启后优先级按css style的zIndex进行碰撞,zIndex默认0,如marker的rank = 10,DOMOverlay的zIndex = 11,DOMOverlay具有更高的优先级碰撞后marker消失。 |
| vectorBaseMapSource | Boolean | 是否允许碰撞底图元素(道路名、poi、icon),默认为false |
FILE:references/jsapigl/docs/点标记.md
## MultiMarker {#1}
表示地图上的多个标注点,可自定义标注的图标。
|构造函数|
| :- |
|TMap.MultiMarker(options:MultiMarkerOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将多个标注点同时从地图中移除。|
|setGeometries(geometries: PointGeometry[]) |this |更新标注点数据,如果参数为null或undefined不会做任何处理。|
|setStyles(styles:MultiMarkerStyleHash) |this |设置MultiMarker图层相关样式信息,如果参数为null或undefined不会做任何处理。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
|getMap() |Map |获取地图对象,若为空返回null。|
|getGeometries() |PointGeometry[] |获取点数据。|
|getStyles() |MultiMarkerStyleHash |获取图层相关样式信息。|
|getVisible() |visible |获取可见状态。|
|getGeometryById(id:String) |PointGeometry |根据点数据id来获取点数据。|
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
|updateGeometries(geometry:PointGeometry[]) |this |更新标注点数据,如果geometry的id存在于多点标注的集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的点标注添加到集合中;如果参数为null或undefined不会做任何处理。|
|add(geometries: PointGeometry PointGeometry[]) |this |向图层中添加标注点,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的标注存在重复id,这些标注点会被重新分配id;如果参数为null或undefined不会做任何处理。|
|remove(ids: String[]) |this |移除指定id的标注点,如果参数为null或undefined不会做任何处理。|
|moveAlong(param: MoveAlongParamSet, options:Object) |this |指定id的标注点,沿着指定路径移动;每次新调用moveAlong时,尚未完成的动画会被取消,并触发move_stopped事件;options中如果设置autoRotation为true,对于faceTo为’map’的点标记,会根据路径方向自动改变点标记图片的旋转角度。|
|stopMove() |this |停止移动,尚未完成的动画会被取消,并触发move_stopped事件;已经完成的动画不会触发move_stopped事件。|
|pauseMove() |this |暂停点标记的动画效果,并触发move_paused事件;未在移动状态不会触发move_paused事件。|
|resumeMove() |this |重新开始点标记的动画效果,并触发move_resumed事件;未在暂停状态不会触发move_resumed事件。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
**事件:**</br>
监听事件通过on、off方法绑定与解绑。查看示例
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |GeometryOverlayEvent |点击事件。|
|dblclick |GeometryOverlayEvent |双击事件。|
|mousedown |GeometryOverlayEvent |鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发。|
|mouseup |GeometryOverlayEvent |鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发。|
|mousemove |GeometryOverlayEvent |鼠标在地图上移动时触发,只在桌面浏览器中触发。|
|hover |GeometryOverlayEvent |鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的PointGeometry,无图形时事件对象为null,只在桌面浏览器中触发。|
|touchstart |GeometryOverlayEvent |在地图区域触摸开始时触发,只在移动浏览器中触发。|
|touchmove |GeometryOverlayEvent |在地图区域触摸移动时触发,只在移动浏览器中触发。|
|touchend |GeometryOverlayEvent |在地图区域触摸结束时触发,只在移动浏览器中触发。|
|moving |Object |点标记在执行moveAlong动画时触发事件,事件参数为一个key-value形式对象, key代表MultiMarker点数据集合中的PointGeometry的id,value是一个 MarkerMovingEventItem 对象。|
|move_ended |none |点标记执行moveAlong动画结束时触发事件。|
|move_stopped |none |点标记执行moveAlong动画被停止时触发事件。|
|move_paused |none |点标记执行moveAlong动画被暂停时触发事件。|
|move_resumed |none |点标记执行moveAlong动画由暂停到恢复时触发事件。|
| geometry_processed | | 由增删改引起的几何数据变更被处理完成后触发。(注意:1、该事件仅代表数据处理完成,不代表数据被渲染完成,通常数据处理完后20~50ms内可被渲染;2、当该实例对象被编辑器激活进行编辑前,需要开发者手动阻止该事件监听函数中的业务逻辑执行,在编辑完成后可正常执行事件监听函数的业务逻辑) |
**示例:**</br>
本示例中,介绍如何设置标注的属性
html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>设置marker属性</title>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
</head>
<body onLoad="initMap()">
<script>
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始化marker
var marker = new TMap.MultiMarker({
id: "marker-layer", //图层id
map: map,
styles: { //点标注的相关样式
"marker": new TMap.MarkerStyle({
"width": 25,
"height": 35,
"anchor": { x: 16, y: 32 },
"src": "img/marker.png"
})
},
geometries: [{ //点标注数据数组
"id": "demo",
"styleId": "marker",
"position": new TMap.LatLng(39.984104, 116.307503),
"properties": {
"title": "marker"
}
}]
});
</body>
</html>
新窗口打开 在线试一试
</br></br>
## MultiMarkerOptions {#2}
----
MultiMarker的配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|id |String |图层id,若没有会自动分配一个。|
|map |Map |显示Marker图层的底图。|
|zIndex | Number | 图层绘制顺序。
|styles |MultiMarkerStyleHash |点标注的相关样式。|
|collisionOptions | CollisionOptions | 图层碰撞配置参数。 |
|geometries |PointGeometry[] |点标注数据数组。|
| minZoom |Number |最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3|
| maxZoom |Number |最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20|
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
## CollisionOptions {#3}
----
图层碰撞配置参数。
| 属性名称 | 类型 | 说明 |
| :------------------ | :------ | :----------------------------------------------------------- |
| sameSource | Boolean | 是否开启同图层内碰撞,如开启,碰撞优先级按rank值进行碰撞,rank值越大,碰撞权重越高,默认为false |
| crossSource | Boolean | 是否开启跨图层间碰撞,所有开启跨图层间进行碰撞,优先级按zIndex值进行碰撞,zIndex值越大,碰撞权重越高,默认为false |
| vectorBaseMapSource | Boolean | 是否允许碰撞地图元素,开启后图层优先级高于地图元素优先级,默认为false |
</br></br>
## MultiMarkerStyleHash {#4}
----
一个key-value形式对象, 表示Marker图层的相关样式信息,key使用字符串,value是 MarkerStyle 对象。
</br></br>
## MarkerStyle {#5}
----
应用于Marker图层的样式类型。
|构造函数|
| :- |
|TMap.MarkerStyle(options:MarkerStyleOptions)|
</br></br>
## MarkerStyleOptions {#6}
----
MarkerStyle配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|width |Number |必需,标注点图片的宽度,默认为34
|height |Number |必需,标注点图片的高度,默认为50
|anchor |Object |标注点图片的锚点位置,对象字面量{x:Number, y:Number}形式,在地图各种操作中,锚点的位置与标注位置点是永远对应的;若没有锚点默认为{ x: width/2, y: height };锚点以图片左上角点为原点|
|src |String |标注点图片url或base64地址,若为url地址图片一定要在服务器端配置允许跨域|
|faceTo |String |标注点图片的朝向,可取’map’(贴地)或’screen’(直立),默认为’screen’。|
| enableRelativeScale |Boolean | 是否启动随地图放大缩小,默认为false(**只针对非碰撞Marker生效**) |
| relativeScaleOptions | RelativeScaleOptions | Marker的图像、文本随地图放大缩小的控制参数(**只针对非碰撞Marker生效**) |
|rotate |Number |标注点图片的旋转角度,单位为度,非负数;以锚点为旋转原点,逆时针为正。|
| opacity |Number | 标注点图片的透明度,取值0-1。 |
|color |String |标注点文本颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0,0,0,1) 。|
|strokeColor |String |标注点文本描边颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0,0,0,0)。|
| strokeWidth | Number | 标注点文本描边宽度,默认为1。 |
| size| Number | 标注点文本文字大小属性,默认为14。|
| direction | String | 标注点文本文字相对于标注点图片的方位,可选位于标注点图片的center,top,bottom,left,right方位,默认位于图片的中心center 。|
| offset| Object: {x:Number, y:Number} | 标注点文本文字基于direction方位的偏移量,单位为像素,以文本文字中心为原点,x轴向右为正向左为负,y轴向下为正向上为负,默认为{x:0, y:0} 。|
| wrapOptions | LabelWrapOptions | 标注点文本自动换行,支持设置软换行和硬换行,软换行支持配置最大换行宽度,硬换行换行符为'\n'。 LabelWrapOptions如果为空对象则以文本中换行符进行换行,如果为null或undefined则不换行|
| padding | String | 标注点文字背景框内边距,单位为像素,属性支持接受1~2个值,规则符合css规范。</br>例:“15px” 上下左右内边距为15px</br>例:“15px 20px” 上下内边距为15px,左右内边距为20px</br>**注意设置背景宽高后padding将不生效** |
| backgroundWidth | Number | 标注点文本背景的宽度,单位为像素,默认为0 |
| backgroundHeight | Number | 标注点文本背景的高度,单位为像素,默认为0 |
| backgroundColor | String | 标注点文本背景颜色属性,支持rgb(),rgba(),#RRGGBB等形式,**该属性生效需要设置padding 或 backgroundWidth和backgroundHeight**,默认为rgba(0,0,0,1) |
| backgroundBorderWidth | Number | 标注点文字背景框边线宽度,单位为像素;**该属性生效需要设置padding 或 backgroundWidth和backgroundHeight**,默认为0 |
| backgroundBorderRadius | Number | 标注点文本背景框圆角,单位为像素;**该属性生效需要设置padding 或 backgroundWidth和backgroundHeight**,默认为0 |
| backgroundBorderColor | String | 标注点文本背景描边颜色属性,支持rgb(),rgba(),#RRGGBB等形式,**该属性生效需要设置padding 或 backgroundWidth和backgroundHeight**,默认为rgba(0,0,0,0) |
</br></br>
## PointGeometry {#7}
----
点图形数据。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|id |String |点图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个。|
|styleId |String |对应MultiMarkerStyleHash中的样式id, 如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制。|
|position |LatLng |标注点位置。|
|rank |Number |标注点的图层内绘制顺序。|
|properties |Object |标注点的属性数据。|
| content| String | 标注点文本,默认为undefined,即无标注点文本绘制效果 |
| markerAnimation| MarkerAnimation | 标注点入场和离场动画设置 |
</br></br>
## MoveAlongParamSet {#8}
----
MultiMarker的moveAlong方法的参数对象。一个key-value形式对象, keyMultiMarker点数据集合中的PointGeometry的id,value是 MoveAlongParam 对象。
</br></br>
## MoveAlongParam {#9}
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|path |LatLng[] |移动路径的坐标串|
|duration |Number |完成移动所需的时间,单位:毫秒 (同时指定duration和speed,duration优先级高)|
|speed |Number |speed为指定速度,正整数,单位:千米/小时|
</br></br>
## MarkerMovingEventItem {#10}
----
点标记沿线移动时返回的信息。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|passedLatLngs |LatLng[] |marker所走过的路径坐标串|
|angle |Number |自动旋转情况下,当前点的旋转角度(只对faceTo为’Map’的点标记有效)|
</br></br>
## MarkerAnimation 对象规范 {#11}
----
| 名称| 类型| 说明|
| -- | -- | -- |
| enter | MarkerAnimationParams | 入场动画 |
| leave | MarkerAnimationParams | 离场动画 |
</br></br>
## MarkerAnimationParams 对象规范 {#12}
----
| 名称| 类型| 说明|
| -- | -- | -- |
| scaleStart | Number | scale动画起始值,默认为1.0 |
| scaleEnd | Number | scale动画终止值,默认为1.0 |
| alphaStart | Number| alpha动画起始值,默认为1.0 |
| alphaEnd | Number| alpha动画终止值,默认为1.0 |
| duration | Number | 动画时长,同时作用于scale动画和alpha动画,默认为500,单位为毫秒 |
</br></br>
## LabelWrapOptions {#13}
----
文本换行配置。当同时指定了最大换行宽度、最大行数和换行符时,换行优先级为: 最大行数>换行符>最大换行宽度。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| maxWidth | Number | 最大换行宽度,单位是像素,默认不限制 |
| maxLineCount | Number | 最大行数,默认不限制 |
| rowSpacing | Number | 行间距,单位是像素,默认为0 |
</br></br>
## RelativeScaleOptions {#14}
----
Marker的图像、文本随地图放大缩小的控制参数。
| 属性名称 | 类型 | 说明 |Lite版本是否支持 |
| -- | -- | -- |
|scaleZoom |Number | 设置marker图片宽高像素单位与zoom级别的瓦片像素相同,如当设置为18时,zoom小于18marker会被缩小直至达到minScale设置的最小缩放倍数,大于时marker直至达到maxScale设置的最大缩放倍数;enableRelativeScale为true时生效,默认18 |
| minScale | Number | 设置最小的缩放比例,范围 \[0, 1),默认为0.5 |
| maxScale | Number | 设置最大的缩放比例,范围 \[1, 10), 默认为1 |
FILE:references/jsapigl/docs/环境检测.md
#### Brower {#1}
Brower用于环境检测。
**属性**
|名称|类型|说明
|--|--|--|
|userAgent |String |当前浏览器的userAgent信息 |
|isMobileDevice |Boolean |是否运行在移动设备 |
|platform |String |平台类型,如:'Windows'、'Mac'、'iPhone'、'Android'、'Linux' |
|isWindowsDevice |Boolean |是否运行在windows设备 |
|isIosDevice |Boolean |是否运行在iOS设备 |
|isIPad |Boolean |是否运行在iPad |
|isIPhone |Boolean |是否运行在iPhone |
|isAndroidDevice |Boolean |是否运行在安卓设备 |
|isAndroid23 |Boolean |是否运行在安卓4以下系统 |
|isChrome |Boolean |是否运行在Chrome浏览器 |
|isFirefox |Boolean |是否运行在火狐浏览器 |
|isSafari |Boolean |是否运行在Safari浏览器 |
|isWeChat |Boolean |是否运行在微信 |
|isUC |Boolean |是否运行在UC浏览器 |
|isQQ |Boolean |是否运行在QQ或者QQ浏览器 |
|isIE |Boolean |是否运行在IE浏览器 |
|isEdge |Boolean |是否运行在Edge浏览器 |
|isMiniProgram |Boolean |是否运行在微信小程序 |
|isMiniProgramMobile |Boolean |是否运行在微信小程序移动端 |
|isMiniProgramPC |Boolean |是否运行在微信小程序pc端 |
|isMobileWebkit |Boolean |是否运行在支持Webkit移动端浏览器 |
|isMobileWebkit3d |Boolean |是否运行在支持Css3D的Webkit移动端浏览器 |
|isRetinaDevice |Boolean |是否运行在高清屏幕设备,devicePixelRatio>1 |
|isTouchDevice |Boolean |是否运行在触屏设备 |
|isWebkit |Boolean |是否运行在webkit浏览器 |
|isSupportLocalStorage |Boolean |运行环境是否支持LocaStorage |
|isSupportGeolocation |Boolean |运行环境是否支持Geolocation |
|isSupportWebkit3d |Boolean |是否运行在支持Css3D的Webkit浏览器 |
|isSupportGecko3d |Boolean |是否运行在支持Css3D的gecko浏览器 |
|isSupportAny3d |Boolean |是否运行在支持Css3D的浏览器 |
|isSupportCanvas |Boolean |运行环境是否支持canvas |
|isSupportSvg |Boolean |运行环境是否支持svg |
|isSupportVML |Boolean |运行环境是否支持vml |
|isSupportWorker |Boolean |运行环境是否支持WebWorker |
|isSupportWebsocket |Boolean |运行环境是否支持WebSocket |
|isSupportIndexDB |Boolean |运行环境是否支持IndexDB |
|isSupportWebGL |Boolean |运行环境是否支持webgl |
|isSupportWebGL2 |Boolean |运行环境是否支持webgl 2.0 |
FILE:references/jsapigl/docs/矢量图形.md
## MultiPolyline {#1}
----
表示地图上的多个折线,可以自定义每个折线的样式。
|构造函数|
| :- |
|TMap.MultiPolyline(options:MultiPolylineOptions)|
|方法 |返回值 |说明|
| :- |:- |:- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将多折线同时从地图中移除。|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|setGeometries(geometries: PolylineGeometry[]) |this |更新折线数据,如果参数为null或undefined不会做任何处理。|
|setStyles(styles:MultiPolylineStyleHash) |this |设置MultiPolyline图层相关样式信息,如果参数为null或undefined不会做任何处理。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
|getMap() |Map |获取地图对象,若为空返回null。|
|getGeometries() |PolylineGeometry[] |获取折线数据。|
|getStyles() |MultiPolylineStyleHash |获取图层相关样式信息。
|getVisible() |visible |获取可见状态。|
|getZIndex() |Number |获取图层绘制顺序。|
|getGeometryById(id:String) |PolylineGeometry |根据折线数据id来获取点数据。|
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
|updateGeometries(geometry: PolylineGeometry[]) |this |更新折线数据,如果geometry的id存在于集合中,会更新对应id的数据,如果之前不存在于集合中,会作为新的折线添加到集合中;如果参数为null或undefined不会做任何处理。|
|add(geometries: PolylineGeometry[]) |this |向图层中添加折线,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的折线存在重复id,这些折线会被重新分配id;如果参数为null或undefined不会做任何处理。|
|remove(ids: String[]) |this |移除指定id的折线,如果参数为null或undefined不会做任何处理。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| destroy() | | 销毁图层对象。 |
| eraseTo(id: String, index: Number, point:LatLng) | | 用于线的擦除。第一个参数id 表示指定geometry的ID,擦除效果仅对该id对应的路线有效。 <br>第二个参数index表示要擦除到的坐标索引 index 。<br>第三个参数指线段 (坐标索引为[ index -1 , index ] )上擦除点的经纬度坐标( 如果这个坐标不在擦除的索引范围内,会一直擦除到坐标索引为index的点 )。只支持简单折线。查看示例 |
监听事件通过on、off方法绑定与解绑。 查看示例
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |GeometryOverlayEvent |点击事件。|
|dblclick |GeometryOverlayEvent |双击事件。|
|rightclick |GeometryOverlayEvent |右键点击事件。|
|mousedown |GeometryOverlayEvent |鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发。|
|mouseup |GeometryOverlayEvent |鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发。|
|mousemove |GeometryOverlayEvent |鼠标在地图上移动时触发,只在桌面浏览器中触发。|
|mouseover |GeometryOverlayEvent |鼠标在移动到折线图层上时触发,只在桌面浏览器中触发。|
|mouseout |GeometryOverlayEvent |鼠标移出折线图层时触发,只在桌面浏览器中触发。|
|hover |GeometryOverlayEvent |鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的PolylineGeometry,无图形时事件对象为null,只在桌面浏览器中触发。|
|touchstart |GeometryOverlayEvent |在地图区域触摸开始时触发,只在移动浏览器中触发。|
|touchmove |GeometryOverlayEvent |在地图区域触摸移动时触发,只在移动浏览器中触发。|
|touchend |GeometryOverlayEvent |在地图区域触摸结束时触发,只在移动浏览器中触发。|
| geometry_processed | | 由增删改引起的几何数据变更被处理完成后触发。(注意:1、该事件仅代表数据处理完成,不代表数据被渲染完成,通常数据处理完后20~50ms内可被渲染;2、当该实例对象被编辑器激活进行编辑前,需要开发者手动阻止该事件监听函数中的业务逻辑执行,在编辑完成后可正常执行事件监听函数的业务逻辑) |
</br></br>
MultiPolylineOptions {#2}
----
MultiPolyline配置参数。
| 名称 |类型 |说明|
| :- | :- |:- |
|id |String |图层id,若没有会自动分配一个。
|map |Map |显示折线图层的底图。
|zIndex |Number |图层绘制顺序
|styles |MultiPolylineStyleHash[] |折线的相关样式。
|enableGeodesic | Boolean | 绘制折线是否是大圆航线, 默认为 false。
| enableSimplify | Boolean | 是否开启抽稀, 默认为true(开启大圆航线后将默认关闭抽稀)。
|geometries |PolylineGeometry[] |折线数据数组。|
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
</br></br>
## MultiPolylineStyleHash {#3}
----
一个key-value形式对象, 表示折线图层的相关样式信息,key使用字符串,value是 PolylineStyle 对象。
</br></br>
## PolylineStyle {#4}
----
表示应用于折线图层的样式类型。
|构造函数|
| :- |
|TMap.PolylineStyle(options:PolylineStyleOptions)|
|属性名称 |类型 |说明|
| :- | :- | :- |
|color |String |线填充色,支持rgb(),rgba(),#RRGGBB等形式,默认为#3777FF。|
|width |Number |折线宽度,正整数,单位为像素,指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差,默认为3。|
|borderWidth |Number |边线宽度,非负整数,默认为0,单位为像素,指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差,默认为1。|
|borderColor |String |边线颜色,支持rgb(),rgba(),#RRGGBB等形式,borderWidth不为0时有效,默认为#3777FF。|
|eraseColor |String |擦除线填充色,支持rgb(),rgba(),#RRGGBB等形式,默认为#3777FF。|
|lineCap |String |线端头方式,可选为butt,round,square,默认为butt。|
|dashArray |Number[] |虚线展示方式,[0, 0]为实线,[10, 10]表示十个像素的实线和十个像素的空白(如此反复)组成的虚线,默认为[0, 0];这里的像素指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差。|
|showArrow |Boolean |是否沿线路方向显示箭头,默认为false,建议线宽度大于6时使用。|
|arrowOptions | ArrowOptions|箭头显示配置,仅在showArrow为true时有效。|
| enableBloom | Boolean | 是否对折线启用泛光效果,需在MapRenderOptions中开启泛光后才可使用。查看示例 |
</br></br>
## ArrowOptions {#5}
----
折线上箭头配置参数。
|属性名称|类型|说明|
| :- | :- | :- |
|width |Number |箭头图标宽度,单位为px,默认为6,最大支持255|
|height |Number |箭头图标高度(沿线方向长度),单位为px,默认为4,最大支持255|
|space |Number |箭头图标之间的孔隙长度,单位为px,默认为50,最大支持255|
| animSpeed |Number |箭头动态移动的速度,默认为0,静止不动,单位为(像素/秒)|
!
</br></br>
## PolylineGeometry {#6}
----
折线数据。
|属性名称 |类型 |说明|
| :- | :- | :- |
|id |String |折线图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个。|
|styleId |String |对应MultiPolylineStyleHash中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制。|
|paths |LatLng[] 或 LatLng[][] |折线的位置信息,可以传入[latLng1, latLng2, latLng3]表示一条折线;另外一条折线可能由多个不相连的线组成一个逻辑单位,如:[[latLng1, latLng2, latLng3], [latLng4, latLng5, latLng6]]。|
|rainbowPaths |Array |多颜色折线的信息,若传入该属性,其优先级高于paths。数据格式:[{path: [latLng1, latLng2, latLng3], color: '#FFFFFF', borderColor: '#FFFFFF'}, {path: [latLng3, latLng4, latLng5], color: '#000000', borderColor: '#000000'}],数组中每个对象包含一个path和对应填充颜色以及边线颜色,对于连续的线,需要保证后一个对象中path的起点是前一个对象中path的终点,颜色可选填,支持rgb(),rgba(),#RRGGBB等形式,默认为styleId对应的样式对象中的颜色配置。 查看示例|
|properties |Object |折线的属性数据。|
|rank |Number |折线图层内绘制顺序。|
</br></br>
## MultiPolygon {#7}
----
表示地图上的多个多边形,可以自定义每个多边形的样式。
|构造函数|
| :- |
|TMap.MultiPolygon(options:MultiPolygonOptions)|
|方法 |返回值 |说明|
| :- | :- | :- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将多边形同时从地图中移除。
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|setGeometries(geometries: PolygonGeometry[]) |this |更新多边形数据,如果参数为null或undefined不会做任何处理。|
|setStyles(styles:MultiPolygonStyleHash) |this |设置MultiPolygon图层相关样式信息,如果参数为null或undefined不会做任何处理。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
|getMap() |Map |获取地图对象,若为空返回null。|
|getGeometries() |PolygonGeometry[] |获取多边形数据。|
|getStyles() |MultiPolygonStyleHash |获取图层相关样式信息。|
|getVisible() |visible |获取可见状态。|
|getZIndex() |Number |获取图层绘制顺序。|
|getGeometryById(id:String) |PolygonGeometry |根据多边形数据id来获取点数据。|
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
|updateGeometries(geometry:PolygonGeometry[]) |this |更新多边形数据,如果geometry的id存在于集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的多边形添加到集合中;如果参数为null或undefined不会做任何处理。|
|add(geometries: PolygonGeometry[]) |this |向图层中添加多边形,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的多边形存在重复id,这些多边形会被重新分配id;如果参数为null或undefined不会做任何处理。|
|remove(ids: String[]) |this |移除指定id的多边形,如果参数为null或undefined不会做任何处理。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| destroy() | | 销毁图层对象 |
**事件:**</br>
监听事件通过on、off方法绑定与解绑。 查看示例
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |GeometryOverlayEvent |点击事件。|
|dblclick |GeometryOverlayEvent |双击事件。|
|mousedown |GeometryOverlayEvent |鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发。|
|mouseup |GeometryOverlayEvent |鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发。|
|mousemove |GeometryOverlayEvent |鼠标在地图上移动时触发,只在桌面浏览器中触发。|
|hover |GeometryOverlayEvent |鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的PolygonGeometry,无图形时事件对象为null,只在桌面浏览器中触发。|
|touchstart |GeometryOverlayEvent |在地图区域触摸开始时触发,只在移动浏览器中触发。|
|touchmove |GeometryOverlayEvent |在地图区域触摸移动时触发,只在移动浏览器中触发。|
|touchend |GeometryOverlayEvent |在地图区域触摸结束时触发,只在移动浏览器中触发。|
| geometry_processed | | 由增删改引起的几何数据变更被处理完成后触发。(注意:1、该事件仅代表数据处理完成,不代表数据被渲染完成,通常数据处理完后20~50ms内可被渲染;2、当该实例对象被编辑器激活进行编辑前,需要开发者手动阻止该事件监听函数中的业务逻辑执行,在编辑完成后可正常执行事件监听函数的业务逻辑) |
</br></br>
## MultiPolygonOptions {#8}
----
MultiPolygon配置参数。
|属性名称 |类型 |说明|
| :- | :- |:- |
|id |String |图层id,若没有会自动分配一个。|
|map |Map |显示多边形图层的底图。|
|zIndex |Number |图层绘制顺序。|
|styles |MultiPolygonStyleHash |多边形的相关样式。|
|geometries |PolygonGeometry[] |多边形数据数组。|
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
</br></br>
## MultiPolygonStyleHash {#9}
----
MultiPolygon一个key-value形式对象, 表示多边形图层的相关样式信息,key使用字符串,value是PolygonStyle或者ExtrudablePolygonStyle对象。
</br></br>
## PolygonStyle {#10}
----
表示应用于多边形图层的样式类型。
|构造函数|
|:- |
|TMap.PolygonStyle(options:PolygonStyleOptions)|
|属性名称 |类型 |说明|
| :- | :- |:- |
|color |String |面填充色,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(55,119,255,0.16)。|
|showBorder |Boolean |是否显示边线,默认为true。|
|borderColor |String |边线颜色,支持rgb(),rgba(),#RRGGBB等形式,默认为#3777FF,showBorder为true时有效。|
|borderWidth |Number |边线宽度,正整数,单位为像素,指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差,默认为2,showBorder为true时有效。|
|borderDashArray |Number[] |边线虚线虚线展示方式,[0, 0]为实线,[10, 10]表示十个像素的实线和十个像素的空白(如此反复)组成的虚线,默认为[0, 0];这里的像素指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差。|
</br></br>
## ExtrudablePolygonStyle {#11}
----
表示应用于有立体拉伸需求的多边形的样式类型,拔起面块的边线不支持设置宽度永远为1像素宽度。
|构造函数|
|:- |
|TMap.ExtrudablePolygonStyle(options:ExtrudablePolygonStyleOptions)|
|属性名称 |类型 |说明|
| :- | :- |:- |
|color |String |面填充色,支持rgb(),rgba(),#RRGGBB等形式。|
|extrudeHeight |Number |多边形拔起高度,默认为1,单位米。|
|showBorder |Boolean |是否显示拔起面块的边线,拔起面块的边线不支持设置宽度,永远为1像素宽度。|
|borderColor |String |边线颜色,支持rgb(),rgba(),#RRGGBB等形式,showBorder为true时有效。|
</br></br>
## PolygonGeometry {#12}
----
多边形数据。
|属性名称 |类型 |说明|
| :- | :- |:- |
|id |String |多边形图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个。|
|styleId |String |对应MultiPolygonStyleHash中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制。|
|paths |LatLng[] 或 LatLng[][] 或 LatLng[][][] |多边形的位置信息,可以传入[latLng1, latLng2, latLng3, latLng5, latLng1]这种简单多边形;也可以传入带洞多边形[[latLng1, latLng2, latLng3, latLng5, latLng1], [latLng6, latLng8, latLng9, latLng6], [latLng10, latLng11, latLng10]]这个多边形包含一个外环[latLng1, latLng2, latLng3, latLng5, latLng1]和两个洞[latLng6, latLng8, latLng9, latLng6], [latLng10, latLng11, latLng10]; 另外一个多边形可能有多个不相邻的多边形组成一个逻辑主体;比如南沙群岛就是由多个分离的多边形组成一个主体,一个岛屿可能存在多个岛中湖形成带洞多边形;示例:[[[latLng1, latLng2, latLng3, latLng5, latLng1], [latLng6, latLng8, latLng9, latLng6], [latLng10, latLng11, latLng10]], [[latLng11, latLng12, latLng13, latLng14, latLng11]], [[latLng15, latLng16, latLng17, latLng18, latLng15]]], 这个多边形主体中包含了三个多边形, 其中第一个多边形包含一个外环[latLng1, latLng2, latLng3, latLng5, latLng1]和两个洞[latLng6, latLng8, latLng9, latLng6], [latLng10, latLng11, latLng10], 第二和第三个多边形都是不带洞的简单多边形。查看示例|
|rank |Number |多边形的图层内绘制顺序。|
|properties |Object |多边形的属性数据。|
<br><br>
## MultiRectangle{#MultiRectangle}
---------
表示地图上的多个矩形,可以自定义每个矩形的样式。
| 构造函数 |
| :----------------------------------------------------------- |
| new TMap.MultiRectangle(options: MultiRectangleOptions); |
| 方法名 | 返回值 | 说明 |
| :----------------------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| setMap(map:Map) | this | 设置地图对象,如果map为null意味着将多矩形同时从地图中移除 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序 |
| setGeometries(geometries: RectangleGeometry[]) | this | 更新多矩形数据,如果参数为null或undefined不会做任何处理 |
| setStyles(styles:MultiRectangleStyleHash | this | 设置MultiRectangle图层相关样式信息,如果参数为null或undefined不会做任何处理 |
| setVisible(visible: Boolean) | this | 设置图层是否可见 |
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
| getMap() | Map | 获取地图对象,若为空返回null |
| getGeometries() | RectangleGeometry[] | 获取多矩形数据 |
| getStyles() | MultiRectangleStyleHash | 获取图层相关样式信息 |
| getVisible() | visible | 获取可见状态 |
| getZIndex() | Number | 获取图层绘制顺序 |
| getGeometryById(id:String) | RectangleGeometry | 根据多矩形数据id来获取点数据 |
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
| updateGeometries(geometry: RectangleGeometry[]) | this | 更新多矩形数据,如果geometry的id存在于集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的矩形添加到集合中;如果参数为null或undefined不会做任何处理 |
| add(geometries: RectangleGeometry[]) | this | 向图层中添加矩形,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的多边形存在重复id,这些多边形会被重新分配id;如果参数为null或undefined不会做任何处理 |
| remove(ids: String[]) | this | 移除指定id的矩形,如果参数为null或undefined不会做任何处理 |
| destroy() | | 销毁图层对象 |
## 静态方法
| 方法名 | 返回值 | 说明 |
| :--------- | :------------------- | :--------------------------------------- |
| getBounds(geometry: RectangleGeometry)| LatLngBounds | 根据geometry获取矩形的左下角和右上角顶点经纬度坐标 |
**事件:**</br>
监听事件通过on、off方法绑定与解绑。
| 事件名 | 参数 | 说明 |
| :--------- | :------------------- | :--------------------------------------- |
| click | GeometryOverlayEvent | 点击事件 |
| dblclick | GeometryOverlayEvent | 双击事件 |
| mousedown | GeometryOverlayEvent | 鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发 |
| mouseup | GeometryOverlayEvent | 鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发 |
| mousemove | GeometryOverlayEvent | 鼠标在地图上移动时触发,只在桌面浏览器中触发 |
| hover | GeometryOverlayEvent | 鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的RectangleGeometry[],无图形时事件对象为null,只在桌面浏览器中触发 |
| touchstart | GeometryOverlayEvent | 在地图区域触摸开始时触发,只在移动浏览器中触发 |
| touchmove | GeometryOverlayEvent | 在地图区域触摸移动时触发,只在移动浏览器中触发 |
| touchend | GeometryOverlayEvent | 在地图区域触摸结束时触发,只在移动浏览器中触发 |
<br><br>
## MultiRectangleOptions 对象规范{#MultiRectangleOptions}
MultiRectangle配置参数。
| 名称 | 类型 | 说明 |
| :--------- | :----------------------------------------------------------- | :--------------------------- |
| id | String | 图层id,若没有会自动分配一个 |
| map | Map | 显示多矩形图层的底图 |
| zIndex | Number | 图层绘制顺序 |
| styles | MultiRectangleStyleHash | 矩形的相关样式 |
| geometries | RectangleGeometry[] | 矩形数据数组 |
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
<br>
## MultiRectangleStyleHash 对象规范{#MultiRectangleStyleHash}
一个key-value形式对象, 表示多矩形图层的相关样式信息,key使用字符串,value是RectangleStyle。
<br><br>
## RectangleStyle{#RectangleStyle}
表示应用于矩形图层的样式类型。
| 构造函数 |
| :-------------------------------------------------------- |
| new TMap.RectangleStyle(options: RectangleStyleOptions); |
<br>
## RectangleStyleOptions 对象规范{#RectangleStyleOptions}
RectangleStyle配置参数。
| 属性名称 | 类型 | 说明 |
| :---------- | :------ | :----------------------------------------------------------- |
| color | String | 面填充色,支持rgb(),rgba(),#RRGGBB等形式 |
| showBorder | Boolean | 是否显示边线,默认为true |
| borderColor | String | 边线颜色,支持rgb(),rgba(),#RRGGBB等形式,默认为#3777FF,showBorder为true时有效 |
| borderWidth | Number | 折线宽度,正整数,单位为像素,指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差,默认为2,showBorder为true时有效 |
## RectangleGeometry 对象规范{#RectangleGeometry}
-----
多矩形数据。
| 名称 | 类型 | 说明 |
| :--------- | :----- | :----------------------------------------------------------- |
| id | String | 多矩形图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个 |
| styleId | String | 对应MultiRectangleStyleHash中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制 |
| center | LatLng | 矩形的中心点位置 |
| width | Number | 矩形的宽,正数,单位为米 |
| height | Number | 矩形的高,正数,单位为米 |
| properties | Object | 矩形的属性数据 |
| rank | Number | 矩形的图层内绘制顺序 |
</br></br>
## MultiCircle {#13}
----
表示地图上的多个圆形,可以自定义每个圆形的样式。
|构造函数|
|:- |
|new TMap.MultiCircle(options);|
|参数名 |类型 |说明|
| :- | :- |:- |
|options |MultiCircleOptions |多圆的参数对象|
|方法 |返回值 |说明|
| :- | :- | :- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将多圆同时从地图中移除|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序|
|setGeometries(geometries:CircleGeometry[]) |this |更新多圆数据,如果参数为null或undefined不会做任何处理|
|setStyles(styles:MultiCircleStyleHash) |this |设置MultiCircle图层相关样式信息,如果参数为null或undefined不会做任何处理|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
|getMap() |Map |获取地图对象,若为空返回null|
|getGeometries() |CircleGeometry[] |获取多圆数据|
|getStyles() |MultiCircleStyleHash |获取图层相关样式信息|
|getVisible() |visible |获取可见状态|
|getZIndex() |Number |获取图层绘制顺序|
|getGeometryById(id:String) |CircleGeometry |根据多圆数据id来获取点数据|
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
|updateGeometries(geometry: CircleGeometry[]) |this |更新多圆数据,如果geometry的id存在于集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的圆添加到集合中;如果参数为null或undefined不会做任何处理|
|add(geometries: CircleGeometry[]) |this |向图层中添加圆,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的多边形存在重复id,这些多边形会被重新分配id;如果参数为null或undefined不会做任何处理|
|remove(ids: String[]) |this |移除指定id的圆,如果参数为null或undefined不会做任何处理|
| destroy() | | 销毁图层对象 |
**事件:**</br>
监听事件通过on、off方法绑定与解绑。
|事件名 |参数 |说明|
| :- | :- | :- |
|click |GeometryOverlayEvent |点击事件|
|dblclick |GeometryOverlayEvent|双击事件|
|mousedown |GeometryOverlayEvent|鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发|
|mouseup |GeometryOverlayEvent|鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发|
|mousemove |GeometryOverlayEvent|鼠标在地图上移动时触发,只在桌面浏览器中触发|
|hover |GeometryOverlayEvent|鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的CircleGeometry,无图形时事件对象为null,只在桌面浏览器中触发|
|touchstart |GeometryOverlayEvent|在地图区域触摸开始时触发,只在移动浏览器中触发|
|touchmove |GeometryOverlayEvent|在地图区域触摸移动时触发,只在移动浏览器中触发|
|touchend |GeometryOverlayEvent|在地图区域触摸结束时触发,只在移动浏览器中触发|
</br></br>
## MultiCircleOptions {#14}
----
MultiCircle配置参数。
|属性名称 |类型 |说明|
| :- | :- | :- |
|id |String |图层id,若没有会自动分配一个|
|map |Map |显示多圆图层的底图|
|zIndex |Number |图层绘制顺序|
|styles |MultiCircleStyleHash |圆的相关样式|
|geometries |CircleGeometry[] |圆数据数组|
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
</br></br>
## MultiCircleStyleHash {#15}
----
一个key-value形式对象, 表示多圆图层的相关样式信息,key使用字符串,value是 CircleStyle。
</br></br>
## CircleGeometry {#16}
----
多圆数据。
|属性名称 |类型 |说明|
| :- | :- | :- |
|id |String |多圆图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个|
|styleId |String |对应MultiCircleStyleHash中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制|
|center |LatLng |圆的中心点位置|
|radius |Number |圆的半径,正数,单位为米|
|properties |Object |圆的属性数据|
| rank | Number | 圆的图层内绘制顺序 |
</br></br>
## CircleStyle {#17}
----
表示应用于圆图层的样式类型。
|构造函数|
| :- |
|new TMap.CircleStyle(options);|
|参数名 |类型 |说明|
| :- |:- |:- |
|options |CircleStyleOptions |圆的样式信息|
</br></br>
## CircleStyleOptions {#18}
----
CircleStyle配置参数。
|属性名称 |类型 |说明|
| :- |:- |:- |
|color |String |面填充色,支持rgb(),rgba(),#RRGGBB等形式|
|showBorder |Boolean |是否显示边线,默认为true|
|borderColor |String |边线颜色,支持rgb(),rgba(),#RRGGBB等形式,默认为#3777FF,showBorder为true时有效|
|borderWidth |Number |折线宽度,正整数,单位为像素,指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差,默认为2,showBorder为true时有效|
<br><br>
## MultiEllipse{#MultiEllipse}
-----------
表示地图上的多个椭圆,可以自定义每个椭圆的样式。
| 构造函数 |
| ------------------------------------------------------------ |
| new TMap.MultiEllipse(options:MultiEllipseOptions); |
| 方法名 | 返回值 | 说明 |
| :----------------------------------------------------------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| setMap(map:Map) | this | 设置地图对象,如果map为null意味着将多椭圆同时从地图中移除 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序 |
| setGeometries(geometries:EllipseGeometry[]) | this | 更新多椭圆数据,如果参数为null或undefined不会做任何处理 |
| setStyles(styles:MultiEllipseStyleHash) | this | 设置MultiEllipse图层相关样式信息,如果参数为null或undefined不会做任何处理 |
| setVisible(visible: Boolean) | this | 设置图层是否可见 |
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
| getMap() | Map | 获取地图对象,若为空返回null |
| getGeometries() | EllipseGeometry[] | 获取多椭圆数据 |
| getStyles() | MultiEllipseStyleHash | 获取图层相关样式信息 |
| getVisible() | visible | 获取可见状态 |
| getZIndex() | Number | 获取图层绘制顺序 |
| getGeometryById(id:String) | EllipseGeometry | 根据多椭圆数据id来获取点数据 |
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
| updateGeometries(geometry: EllipseGeometry[]) | this | 更新多椭圆数据,如果geometry的id存在于集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的椭圆添加到集合中;如果参数为null或undefined不会做任何处理 |
| add(geometries: EllipseGeometry[]) | this | 向图层中添加椭圆,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的多边形存在重复id,这些多边形会被重新分配id;如果参数为null或undefined不会做任何处理 |
| remove(ids: String[]) | this | 移除指定id的椭圆,如果参数为null或undefined不会做任何处理 |
| destroy | | 销毁图层对象 |
| 事件名 | 参数 | 说明 |
| :--------- | :------------------- | :----------------------------------------------------------- |
| click | GeometryOverlayEvent | 点击事件 |
| dblclick | GeometryOverlayEvent | 双击事件 |
| mousedown | GeometryOverlayEvent | 鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发 |
| mouseup | GeometryOverlayEvent | 鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发 |
| mousemove | GeometryOverlayEvent | 鼠标在地图上移动时触发,只在桌面浏览器中触发 |
| hover | GeometryOverlayEvent | 鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的EllipseGeometry,无图形时事件对象为null,只在桌面浏览器中触发 |
| touchstart | GeometryOverlayEvent | 在地图区域触摸开始时触发,只在移动浏览器中触发 |
| touchmove | GeometryOverlayEvent | 在地图区域触摸移动时触发,只在移动浏览器中触发 |
| touchend | GeometryOverlayEvent | 在地图区域触摸结束时触发,只在移动浏览器中触发 |
<br>
## MultiEllipseOptions 对象规范{#MultiEllipseOptions}
---------
MultiEllipse配置参数。
| 名称 | 类型 | 说明 |
| :--------- | :----------------------------------------------------------- | :--------------------------- |
| id | String | 图层id,若没有会自动分配一个 |
| map | Map | 显示多椭圆图层的底图 |
| zIndex | Number | 图层绘制顺序 |
| styles | MultiEllipseStyleHash | 椭圆的相关样式 |
| geometries | EllipseGeometry[] | 椭圆数据数组 |
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
<br><br>
## MultiEllipseStyleHash 对象规范{#MultiEllipseStyleHash}
一个key-value形式对象, 表示多椭圆图层的相关样式信息,key使用字符串,value是EllipseStyle。
<br><br>
## EllipseStyle{#EllipseStyle}
表示应用于椭圆图层的样式类型。
| 构造函数 |
| :------------------------------------------------------------ |
| new TMap.EllipseStyle(options:EllipseStyleOptions); |
<br><br>
## EllipseStyleOptions 对象规范{#EllipseStyleOptions}
EllipseStyle配置参数。
| 名称 | 类型 | 说明 |
| :---------- | :------ | :----------------------------------------------------------- |
| color | String | 面填充色,支持rgb(),rgba(),#RRGGBB等形式 |
| showBorder | Boolean | 是否显示边线,默认为true |
| borderColor | String | 边线颜色,支持rgb(),rgba(),#RRGGBB等形式,默认为#3777FF,showBorder为true时有效 |
| borderWidth | Number | 折线宽度,正整数,单位为像素,指的是地图pitch为0时的屏幕像素大小,如果pitch不为0,实际绘制出来的线宽度与屏幕像素会存在一定误差,默认为2,showBorder为true时有效 |
<br><br>
## EllipseGeometry 对象规范{#EllipseGeometry}
多椭圆数据。
| 名称 | 类型 | 说明 |
| :---------- | :----- | :----------------------------------------------------------- |
| id | String | 多椭圆图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个 |
| styleId | String | 对应 MultiEllipseStyleHash中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制 |
| center | LatLng | 椭圆的中心点位置 |
| majorRadius | Number | 椭圆的主半径,正数,单位为米 |
| minorRadius | Number | 椭圆的辅半径,正数,单位为米 |
| properties | Object | 椭圆的属性数据 |
| rank | Number | 椭圆的图层内绘制顺序 |
FILE:references/jsapigl/docs/信息窗体.md
## InfoWindow {#1}
----
用于创建信息窗覆盖物。
|构造函数|
| :- |
|TMap.InfoWindow(options:InfoWindowOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setPosition(position:LatLng) |this |设置经纬度位置。|
|setContent(content:String) |this |设置信息窗显示内容。|
|setMap(map:Map) |this |设置信息窗口所在的map对象,传入null则代表将infoWindow从Map中移除。|
|getMap() |Map |获取信息窗口所在的map对象。|
|open() |this |打开信息窗口。|
|close() |this |关闭信息窗口。|
|destroy() |this |销毁信息窗。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
|事件名 |参数 |说明|
| :- | :- |:- |
|closeclick |none |点击信息窗的关闭按钮时会触发此事件。|
**示例:**</br>
本示例中,介绍创建信息框
html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>添加信息窗口</title>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<script charset="utf-8" src="https://map.qq.com/api/js?v=2.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77"></script>
</head>
<body onload="initMap();">
<div id="container"></div>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);//设置中心点坐标
//初始化地图
var map = new TMap.Map("container", {
center: center
});
//初始化infoWindow
var infoWindow = new TMap.InfoWindow({
map: map,
position: center, //设置信息框位置
content: "Hello World!" //设置信息框内容
});
}
</script>
</body>
</html>
新窗口打开 在线试一试
</br></br>
## InfoWindowOptions {#2}
----
信息窗配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|map |Map |(必需)显示信息窗的地图。|
|position |LatLng |(必需)信息窗的经纬度坐标。|
|content |String |信息窗显示内容,默认为空字符串。当enableCustom为true时,需传入信息窗体的dom字符串。 查看示例|
|zIndex |Number |信息窗的z-index值,默认为0。|
|offset |Object |信息窗相对于position对应像素坐标的偏移量,x方向向右偏移为正值,y方向向下偏移为正值,默认为{x:0, y:0}。|
| enableCustom | Boolean | 信息窗体样式是否为自定义,默认为false。
FILE:references/jsapigl/docs/附加库:服务类库.md
## 地点搜索 {#1}
### Search
---
提供多种搜索功能。
|构造函数|
| :- |
|new TMap.service.Search(options)|
|参数名称|类型|说明|
| :- | :- | -- |
|options | Object |搜索类配置参数对象规范见下表|
| 名称 | 类型 | 是否必填 | 说明 |
| -------- | ------ | -------- | -------------------------------------- |
| pageSize | Number | 否 | 每页条目数,最大限制为20条,默认为10条 |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| searchNearby(params: SearchNearByParams) | Promise | 搜索某地周围的一个圆形范围符合指定关键字的地点;搜索完成后resolve状态下返回SearchResult,reject状态下返回ErrorResult |
| searchRegion(params: SearchRegionParams) | Promise | 搜索某地区cityName附近符合给定关键字的地点;搜索完成后resolve状态下返回SearchResult,reject状态下返回ErrorResult |
| searchRectangle(params: SearchRectangleParams) | Promise | 在矩形范围内以给定关键定搜索地点;搜索完成后resolve状态下返回SearchResult,reject状态下返回ErrorResult |
| explore(params: ExploreParams) | Promise | 只需提供搜索中心点及半径(无须关键词),即可搜索获取周边高热度地点;搜索完成后resolve状态下返回SearchResult,reject状态下返回ErrorResult |
| here(params: HereParams) | Promise | 与explore相似,但本接口侧重于以地标+主要的路+近距离POI为主;搜索完成后resolve状态下返回SearchResult,reject状态下返回ErrorResult |
| searchPoiId(params: SearchPoiIdParams) | Promise | 通过POI的id查询详细信息;搜索完成后resolve状态下返回SearchResult,reject状态下返回ErrorResult |
| setPageSize(pageSize:Number) | this | 设置每页返回的结果数量 |
| getPageSize() | Number | 获取每页返回的结果数量 |
<br>
### SearchNearbyParams {#2}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ---------- | ----------- | -------- | ------------------------------------------------------------ |
| keyword | String | 是 | 搜索关键字 |
| center | TMap.LatLng | 是 | 搜索中心点的经纬度 |
| radius | Number | 是 | 搜索半径,取值范围:10到1000 |
| autoExtend | Boolean | 否 | 当前范围无结果时,是否自动扩大范围,取值:<br/>false:不扩大;<br/>true [默认]:自动扩大范围(依次按照按1公里、2公里、5公里,最大到全城市范围搜索) |
| filter | String | 否 | 筛选条件,用法详见下方filter参数用法 |
| orderby | String | 否 | 排序,支持按距离由近到远排序,取值:_distance |
| pageIndex | Number | 否 | 第x页,默认第1页 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### SearchRegionParams {#3}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ----------------- | ----------- | -------- | ------------------------------------------------------------ |
| keyword | String | 是 | 搜索关键字 |
| cityName | String | 是 | 检索城市名称, 如北京市,同时支持adcode(行政区划代码,可精确到区县级),如130681 |
| autoExtend | Boolean | 否 | 当前范围无结果时,是否自动扩大范围,取值:<br/>false:不扩大;<br/>true [默认]:自动扩大范围(依次按照按1公里、2公里、5公里,最大到全城市范围搜索) |
| referenceLocation | TMap.LatLng | 否 | 当keyword使用酒店、超市等泛分类关键词时,这类场景大多倾向于搜索附近,传入此经纬度,搜索结果会优先就近地点,体验更优 |
| filter | String | 否 | 筛选条件,用法详见下方filter参数用法 |
| pageIndex | Number | 否 | 第x页,默认第1页 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### SearchRectangleParams {#4}
---
| 名称 | 类型 | 是否必填 | 说明 |
| --------- | ----------------- | -------- | ------------------------------------------------------------ |
| keyword | String | 是 | 搜索关键字 |
| bounds | TMap.LatLngBounds | 是 | 检索范围 |
| filter | String | 否 | 筛选条件,用法详见下方filter参数用法 |
| pageIndex | Number | 否 | 第x页,默认第1页 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### ExploreParams {#5}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------------- | ----------- | -------- | ------------------------------------------------------------ |
| center | TMap.LatLng | 是 | 搜索中心点的经纬度 |
| radius | Number | 是 | 搜索半径,取值范围:10到1000 |
| autoExtend | Boolean | 否 | 当前范围无结果时,是否自动扩大范围,取值:<br/>0:不扩大;<br/>1 [默认]:自动扩大范围(依次按照按1公里、2公里、5公里,最大到全城市范围搜索)。 |
| policy | Number | 否 | 搜索策略,可选值:<br/>1 [默认]:地点签到场景,针对用户签到的热门 地点进行优先排序;<br/>2 :位置共享场景,用于发送位置、位置分享等场景的热门地点优先排序 |
| addressFormat | String | 否 | 地址格式,可选值:short,返回不包含省市区的短地址<br/>(缺省侧为包含省市区的标准地址) |
| filter | String | 否 | 筛选条件,用法详见下方filter参数用法 |
| orderby | String | 否 | 排序,支持按距离由近到远排序,取值:_distance |
| pageIndex | Number | 否 | 第x页,默认第1页 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### HereParams {#6}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------------- | ----------- | -------- | ------------------------------------------------------------ |
| center | TMap.LatLng | 是 | 搜索中心点的经纬度 |
| radius | Number | 是 | 搜索半径,取值范围:10到1000 |
| policy | Number | 否 | 搜索策略:<br/>policy=1[默认]:以地标+主要的路+近距离POI为主,着力描述当前位置;<br/>policy=2 到家场景:筛选合适收货的POI,并会细化收货地址,精确到楼栋;<br/>policy=3 出行场景:过滤掉车辆不易到达的POI(如一些景区内POI),增加道路出入口、交叉口、大区域出入口类POI,排序会根据真实API大用户的用户点击自动优化 |
| addressFormat | String | 否 | 地址格式,可选值:short,返回不包含省市区的短地址<br/>(缺省侧为包含省市区的标准地址) |
| filter | String | 否 | 筛选条件,用法详见下方filter参数用法 |
| orderby | String | 否 | 排序,支持按距离由近到远排序,取值:_distance |
| pageIndex | Number | 否 | 第x页,默认第1页 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br >
### SearchPoiIdParams {#7}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ---- | ------ | -------- | --------------------------- |
| id | String | 是 | 腾讯地图POI(地点)唯一标识 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### Search 类的filter 参数用法 {#8}
---
| 说明 | 示例 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| 1. 指定分类筛选,语句格式为:<br/>category=分类名1,分类名2<br/>最多支持5个分类词(支持的分类请参考:POI分类表)<br/>2. 排除指定分类,语句格式为:<br/>category<>分类名1,分类名2<br/>最多支持5个分类词(支持的分类请参考:POI分类表)<br/>3. 筛选有电话的地点:tel<>null | **搜索指定分类**<br/>filter=category=公交站<br/>**搜索多个分类**<br/>filter=category=大学,中学<br/>**排除指定分类**<br/>filter=category<>商务楼宇 |
<br>
### SearchResult {#9}
---
| 名称 | | | 类型 | 说明 |
| ------- | --------- | -------- | ----------------------------- | ------------------------------------------------------------ |
| status | | | Number | 状态码,0为正常,其它为异常,详细请参阅状态码说明 |
| message | | | String | 状态说明 |
| count | | | Number | 本次搜索结果总数,另外本服务限制最多返回200条数据(data), 翻页(pageIndex)超过搜索结果总数 或 最大200条限制时,将返回最后一页数据 |
| data | | | Array(数组内各元素结构如下) | 搜索结果POI数组,每项为一个POI对象 |
| | id | | String | POI(地点)唯一标识 |
| | title | | String | POI(地点)名称 |
| | address | | String | 地址(不含省市区信息的短地址) |
| | tel | | String | 电话 |
| | category | | String | POI分类 |
| | type | | Number | POI类型,值说明:0:普通POI / 1:公交车站 / 2:地铁站 / 3:公交线路 / 4:行政区划 |
| | location | | TMap.LatLng | 坐标 |
| | _distance | | Number | 距离,单位: 米,在周边搜索、城市范围搜索传入定位点时返回 |
| | ad_info | | Object(对象结构如下) | 行政区划信息 |
| | | adcode | Number | 行政区划代码,详见:行政区划代码说明 |
| | | province | String | 省 |
| | | city | String | 市 |
| | | district | String | 区 |
<br>
## 关键词输入提示 {#10}
### Suggestion
---
用于获取输入关键字的补完与提示,以便帮助用户快速输入。
|构造函数|
| :- |
|new TMap.service.Suggestion(options)|
|参数名称|类型|说明|
| :- | :- | -- |
|options | Object |关键字提示类配置参数对象规范见下表|
| 名称 | 类型 | 是否必填 | 说明 |
| --------- | ------- | -------- | ------------------------------------------------------------ |
| pageSize | Number | 否 | 每页条目数,最大限制为20条,默认为10条 |
| region | String | 否 | 限制城市范围,根据城市名称限制地域范围, 如,仅获取“广州市”范围内的提示内容;缺省时侧进行全国范围搜索 |
| regionFix | Boolean | 否 | false:[默认]当前城市无结果时,自动扩大范围到全国匹配;<br/>true:固定在当前城市 |
| policy | Number | 否 | 检索策略,目前支持:<br/>policy=0:默认,常规策略;<br/>policy=1:本策略主要用于收货地址、上门服务地址的填写,提高了小区类、商务楼宇、大学等分类的排序,过滤行政区、道路等分类(如海淀大街、朝阳区等),排序策略引入真实用户对输入提示的点击热度,使之更为符合此类应用场景,体验更为舒适<br/>policy=10:出行场景(网约车) – 起点查询;<br/>policy=11:出行场景(网约车) – 终点查询 |
| filter | String | 否 | 筛选条件:<br/>基本语法:columnName<筛选列>=value<列值>;<br/>目前支持按POI分类筛选(例:category=分类词),若指定多个分类用英文逗号分隔,最多支持五个分类,支持的分类词可参考WebService API的相关说明 |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| getSuggestions(params: GetSuggestionsParams) | Promise | 获取输入关键字的补完与提示,帮助用户快速输入;搜索完成后resolve状态下返回SuggestionResult,reject状态下返回ErrorResult |
| setPageSize(pageSize:Number) | this | 设置每页返回的结果数量 |
| setRegion(region: String) | this | 设置城市范围 |
| setRegionFix(regionFix: Boolean) | this | 设置是否扩大匹配 |
| setPolicy(policy: Number) | this | 设置检索策略 |
| setFilter(filter: String) | this | 设置筛选条件 |
| getPageSize() | Number | 获取每页返回的结果数量 |
| getRegion() | String | 获取城市范围 |
| getRegionFix() | Boolean | 获取是否扩大匹配 |
| getPolicy() | Number | 获取检索策略 |
| getFilter() | String | 获取筛选条件 |
<br>
### GetSuggestionsParams {#11}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------------- | ----------- | -------- | ------------------------------------------------------------ |
| keyword | String | 是 | 希望获取输入提示的关键字 |
| location | TMap.LatLng | 否 | 定位坐标,传入后,若用户搜索关键词为类别词(如酒店、餐馆时),与此坐标距离近的地点将靠前显示 |
| getSubpois | Boolean | 否 | 是否返回子地点,如大厦停车场、出入口等取值:<br/>false [默认]:不返回;<br/>true:返回 |
| addressFormat | String | 否 | 地址格式,可选值:short,返回不包含省市区的短地址<br/>(缺省侧为包含省市区的标准地址) |
| pageIndex | Number | 否 | 第x页,默认第1页 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### SuggestionResult {#12}
---
| 名称 | | 类型 | 是否必带 | 说明 |
| :------- | --------- | :---------------------------- | :------- | :----------------------------------------------------------- |
| status | | Number | 是 | 状态码,0为正常,其它为异常,详细请参阅状态码说明 |
| message | | String | 是 | 状态说明 |
| count | | Number | 是 | 结果总数(注:本服务一个查询条件最多返回100条结果) |
| data | | Array(数组内各元素结构如下) | 是 | 提示词数组,每项为一个POI对象 |
| | id | String | 是 | POI唯一标识 |
| | title | String | 是 | 提示文字 |
| | address | String | 是 | 地址 |
| | province | String | 是 | 省 |
| | city | String | 是 | 市 |
| | adcode | String | 是 | 行政区划代码 |
| | type | Number | 是 | POI类型,值说明:0:普通POI / 1:公交车站 / 2:地铁站 / 3:公交线路 / 4:行政区划 |
| | _distance | Number | 否 | 传入location(定位坐标)参数时,返回定位坐标到各POI的距离 |
| | location | TMap.LatLng | 是 | 提示所述位置坐标 |
| sub_pois | | Array(数组内各元素结构如下) | 否 | 子地点列表,仅在输入参数getSubpois=true时返回 |
| | parent_id | String | 是 | 主地点ID,对应data中的地点ID |
| | id | String | 是 | 地点唯一标识 |
| | title | String | 是 | 地点名称 |
| | address | String | 是 | 地址 |
| | location | TMap.LatLng | 是 | 坐标 |
| | adcode | Number | 是 | 行政区划代码 |
| | city | String | 是 | 地点所在城市名称 |
<br>
## 正逆地址解析 {#13}
### Geocoder
---
提供文字地址与经纬度之间的转换工具。
|构造函数|
| :- |
|new TMap.service.Geocoder()|
| 方法 | 返回值 | 说明 |
| -------------------------------------- | ------- | ------------------------------------------------------------ |
| getAddress(params: GetAddressParams) | Promise | 提供由经纬度到文字地址及相关位置信息的转换能力;搜索完成后resolve状态下返回AddressResult,reject状态下返回ErrorResult |
| getLocation(params: GetLocationParams) | Promise | 根据指定的文字地址转换为经纬度,并同时提供结构化的省市区地址信息;搜索完成后resolve状态下返回LocationResult,reject状态下返回ErrorResult |
<br>
### GetAddressParams {#14}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ---------- | ----------- | -------- | ------------------------------------------------------------ |
| location | TMap.LatLng | 是 | 经纬度(GCJ02坐标系) |
| getPoi | Boolean | 否 | 是否返回周边地点(POI)列表,可选值:<br/>false:不返回(默认);<br/>true:返回 |
| poiOptions | String | 否 | 周边POI列表控制参数,详见下方poiOptions用法 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### GetLocationParams {#15}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------- | ------ | -------- | ---------------------------------------------------- |
| address | String | 是 | 地址(注:地址中请包含城市名称,否则会影响解析效果) |
| region | String | 否 | 地址所在城市(若地址中包含城市名称侧可不传) |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### Geocoder类的 poiOptions 用法 {#16}
---
| 说明 | 示例 |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| 周边POI列表控制参数:<br/>1 poiOptions=address_format=short<br/>返回短地址,缺省时返回长地址<br/>2 poiOptions=radius=5000<br/>半径,取值范围 1-5000(米)<br/>3 poiOptions=policy=1/2/3/4/5<br/>控制返回场景,<br/>policy=1[默认] 以地标+主要的路+近距离POI为主,着力描述当前位置;<br/>policy=2 到家场景:筛选合适收货的POI,并会细化收货地址,精确到楼栋;<br/>policy=3 出行场景:过滤掉车辆不易到达的POI(如一些景区内POI),增加道路出入口、交叉口、大区域出入口类POI,排序会根据真实API大用户的用户点击自动优化<br/>policy=4 社交签到场景,针对用户签到的热门 地点进行优先排序<br/>policy=5 位置共享场景,用户经常用于发送位置、位置分享等场景的热门地点优先排序<br/>4 注:本接口最多返回10条周边POI,如需更多请参见地点搜索-周边推荐 | 【单个参数写法示例】:<br/>poiOptions=address_format=short<br/>【多个参数英文分号间隔,写法示例】:<br/>poiOptions=address_format=short;radius=5000;policy=2 |
<br>
### AddressResult {#17}
---
| 名称 | | | | 类型 | 是否必带 | 说明 |
| :--------- | ------------------- | ------------- | --------- | :---------------------------- | :------- | :----------------------------------------------------------- |
| status | | | | Number | 是 | 状态码,0为正常,其它为异常,详细请参阅状态码说明 |
| message | | | | String | 是 | 状态说明 |
| request_id | | | | String | 是 | 本次请求的唯一标识 |
| result | | | | Object(对象结构如下) | 是 | 逆地址解析结果 |
| | address | | | String | 是 | 以行政区划+道路+门牌号等信息组成的标准格式化地址 |
| | formatted_addresses | | | Object(对象结构如下) | 否 | 结合知名地点形成的描述性地址,更具人性化特点 |
| | | recommend | | String | 否 | 推荐使用的地址描述,描述精确性较高 |
| | | rough | | String | 否 | 粗略位置描述 |
| | address_component | | | Object(对象结构如下) | 是 | 地址部件,address不满足需求时可自行拼接 |
| | | nation | | String | 是 | 国家 |
| | | province | | String | 是 | 省 |
| | | city | | String | 是 | 市 |
| | | district | | String | 否 | 区,可能为空字串 |
| | | street | | String | 否 | 街道,可能为空字串 |
| | | street_number | | String | 否 | 门牌,可能为空字串 |
| | ad_info | | | Object(对象结构如下) | 是 | 行政区划信息 |
| | | nation_code | | String | 是 | 国家代码(ISO3166标准3位数字码) |
| | | adcode | | String | 是 | 行政区划代码,规则详见:行政区划代码说明 |
| | | city_code | | String | 是 | 城市代码,由国家码+行政区划代码(提出城市级别)组合而来,总共为9位 |
| | | name | | String | 是 | 行政区划名称 |
| | | location | | TMap.LatLng | 是 | 行政区划中心点坐标 |
| | | nation | | String | 是 | 国家 |
| | | province | | String | 是 | 省 / 直辖市 |
| | | city | | String | 是 | 市 / 地级区 及同级行政区划 |
| | | district | | String | 否 | 区 / 县级市 及同级行政区划 |
| | address_reference | | | Object(对象结构如下) | 否 | 坐标相对位置参考 |
| | | famous_area | | Object(对象结构如下) | 否 | 知名区域,如商圈或人们普遍认为有较高知名度的区域 |
| | | | id | String | 是 | 地点唯一标识 |
| | | | title | String | 否 | 名称/标题 |
| | | | location | TMap.LatLng | 否 | 坐标 |
| | | | _distance | Number | 否 | 此参考位置到输入坐标的直线距离 |
| | | | _dir_desc | String | 否 | 此参考位置到输入坐标的方位关系,如:北、南、内 |
| | | business_area | | Object | 否 | 商圈【注】:对象结构同 famous_area |
| | | town | | Object(对象结构如下) | 否 | 乡镇街道 |
| | | | id | String | 是 | 地点唯一标识 |
| | | | title | String | 否 | 名称/标题 |
| | | | location | TMap.LatLng | 否 | 坐标 |
| | | | _distance | Number | 否 | 此参考位置到输入坐标的直线距离 |
| | | | _dir_desc | String | 否 | 此参考位置到输入坐标的方位关系,如:北、南、内 |
| | | landmark_l1 | | Object | 否 | 一级地标,可识别性较强、规模较大的地点、小区等 【注】对象结构同 famous_area |
| | | landmark_l2 | | Object | 否 | 二级地标,较一级地标更为精确,规模更小 【注】对象结构同 famous_area |
| | | street | | Object | 否 | 街道 【注】对象结构同 famous_area |
| | | street_number | | Object | 否 | 门牌 【注】对象结构同 famous_area |
| | | crossroad | | Object | 否 | 交叉路口 【注】对象结构同 famous_area |
| | | water | | Object | 否 | 水系 【注】对象结构同 famous_area |
| | poi_count | | | Number | 否 | 查询的周边poi的总数,仅在传入参数getPoi=1时返回 |
| | pois | | | Array(数组内各元素结构如下) | 否 | 周边地点(POI)数组,数组中每个子项为一个POI对象 |
| | | id | | String | 否 | 地点(POI)唯一标识 |
| | | title | | String | 否 | 名称 |
| | | address | | String | 否 | 地址 |
| | | category | | String | 否 | 地点分类信息 |
| | | location | | TMap.LatLng | 否 | 提示所述位置坐标 |
| | | ad_info | | Object(对象结构如下) | 否 | 行政区划信息 |
| | | | adcode | Number | 是 | 行政区划代码 |
| | | | province | String | 是 | 省 |
| | | | city | String | 是 | 市 |
| | | | district | String | 是 | 区 |
| | | _distance | | Number | 否 | 该POI到逆地址解析传入的坐标的直线距离 |
<br>
### LocationResult {#18}
---
| 名称 | | | 类型 | 是否必带 | 说明 |
| :------ | ------------------ | ------------- | :--------------------- | :------- | :----------------------------------------------------------- |
| status | | | Number | 是 | 状态码,0为正常,其它为异常,详细请参阅状态码说明 |
| message | | | String | 是 | 状态说明 |
| result | | | Object(对象结构如下) | 是 | 地址解析结果 |
| | title | | String | 是 | 解析到坐标所用到的关键地址、地点 |
| | location | | TMap.LatLng | 是 | 解析到的坐标(GCJ02坐标系) |
| | address_components | | Object(对象结构如下) | 是 | 解析后的地址部件 |
| | | province | String | 是 | 省 |
| | | city | String | 是 | 市 |
| | | district | String | 是 | 区,可能为空字串 |
| | | street | String | 是 | 街道,可能为空字串 |
| | | street_number | String | 是 | 门牌,可能为空字串 |
| | ad_info | | Object(对象结构如下) | 是 | 行政区划信息 |
| | | adcode | String | 是 | 行政区划代码,规则详见:行政区划代码说明 |
| | similarity | | Number | 是 | 即将下线,由reliability代替 |
| | deviation | | Number | 是 | 即将下线,由level代替 |
| | reliability | | Number | 是 | 可信度参考:值范围 1 <低可信> - 10 <高可信> 我们根据用户输入地址的准确程度,在解析过程中,将解析结果的可信度(质量),由低到高,分为1 - 10级,该值>=7时,解析结果较为准确,<7时,会存各类不可靠因素,开发者可根据自己的实际使用场景,对于解析质量的实际要求,进行参考 |
| | level | | Number | 否 | 解析精度级别,分为11个级别,一般>=9即可采用(定位到点,精度较高) 也可根据实际业务需求自行调整,完整取值表见WebService API的相关说明 |
<br>
## 路线规划 {#19}
### Driving
---
提供驾车路线规划服务,支持结合实时路况、少收费、不走高速等多种偏好,精准预估到达时间。
|构造函数|
| :- |
|new TMap.service.Driving(options)|
|参数名称|类型|说明|
| :- | :- | -- |
|options | Object |驾车路线规划服务类配置参数对象规范见下表|
| 名称 | 类型 | 是否必填 | 说明 |
| ----------- | ------- | -------- | ------------------------------------------------------------ |
| policy | String | 否 | 检索策略<br />一、策略参数(以下三选一)<br/>LEAST_TIME:[默认]参考实时路况,时间最短;<br/>PICKUP:网约车场景 – 接乘客;<br/>TRIP:网约车场景 – 送乘客<br/>二、单项偏好参数<br/>(可与策略参数并用,可多选,逗号分隔)<br/>REAL_TRAFFIC:参考实时路况;<br/>LEAST_FEE:少收费;<br/>AVOID_HIGHWAY:不走高速;<br/>NAV_POINT_FIRST: 该策略会通过终点坐标查找所在地点(如小区/大厦等),并使用地点出入口做为目的地,使路径更为合理 |
| mp | Boolean | 否 | 是否返回多种方案:<br />true:返回多种方案;<br />false:返回一种方案 |
| noStep | Boolean | 否 | 是否不需路线引导:<br />true: 不需路线引导;<br />false:需要路线引导 |
| plateNumber | String | 否 | 车牌号,填入后,路线引擎会根据车牌对限行区域进行避让,不填则不不考虑限行问题 |
| carType | Number | 否 | 车辆类型:<br />0:[默认]普通汽车;<br/>1:新能源 |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| search(params: DrivingSearchParams) | Promise | 搜索驾车路线;搜索完成后resolve状态下返回DrivingPlan,reject状态下返回ErrorResult |
| setPolicy(policy: String) | this | 设置检索策略 |
| setMp(mp:Boolean) | this | 设置是否返回多种方案 |
| setNoStep(noStep: Boolean) | this | 设置是否不需路线引导 |
| setPlateNumber(plateNumber:String) | this | 设置车牌号 |
| setCarType(carType:String) | this | 设置车辆类型 |
| getPolicy() | String | 获取检索策略 |
| getMp() | Boolean | 获取是否返回多种方案的设置 |
| getNoStep() | Boolean | 获取是否不需路线引导的设置 |
| getPlateNumber() | String | 获取车牌号 |
| getCarType() | Number | 获取车辆类型 |
<br>
### DrivingSearchParams {#20}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------------ | ---------------- | -------- | ------------------------------------------------------------ |
| from | TMap.LatLng | 是 | 起点位置坐标 |
| to | TMap.LatLng | 是 | 终点位置坐标 |
| fromPoi | String | 否 | 起点POI ID,传入后,优先级高于from |
| heading | Number | 否 | [from辅助参数]在起点位置时的车头方向,数值型,取值范围0至360(0度代表正北,顺时针一周360度);传入车头方向,对于车辆所在道路的判断非常重要,直接影响路线计算的效果 |
| speed | Number | 否 | [from辅助参数]速度,单位:米/秒,默认3; 当速度低于1.39米/秒时,heading将被忽略 |
| accuracy | Number | 否 | [from辅助参数]定位精度,单位:米,取>0数值,默认5;当定位精度>30米时heading参数将被忽略 |
| roadType | Number | 否 | [from辅助参数] 起点道路类型,可选值:<br/>0 [默认]不考虑起点道路类型;<br/>1 在桥上;2 在桥下;3 在主路;4 在辅路;5 在对面;6 桥下主路;7 桥下辅路 |
| fromTrack | TMap.LatLng[] | 否 | 起点轨迹:在真实的场景中,易受各种环境及设备精度影响,导致定位点产生误差,传入起点前段轨迹,可有效提升准确度;传入一个TMap.LatLng数组,其元素为轨迹中的点 |
| toPoi | String | 否 | 终点POI ID(可通过腾讯位置服务地点搜索服务得到),当目的地为较大园区、小区时,会以引导点做为终点(如出入口等),体验更优;该参数优先级高于to(坐标),但是当目的地无引导点数据或POI ID失效时,仍会使用to(坐标)作为终点 |
| waypoints | TMap.LatLng[] | 否 | 为TMap.LatLng数组,其元素依序为途经的点 |
| avoidPolygon | TMap.LatLng[] [] | 否 | 为TMap.LatLng的二维数组,表示避让区域。第一层每个元素代表一个多边形避让区域,最多支持32个避让区域;第二层每个元素代表多边形中的一个顶点,每个区域最多可有9个顶点 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### DrivingPlan {#21}
---
| 参数 | | | | 类型 | 是否必带 | 说明 |
| :------ | ------ | ------------------- | ---------------- | :---------------------------- | :------- | :----------------------------------------------------------- |
| status | | | | Number | 是 | 状态码,正常为0 |
| message | | | | String | 是 | 状态说明 |
| result | | | | Object(对象结构如下) | 是 | 搜索结果 |
| | routes | | | Array(数组内各元素结构如下) | 是 | 路线方案(设置getMp=1时可返回最多3条) |
| | | mode | | String | 是 | 方案交通方式,固定值:“DRIVING” |
| | | tags | | String[] | 否 | 方案标签,表明方案特色 示例:tags:[“LEAST_LIGHT”] |
| | | distance | | Number | 是 | 方案总距离,单位:米 |
| | | duration | | Number | 是 | 方案估算时间(结合路况),单位:分钟 |
| | | traffic_light_count | | Number | 是 | 方案途经红绿灯个数 |
| | | toll | | Number | 否 | 预估过路费(仅供参考),单位:元 |
| | | restriction | | Object(对象结构如下) | 否 | 限行信息 |
| | | | status | Number | 是 | 限行状态码: 0 途经没有限行城市,或路线方案未涉及限行区域;1 途经包含有限行的城市;3 [设置车牌] 已避让限行 ;4 [设置车牌] 无法避开限行区域(本方案包含限行路段) |
| | | polyline | | TMap.LatLng[] | 是 | 方案路线坐标点串 |
| | | waypoints | | Array(数组内各元素结构如下) | 否 | 途经点,顺序与输入waypoints一致 (输入waypoints时才会有此结点返回) |
| | | | title | String | 否 | 途经点路名 |
| | | | location | TMap.LatLng | 否 | 途经点坐标 |
| | | taxi_fare | | Object(对象结构如下) | 否 | 预估打车费 |
| | | | fare | Number | 否 | 预估打车费用,单位:元 |
| | | steps | | Array(数组内各元素结构如下) | 是 | 路线步骤 |
| | | | instruction | String | 是 | 阶段路线描述 |
| | | | polyline_idx | Number[] | 是 | 阶段路线坐标点串在方案路线坐标点串的位置 |
| | | | road_name | String | 否 | 阶段路线路名 |
| | | | dir_desc | String | 否 | 阶段路线方向 |
| | | | distance | Number | 是 | 阶段路线距离,单位:米 |
| | | | act_desc | String | 否 | 阶段路线末尾动作:如:左转调头 |
| | | | accessorial_desc | String | 否 | 末尾辅助动作:如:到达终点 |
<br>
### Bicycling {#22}
---
提供骑行路线规划服务。
|构造函数|
| :- |
|new TMap.service.Bicycling()|
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| search(params: BicyclingSearchParams) | Promise | 搜索骑行路线;搜索完成后resolve状态下返回BicyclingPlan,reject状态下返回ErrorResult |
<br>
### BicyclingSearchParams {#23}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ----- | ----------- | -------- | ------------------------------------------------------------ |
| from | TMap.LatLng | 是 | 起点位置坐标 |
| to | TMap.LatLng | 是 | 终点位置坐标 |
| toPoi | String | 否 | 终点POI ID(可通过腾讯位置服务地点搜索服务得到),当目的地为较大园区、小区时,会以引导点做为终点(如出入口等),体验更优;该参数优先级高于to(坐标),但是当目的地无引导点数据或POI ID失效时,仍会使用to(坐标)作为终点 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### BicyclingPlan {#24}
| 参数 | | | | 类型 | 是否必带 | 说明 |
| :------ | ------ | --------- | ------------ | :---------------------------- | :------- | :--------------------------------------- |
| status | | | | Number | 是 | 状态码,正常为0 |
| message | | | | String | 是 | 状态说明 |
| result | | | | Object(对象结构如下) | 是 | 搜索结果 |
| | routes | | | Array(数组内各元素结构如下) | 是 | 路线方案 |
| | | mode | | String | 是 | 方案交通方式,固定值:“BICYCLING” |
| | | distance | | Number | 是 | 方案整体距离,单位:米 |
| | | duration | | Number | 是 | 方案估算时间,单位:分钟 |
| | | direction | | String | 是 | 方案整体方向 |
| | | polyline | | TMap.LatLng[] | 是 | 方案路线坐标点串 |
| | | steps | | Array(数组内各元素结构如下) | 是 | 路线步骤 |
| | | | instruction | String | 是 | 阶段路线描述 |
| | | | polyline_idx | Number[] | 是 | 阶段路线坐标点串在方案路线坐标点串的位置 |
| | | | road_name | String | 否 | 阶段路线路名 |
| | | | dir_desc | String | 否 | 阶段路线方向 |
| | | | distance | Number | 是 | 阶段路线距离,单位:米 |
| | | | act_desc | String | 否 | 阶段路线末尾动作 |
<br>
### Walking {#25}
---
提供步行路线规划服务。
| 构造函数 |
| :------------------------- |
| new TMap.service.Walking() |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| search(params: WalkingSearchParams) | Promise | 搜索自步行路线;搜索完成后resolve状态下返回WalkingPlan,reject状态下返回ErrorResult |
<br>
### WalkingSearchParams{#26}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ----- | ----------- | -------- | ------------------------------------------------------------ |
| from | TMap.LatLng | 是 | 起点位置坐标 |
| to | TMap.LatLng | 是 | 终点位置坐标 |
| toPoi | String | 否 | 终点POI ID(可通过腾讯位置服务地点搜索服务得到),当目的地为较大园区、小区时,会以引导点做为终点(如出入口等),体验更优;该参数优先级高于to(坐标),但是当目的地无引导点数据或POI ID失效时,仍会使用to(坐标)作为终点 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### WalkingPlan {#27}
---
| 参数 | | | | 类型 | 是否必带 | 说明 |
| :------ | ------ | --------- | ------------ | :---------------------------- | :------- | :----------------------------------------------------------- |
| status | | | | Number | 是 | 状态码,正常为0 |
| message | | | | String | 是 | 状态说明 |
| result | | | | Object(对象结构如下) | 是 | 搜索结果 |
| | routes | | | Array(数组内各元素结构如下) | 是 | 路线方案 |
| | | mode | | String | 是 | 方案交通方式,固定值:“WALKING” |
| | | distance | | Number | 是 | 方案整体距离,单位:米 |
| | | duration | | Number | 是 | 方案估算时间,单位:分钟 |
| | | direction | | String | 是 | 方案整体方向 |
| | | polyline | | TMap.LatLng[] | 是 | 方案路线坐标点串 |
| | | steps | | Array(数组内各元素结构如下) | 是 | 路线步骤 |
| | | | instruction | String | 是 | 阶段路线描述 |
| | | | polyline_idx | Number[] | 是 | 阶段路线坐标点串在方案路线坐标点串的位置 |
| | | | road_name | String | 否 | 阶段路线路名 |
| | | | dir_desc | String | 否 | 阶段路线方向 |
| | | | distance | Number | 是 | 阶段路线距离,单位:米 |
| | | | act_desc | String | 否 | 阶段路线末尾动作 |
| | | | type | Number | 是 | 阶段路线的步行设施类型(type),包含: 0普通道路,1过街天桥,2地下通道,3人行横道 |
<br>
### Transit {#28}
---
提供公交换乖路线规划服务。
| 构造函数 |
| :-------------------------------- |
| new TMap.service.Transit(options) |
| 参数名称 | 类型 | 说明 |
| :------- | :----- | -------------------------------------------- |
| options | Object | 公交换乖路线规划服务类配置参数对象规范见下表 |
| 名称 | 类型 | 是否必填 | 说明 |
| ------ | ------ | -------- | ------------------------------------------------------------ |
| policy | String | 否 | 检索策略<br />1) 排序策略,以下四选一:<br/>policy=LEAST_TIME:时间短(默认);<br/>policy=LEAST_TRANSFER:少换乘;<br/>policy=LEAST_WALKING:少步行;<br/>policy=RECOMMEND:推荐策略,结合步行、换乘、耗时等多方面综合排序结果(与腾讯地图APP默认策略一致)<br/>2) 额外限制条件:<br/>NO_SUBWAY:不坐地铁;<br/>ONLY_SUBWAY:只坐地铁;<br/>SUBWAY_FIRST:地铁优先;<br/>3) 排序策略与额外条件可同时使用,如:<br/>policy=LEAST_TRANSFER,NO_SUBWAY |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| search(params: TransitSearchParams) | Promise | 搜索公交换乘路线;搜索完成后resolve状态下返回TransitPlan,reject状态下返回ErrorResult |
| setPolicy(policy: String) | this | 设置检索策略 |
| getPolicy() | String | 获取检索策略 |
<br>
### TransitSearchParams {#29}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------------- | ----------- | -------- | ------------------------------------------------------------ |
| from | TMap.LatLng | 是 | 起点位置坐标 |
| to | TMap.LatLng | 是 | 终点位置坐标 |
| departureTime | Number | 否 | 出发时间,用于过滤掉非运营时段的线路,格式为Unix时间戳,默认使用当前时间 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### TransitPlan{#30}
---
| 参数 | | | | 类型 | 是否必带 | 说明 |
| :------ | ------ | -------- | -------- | :---------------------------- | :------- | :----------------------------------------------------------- |
| status | | | | Number | 是 | 状态码,0为正常,其它为异常,详细请参阅状态码说明 |
| message | | | | String | 是 | 状态说明 |
| result | | | | Object(对象结构如下) | 是 | 计算结果 |
| | routes | | | Array(数组内各元素结构如下) | 是 | 路线方案 |
| | | distance | | Number | 是 | 方案整体距离,单位:米 |
| | | duration | | Number | 是 | 方案估算时间,单位:分钟 |
| | | bounds | | Number | 是 | 整体路线的外接矩形范围,可在地图显示时使用, 通过矩形西南+东北两个端点坐标定义而面,示例: “39.901405,116.334023,39.940289,116.451720” |
| | | steps | | Array(数组内各元素结构如下) | 是 | 一条完整公交路线可能会包含多种公共交通工具,各交通工具的换乘由步行路线串联起来,形成这样的结构(即 steps数组的结构): [步行 , 公交 , 步行 , 公交 , 步行(到终点)] |
| | | | mode | String | 是 | 本段交通方式,取值: <br />WALKING:步行 <br />TRANSIT:公共交通工具<br />不同的方式,返回不同的数据结构,须根据该参数值来判断以哪种结构进行解析,各类具体定义见WebService API的相关说明 |
| | | | 其他字段 | | 是 | 随mode不同有不同字段返回,见WebService API的相关说明;其中凡为经纬度位置信息的,以TMap.LatLng格式输出。;凡为路线信息的,以TMap.LatLng格式的数组输出 |
<br>
## 行政区划 {#31}
### District
---
本接口提供中国标准行政区划数据,可用于生成城市列表控件等功能时使用。
| 构造函数 |
| :--------------------------------- |
| new TMap.service.District(options) |
| 参数名称 | 类型 | 说明 |
| :------- | :----- | ---------------------------------------- |
| options | Object | 行政区划数据服务类配置参数对象规范见下表 |
| 名称 | 类型 | 是否必填 | 说明 |
| --------- | ------ | -------- | ------------------------------------------------------------ |
| polygon | Number | 否 | 返回行政区划轮廓点串(经纬度点串)<br />注:本参数仅在keyword为adcode,且仅检索一个行政区划时生效<br />0:默认,不返回轮廓<br/>1:包含海域,3公里抽稀粒度<br/>2:纯陆地行政区划,可通过max_offset设置返回轮廓的抽稀级别 |
| maxOffset | Number | 否 | 轮廓点串的抽稀精度(仅对get_polygon=2时支持),单位米,可选值:<br/>100 :100米(当缺省id返回省级区划时,将按500米返回,其它级别正常生效)<br/>500 :500米<br/>1000:1000米<br/>3000:3000米 |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| list() | Promise | 获取省市区列表;搜索完成后resolve状态下返回DistrictInfo,reject状态下返回ErrorResult |
| getChildren(params: GetChildrenParams) | Promise | 获取指定行政区代码的下级行政区划;搜索完成后resolve状态下返回DistrictInfo,reject状态下返回ErrorResult |
| search(params: SearchParams) | Promise | 以关键字搜索行政区划;搜索完成后resolve状态下返回DistrictInfo,reject状态下返回ErrorResult |
| setPolygon(polygon: Number) | this | 设置是否获取行政区范围多边形数据 |
| setMaxOffset(maxOffset: Number) | this | 设置多边形数据精度 |
| getPolygon() | Number | 获取是否获取行政区范围多边形数据 |
| getMaxOffset() | Number | 获取多边形数据精度 |
<br>
### GetChildrenParams {#32}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ---- | ------ | -------- | ------------------------------------------------------------ |
| id | String | 否 | 父级行政区划ID(adcode),缺省时返回一级行政区划,也就是省级 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### SearchParams {#33}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ------- | ------ | -------- | ------------------------------------------------------------ |
| keyword | String | 是 | 搜索关键词:<br/>1.支持输入一个文本关键词<br/>2.支持多个行政区划代码(adcode),英文逗号分隔 |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### DistrictInfo {#34}
---
| 名称 | | | | 类型 | 是否必带 | 说明 |
| :----------- | ---------- | -------- | ---------- | :---------------------------- | :------- | :----------------------------------------------------------- |
| status | | | | Number | 是 | 状态码,0为正常,其它为异常 |
| message | | | | String | 是 | 状态说明 |
| data_version | | | | Number | 是 | 行政区划数据版本,便于您判断更新 |
| result[] | | | | Array | 是 | 结果数组 |
| | 其下数组项 | | | Array(数组内各元素结构如下) | 否 | 第0项,代表一级行政区划,第1项代表二级行政区划,以此类推;使用getChildren接口时,仅为指定父级行政区划的子级区划 |
| | | id | | String | 是 | 行政区划唯一标识(adcode) |
| | | name | | String | 是 | 简称,如“内蒙古” |
| | | fullname | | String | 是 | 全称,如“内蒙古自治区” |
| | | location | | TMap.LatLng | 是 | 经纬度 |
| | | pinyin | | String[] | 否 | 行政区划拼音,每一下标为一个字的全拼如: [“nei”,“meng”,“gu”] |
| | | cidx | | Number[] | 否 | 子级行政区划在下级数组中的下标位置 |
| | | polygon | | Array | 否 | 该行政区划的轮廓经纬度点串(当使用getPolygon=1或2时返回),数组每一项为一个多边形,一个行政区划可以由多块多边形组成 |
| | | | 其下数组项 | TMap.LatLng[] | 否 | 每个数组为一个轮廓多边形点串 |
<br>
## IP定位 {#35}
### IPLocation
---
通过终端设备IP地址获取其当前所在地理位置,精确到市级,常用于显示当地城市天气预报、初始化用户城市等非精确定位场景。
| 构造函数 |
| :---------------------------- |
| new TMap.service.IPLocation() |
| 方法 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| locate(params: LocateParams) | Promise | 通过终端设备IP地址ip获取其当前所在地理位置;搜索完成后resolve状态下返回IPLocationResult,reject状态下返回ErrorResult |
<br>
### LocateParams {#36}
---
| 名称 | 类型 | 是否必填 | 说明 |
| ---- | ------ | -------- | ------------------------------ |
| ip | String | 否 | IP地址,缺省时会使用请求端的IP |
| servicesk | String | 否 | 签名校验<br/> 开启WebServiceAPI签名校验的必传参数,只需要传入生成的SK字符串即可,不需要进行MD5加密操作 |
<br>
### IPLocationResult {#38}
---
| 名称 | | | 类型 | 是否必带 | 说明 |
| :------ | -------- | -------- | :--------------------- | :------- | :----------------------------------------------------------- |
| status | | | Number | 是 | 状态码,0为正常,其它为异常,详细请参阅状态码说明 |
| message | | | String | 是 | 对status的描述 |
| result | | | Object(对象结构如下) | 是 | IP定位结果 |
| | ip | | String | 是 | 用于定位的IP地址 |
| | location | | TMap.LatLng | 是 | 定位坐标 |
| | ad_info | | Object(对象结构如下) | 是 | 定位行政区划信息 |
| | | nation | String | 是 | 国家 |
| | | province | String | 是 | 省 |
| | | city | String | 否 | 市 |
| | | district | String | 否 | 区 |
| | | adcode | Number | 是 | 行政区划代码 |
<br>
### ErrorResult {#37}
---
| 名称 | 类型 | 是否必带 | 说明 |
| ------- | ------ | -------- | -------- |
| status | Number | 是 | 状态码 |
| message | String | 是 | 错误信息 |
FILE:references/jsapigl/docs/附加库:天气图层.md
## 天气图层 {#1}
该附加库用于创建天气图层 ,用户传入对应瓦片key、tileUrl、 time和天气图类型,即可生成对应的气象图效果,当前支持云图、温度图、低云图三种类型。
<br>
|构造函数|
| :- |
|new TMap.weather.WeatherLayer(options)|
|参数名|类型|说明|
| :- | :- | :- |
|options |WeatherLayerOptions |天气图层参数 |
## weatherLayerOptions 对象规范{#1}
<br />
天气图层配置参数。
<br />
| 名称 | 类型 | 说明 |
| :- | :- |:- |
|map |Map|展示图层的地图对象 |
|tileUrl |string |天气数据上传后返回的瓦片服务地址 |
|weatherType |WEATHER_TYPE |标识灰度图色值类型,类型常量见TMap.constants.WEATHER_TYPE,目前已知有云图 CLOUD ,温度 TEMPRATURE,低云LOWCLOUD |
|time |string |数据时间点,字符串类型 |
|key |string |用户上传瓦片后返回的数据标识key |
|colorMap |GradientColor |色系映射方案,配置渐变色,当前图层下GradientColor 不支持设置angle,默认为0, |
|minZoom |Number |最小缩放层级,当地图缩放层级小于该值时该图层不显示,不填默认3 |
|maxZoom |Number |最大缩放层级,当地图缩放层级大于该值时该图层不显示, 不填默认20 |
|maxDataZoom|Number |最大数据层级,当缩放层级大于该值时不再加载新数据,而是以此层级数据拉伸进行展示(最大拉伸等级为9级),默认为等于maxZoom |
|enableBubble | Boolean |是否将图层的鼠标事件冒泡到地图上,默认值为true。|
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |WeatherLayerEvent |鼠标左键单击图层时触发 |
|hover |WeatherLayerEvent|鼠标在图层上悬停位置改变时触发 |
## WeatherLayerEvent 对象规范{#2}
天气图层事件返回参数规范。
| 属性名 | 类型 | 说明 |
| :------ | :-------- | :----------------- |
|value |Number |事件发生位置的对应图层属性值,如云图、低云图代表云量,温度图代表温度(摄氏度) |
|rgba |Array |返回灰度瓦片的像素值(注: 不是当前渲染瓦片的像素颜色值),如[255,255,255, 1] |
|latLng |LatLng|事件发生时的经纬度坐标 |
|point |Object |事件发生时的屏幕位置,返回{x:Number, y:Number}格式 |
|type |String |事件类型 |
|target |Object |事件的目标对象 |
|originalEvent |MouseEvent 或 TouchEvent |浏览器原生的事件对象 |
## TMap.weather.constants.WEATHER_TYPE 常量说明{#3}
天气图层类型常量。
|常量 |说明 |
|:--|:--|
|WEATHER_TYPE.CLOUD |云图 |
|WEATHER_TYPE.TEMPERATURE |温度层 |
|WEATHER_TYPE.LOW_CLOUD |低云图 |
FILE:references/jsapigl/docs/附加库:模型库.md
## GLTFModel {#1}
---
GLTFModel用于创建GLTF模型对象,叠加在地图上进行显示。默认以zoom=20的缩放比例显示模型数据。
|构造函数|
| :- |
|new TMap.model.GLTFModel(options: ModelOptions );|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setPosition(position: LatLng) |this |设置模型锚点所在的经纬度坐标。|
|setAnchor(anchor: Number[]) |this |设置模型锚点。|
|setRotation(rotation: Number[]) |this |设置模型旋转角度。|
|setScale(scale: Number l Number[]) |this |设置模型缩放比例。|
|setMask(mask:LatLng[]) |this |设置模型遮罩区域。|
|getPosition() |LatLng |获取模型锚点所在的经纬度坐标。|
|getAnchor() |Number[] |获取模型锚点坐标。|
|getRotation() |Number[] |获取模型旋转角度。|
|getScale() |Number[] |获取模型缩放比例。|
|getMask() |LatLng[] |获取模型遮罩区域。|
|addTo(map: Map) |this |将模型添加到指定地图对象上。|
|remove() |this |将模型从地图上删除。|
|destroy() |this |销毁模型对象。|
|show() |this |显示模型。|
|hide() |this |隐藏模型。|
| moveAlong(param: ModelMoveAlongParam) | this | 模型的沿线移动方法(使用该方法需要引入geometry附加库)。 |
| stopMove() | this | 停止移动,尚未完成的动画会被取消,并触发move_stopped事件;已经完成的动画不会触发move_stopped事件 。|
| pauseMove() | this | 暂停模型移动的动画效果 。|
| resumeMove() | this | 重新开始模型移动的动画效果。 |
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| startAnimation(keyFrames:GLTFKeyFrame[], opt:AnimationOptions{}) | this | 开始关键帧动画。 |
| stopAnimation() | this | 停止关键帧动画 。 |
| pauseAnimation() | this | 暂停关键帧动画。 |
| resumeAnimation() | this | 恢复关键帧动画。 |
**事件:**</br>
监听事件通过on、off方法绑定与解绑。
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|loaded |ModelLoadEvent |模型资源加载完成。|
|loading |ModelLoadEvent |模型资源加载中。|
|load_failed |ModelLoadEvent |模型资源加载失败。|
|click |ModelMouseEvent |点击模型时触发。|
|moving | ModelMovingEvent |模型在执行moveAlong动画时触发事件。|
|move_end | |模型执行moveAlong动画结束时触发事件。|
|move_stopped | |模型执行moveAlong动画被停止时触发事件。|
</br>
## ModelOptions {#2}
----
创建模型对象的配置参数。
|属性名称 |类型 |说明 |
| :- | :- | :- |
|url |String |模型资源url。|
|map |Map |显示模型的地图。|
|position |LatLng |模型锚点所对应的经纬度坐标。|
|id |String |(可选)模型唯一标识。|
|anchor |Number[] |(可选)模型锚点坐标,锚点为模型局部坐标系中的一个点,与地图经纬度坐标点对齐,锚点格式为[x, y, z],默认为[0, 0, 0]。|
|rotation |Number[] |(可选)模型XYZ三轴上的旋转角度,格式为[x, y, z],默认为[0, 0, 0]。|
|scale |Number l Number[] |(可选)模型在XYZ三轴上的缩放比例。若为数字,则三轴均以该比值进行缩放;若为数组,则可分别指定三轴缩放比例,格式为[x, y, z]。默认为1。|
|mask |LatLng[] |(可选)模型遮罩,该坐标点串形成的封闭区域将不显示底图建筑及POI。|
| namedAnimationsEnabled | Boolean | (可选)是否激活模型内置动画,默认为false |
</br>
## ModelMoveAlongParam {#6}
----
模型沿线移动参数对象规范。
|属性名称 |类型 |说明|
| :- | :- | :- |
|path|PathPoint[]|移动过程中每个节点的位置及时间状态。|
|duration|Number|完成一次移动所需的时间,单位为ms。|
|loop|Number|移动循环的次数,若为Infinity则无线循环,默认为1。|
|degreeToNorth|Number|将模型朝向正北需要在z轴上旋转的角度,默认为0。|
</br>
## PathPoint {#pathpoint}
----
模型沿线移动路径节点。
|属性名称 |类型 |说明|
| :- | :- | :- |
|position|LatLng|路径节点经纬度|
|timestamp|Number|路径节点时间戳|
</br>
## ModelLoadEvent 对象规范{#loadevent}
----
模型对象的加载事件返回对象。
| 名称| 类型| 说明|
| -- | -- | -- |
| target | Model | 事件的目标模型对象。 |
| error | Error | 加载失败的错误对象,load_failed事件返回 。|
| progress | Number | 加载进度,取值范围在0~1之间,loading事件返回 。|
| loaded | Number | 已加载的字节数,loading事件返回。 |
| total | Number | 总字节数,loading事件返回 。|
</br>
## ModelMouseEvent 对象规范{#mouseEvent }
----
模型对象的鼠标事件返回对象。
| 名称| 类型| 说明|
| -- | -- | -- |
| type | String | 事件的类型。 |
| target | Model | 事件的目标模型对象。 |
</br>
## ModelMovingEvent 对象规范{#modelMovingEvent }
----
模型沿线移动中返回的事件对象。
| 名称| 类型| 说明|
| -- | -- | -- |
| passedPath | PathPoint[] | 模型走过的路径的坐标串。 |
| rotation | Number[] | 模型当前的旋转角度。 |
</br>
## AnimationOptions 对象规范{#animation}
----
动画参数。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
| duration | Number | 动画周期时长,单位为ms,默认为1000。 |
| loop | Number | 动画周期循环次数,若为Infinity则无限循环,默认为1。 |
</br>
## GLTFKeyFrame 对象规范 {#gltfkeyframe}
----
GLTF模型关键帧对象。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
| percentage | Number | 动画过程中该关键帧的百分比,取值范围为0~1。 |
| position | LatLng | 模型位置经纬度。 |
| scale | Number/Number[] | 模型缩放级别。 |
| rotation | Number[] | 模型在[x,y,z]上的旋转角度。 |
| anchor | Number[] | 模型锚点。 |
</br>
## Tileset3D {#Tileset3D}
---
Tileset3D用于创建3DTiles模型对象,叠加在地图上进行显示
|构造函数|
| :- |
|new TMap.model.Tileset3D(options:Tileset3DOptions);|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMask(mask: LatLng[]) |this |设置模型遮罩 |
|getMask() |LatLng[] |获取模型遮罩 |
|getPosition() |LatLng[] |获取模型位置 |
|addTo(map: Map) |this |将模型添加到指定地图对象上 |
|destroy() |this |销毁模型对象 |
|show() |this |显示模型 |
|hide() |this |隐藏模型 |
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中 |
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener |
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|initialized | - |模型索引初始化完成后执行的事件,此事件只触发一次 |
|loaded | - |模型资源加载完成,当视图范围内所有瓦片加载完成后触发的,这个事件会被多次触发 |
|load_failed |Error |模型资源加载失败 |
|click |ClickEvent |点击模型时触发 |
|update | - |模型更新,如拖拽、缩放等操作使场景的模型数据更新时触发 |
</br>
## Tileset3DOptions {#Tileset3DOptions}
----
创建 3DTiles 模型对象的配置参数。
|属性名称 |类型 |说明 |
| :- | :- | :- |
|url |String |3DTiles 模型资源 url |
|map |Map |显示模型的地图 |
|id |String |(可选)模型唯一标识 |
|coordType |String |(可选)模型所使用的地图坐标系类型,默认为'gcj02', 可选'wgs84' |
|mask | LatLng[] |(可选)模型遮罩,该遮罩形成的封闭区域将不显示底图建筑及POI |
|offset | Number[] |(可选)Tileset3D 偏移坐标,格式为[x, y, z],单位米,默认为[0, 0, 0] |
</br>
## Tileset3DMouseEvent 对象规范 {#Tileset3DMouseEvent}
----
Tileset3D 单体模型鼠标事件返回对象。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
|type |String |事件类型 |
|target |Tileset3D |事件的目标对象 |
|originalEvent | * |浏览器原生的事件对象 |
|position |LatLng |事件触发的经纬度坐标及高度 |
</br>
## Marker3D {#Marker3D}
---
Marker3D用于创建表示地图上3D样式的标注点。
|构造函数|
| :- |
|new TMap.model.Marker3D(options: Marker3DOptions);|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将多个Marker3D同时从地图中移除 |
|setGeometries(geometries: Marker3DGeometry[]) |this |更新3D标注点数据,如果参数为null或undefined不会做任何处理 |
|setStyles(styles: Marker3DStyleHash) |this |设置Marker3D图层相关样式信息,如果参数为null或undefined不会做任何处理 |
|setVisible(visible: Boolean) |this |设置图层是否可见 |
|getMap() |Map |获取地图对象,若为空返回null |
|getGeometries() | Marker3DGeometry[] |获取点数据 |
|getStyles() |Marker3DStyleHash |获取图层相关样式信息 |
|getVisible() | Boolean |获取可见状态 |
|getGeometryById(id:String) | Marker3DGeometry|根据点数据id来获取点数据 |
|updateGeometries(geometry: Marker3DGeometry[]) |this |更新3D标注点数据,如果geometry的id存在于多点标注的集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的点标注添加到集合中;如果参数为null或undefined不会做任何处理 |
|add(geometries: Marker3DGeometry[]) |this |向图层中添加3D标注点,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的标注存在重复id,这些3D标注点会被重新分配id;如果参数为null或undefined不会做任何处理 |
|remove(ids: String[]) |this |移除指定id的3D标注点,如果参数为null或undefined不会做任何处理 |
|destroy() |void |销毁图层对象 |
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |GeometryOverlayEvent |点击事件 |
|dblclick |GeometryOverlayEvent |双击事件 |
|mousedown |GeometryOverlayEvent |鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发 |
|mouseup |GeometryOverlayEvent |鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发 |
|mousemove |GeometryOverlayEvent |鼠标在地图上移动时触发,只在桌面浏览器中触发 |
|touchstart |GeometryOverlayEvent |在地图区域触摸开始时触发,只在移动浏览器中触发 |
|touchmove |GeometryOverlayEvent |在地图区域触摸移动时触发,只在移动浏览器中触发 |
|touchend |GeometryOverlayEvent |在地图区域触摸结束时触发,只在移动浏览器中触发 |
</br>
## Marker3DOptions 对象规范 {#Marker3DOptions}
----
创建 3DMarker 对象的配置参数。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
|id |String |图层id,若没有会自动分配一个 |
|map |Map |显示Marker3D图层的底图 |
|styles |Marker3DStyleHash |点标注的相关样式 |
|geometries | Marker3DGeometry[] |点标注数据数组 |
</br>
## Marker3DStyleHash 对象规范 {#Marker3DStyleHash}
----
一个key-value形式对象, 表示Marker3D图层的相关样式信息,key使用字符串,value是 Marker3DStyle对象。
</br>
## Marker3DStyle {#Marker3DStyle}
----
Marker3DStyle 表示应用于Marker3D图层的样式类型。
|构造函数|
| :- |
|new TMap.model.Marker3DStyle(options: Marker3DStyleOptions);|
</br>
## Marker3DStyleOptions {#Marker3DStyleOptions}
----
Marker3DStyle配置参数。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
|type |String |Marker3D类型(location / radiation / maglev), 默认为location(标记点) |
|scale |Number |模型在XYZ三轴上的缩放比例。三轴均以该比值进行缩放, 默认为1。 |
|color |String |可选,Marker3D颜色属性,支持rgb(),#RRGGBB等形式,location默认为#D81611,maglev默认为#1DFAF2,radiation默认为#E5811C |
</br>
## Marker3DGeometry {#Marker3DGeometry}
----
Marker3D点图形数据。
| 名称 | 类型 | 说明 |
| :- | :- | :- |
|id |String |点图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个 |
|styleId |String |对应 Marker3DStyleHash 中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry使用默认样式绘制 |
|position |LatLng |3D标注点位置 |
| properties | Object |3D标注点的属性数据 |
FILE:references/jsapigl/docs/附加库:矢量数据图层.md
## GeoJSONLayer{#GeoJSONLayer}
---
GeoJSONLayer 用于解析、加载GeoJSON格式的数据
|构造函数|
| :- |
|new TMap.vector.GeoJSONLayer(options: GeoJSONLayerOptions);|
| 方法名 | 返回值 | 说明 |
| :------------------------------- | :------------------------------------------------------ | :----------------------------------------------------------- |
| setMap(map:Map) | this | 设置展示数据的地图对象,如果map为null意味着将GeoJSON数据从地图中移除 |
| setVisible(visible: Boolean) | this | 设置GeoJSON数据是否可见 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序 |
| setData(data:Object) | this | 切换图层数据源 |
| getGeometryOverlay(type: String) |MultiMarker|MultiPolyline |MultiPolygon | 获取GeoJSON数据的指定类型图层,type支持的值有:'marker'、'polyline'、'polygon' |
| getId() | string | 获取图层的id。 |
<br>
## GeoJSONLayerOptions{#GeoJSONLayerOptions}
GeoJSONLayer配置参数
| 参数名 | 类型 | 说明 |
| :------------ | :------------ | :----------------------------------------------------------- |
| map | Map | 加载GeoJSON的地图对象 |
| data | Object | GeoJSON标准格式的数据 |
| zIndex | Number | 图层绘制顺序,默认为1,有效范围为[1, 9999] |
| minZoom | Number | 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3 |
| maxZoom | Number | 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20 |
| markerStyle | MarkerStyle | GeoJSON数据中点状要素的样式,如果参数为null不会显示和创建点状要素 |
| polylineStyle |PolylineStyle | GeoJSON数据中线状要素的样式,如果参数为null不会显示和创建线状要素 |
| polygonStyle |PolygonStyle | GeoJSON数据中面状要素的样式,如果参数为null不会显示和创建面状要素 |
<br><br>
## MVTLayer{#MVTLayer}
-------
用于创建符合mapbox-vector-tile标准的图层对象,叠加在地图上进行显示;**注意,添加MVTLayer后不支持地图设置中心点偏移**。
| 构造函数 |
| :--------------------------------------------------- |
| new TMap.vector.MVTLayer(options: MVTLayerOptions); |
| 方法名 | 返回值 | 说明 |
| :--------------------------- | :----- | :----------------------------------------------------------- |
| setMap(map: Map/Null) | this | 将图层添加到地图上 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序 |
| getZIndex() | this | 获取图层的zindex |
| getId() | string | 获取图层的id |
| setStyle(style: String/JSON) | this | 设置样式,是一个符合 Mapbox 样式规范 的 JSON 对象,或者是一个指向该 JSON 的 URL 地址 |
| addLayer(params: MVTParams) | id | 添加mvt图层,返回图层id,此处图层指mvt的标准图层,MVTParams符合Mapbox样式规范的图层定义 |
| removeLayer(id: String) | this | 移除mvt图层 |
| getLayers() | id[] | 返回当前加载的所有mvt图层id |
<br>
## MVTLayerOptions对象规范{#MVTLayerOptions}
| 名称 | 类型 | 说明 |
| :----- | :---------- | :----------------------------------------------------------- |
| map | Map | 显示图层的地图 |
| id | String | 图层id |
| style | String/JSON | 非必填,是一个符合 Mapbox 样式规范 的 JSON 对象,或者是一个指向该 JSON 的 URL 地址 |
| zIndex | Number | 图层绘制顺序,默认为0 |
FILE:references/jsapigl/docs/自定义图层.md
## ImageTileLayer {#1}
----
用于创建自定义栅格瓦片图层。
| 构造函数 |
| :- |
|TMap.ImageTileLayer(options:ImageTileLayerOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
| getId() | string | 获取图层的id。 |
|setMap(map:Map/Null) |this |设置展示图层的地图对象。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|setOpacity(opacity: Number) |this |设置图层透明度。|
| destroy() | void | 销毁图层 |
| 静态方法 | 返回值 | 说明 |
| :- | :- |:- |
|createCustomLayer(options: CustomLayerOptions) |Promise |创建个性化图层平台配置的个性化图层。Promise处于fulfilled状态时传递ImageTileLayer图层对象给then方法绑定的处理方法,若鉴权失败则传递null。|
</br></br>
## ImageTileLayerOptions {#2}
----
ImageTileLayer 配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| getTileUrl | Function | (必填)通过传入的瓦片坐标(x,y)以及zoom级别返回瓦片的URL(string类型)。该方法形式如下:Function(x: Number, y: Number, z: Number):string;亦支持返回Promise对象,方法形式如下:Function(x: Number, y: Number, z: Number):Promise,可在Promise中进行异步操作,并resolve对应瓦片的base64数据或URL。 |
|map |Map |展示图层的地图对象。|
|minZoom |Number |最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。|
|maxZoom |Number |最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为18。|
| maxDataZoom | Number | 最大数据层级,当缩放层级大于该值时不再加载新数据,而是以此层级数据拉伸进行展示,默认与maxZoom相同 |
|visible |Boolean |是否可见,默认为true。|
|zIndex |Number |图层绘制顺序,默认为1,有效范围为[1, 9999]。|
|opacity |Number |图层透明度,默认为1。|
| isMainThreadLoaded | Boolean | 是否在主线程中加载瓦片,默认为false;从子线程发出请求可能命中浏览器的安全策略,导致请求失败,此时可改用主线程加载 |
</br></br>
## CustomLayerOptions {#3}
----
创建个性化图层时的配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|layerId |String |(必填)个性化图层的id。|
|map |Map |展示图层的地图对象。|
|minZoom |Number |最小缩放层级,当地图缩放层级小于该值时该图层不显示。|
|maxZoom |Number |最大缩放层级,当地图缩放层级大于该值时该图层不显示。|
|visible |Boolean |是否可见,默认为true。|
|zIndex |Number |图层绘制顺序,默认为1,有效范围为[1, 9999]。|
|opacity |Number |图层透明度,默认为1。|
<br><br>
## WMSLayer{#WMSLayer}
------
用于创建基于OGC标准的WMS地图服务的图层类,仅支持EPSG3857坐标系统的WMS图层。
| 构造函数 |
| :------------------------------------------------------------ |
| new TMap.WMSLayer(options: WMSLayerOptions); |
| 方法名 | 返回值 | 说明 |
| :---------------------------- | :----- | :--------------------- |
| setMap(map: Map/Null) | this | 设置展示图层的地图对象 |
| setVisible(visible: Boolean) | this | 设置图层是否可见 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序 |
| setOpacity(opacity: Number) | this | 设置图层透明度 |
<br>
## WMSLayerOptions {#WMSLayerOptions }
WMSLayer配置参数。
| 名称 | 类型 | 说明 |
| :---------- | :-------- | :----------------------------------------------------------- |
| url | String | 地图服务地址 |
| map | Map | 展示图层的地图对象 |
| minZoom | Number | 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3 |
| maxZoom | Number | 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20 |
| maxDataZoom | Number | 最大数据层级,当缩放层级大于该值时不再加载新数据,而是以此层级数据拉伸进行展示,默认与maxZoom相同 |
| visible | Boolean | 是否可见,默认为true |
| zIndex | Number | 图层绘制顺序,默认为1,有效范围为[1, 9999] |
| opacity | Number | 图层透明度,默认为1 |
| params | WMSParams | OGC标准的WMS地图服务的GetMap接口的参数 |
<br>
## WMSParams 对象规范{#WMSParams}
| 名称 | 类型 | 说明 |
| :------ | :----- | :--------------------- |
| layers | String | 请求的图层名称(必填) |
| version | String | 请求的WMS的版本号 |
<br><br>
## WMTSLayer{#WMTSLayer}
--------
用于创建基于OGC标准的WMTS地图服务的图层类,仅支持EPSG3857坐标系统的WMTS图层。
| 构造函数 |
| :---------------------------------------------- |
| new TMap.WMTSLayer(options: WMTSLayerOptions); |
| 方法名 | 返回值 | 说明 |
| :---------------------------- | :----- | :--------------------- |
| setMap(map: Map/Null) | this | 设置展示图层的地图对象 |
| setVisible(visible: Boolean) | this | 设置图层是否可见 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序 |
| *setOpacity*(opacity: Number) | this | 设置图层透明度 |
<br>
## WMTSLayerOptions 对象{#WMTSLayerOptions}
WMTSLayer配置参数。
| 名称 | 类型 | 说明 |
| :---------- | :--------- | :----------------------------------------------------------- |
| url | String | 地图服务地址 |
| map | Map | 展示图层的地图对象 |
| minZoom | Number | 最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3 |
| maxZoom | Number | 最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20 |
| maxDataZoom | Number | 最大数据层级,当缩放层级大于该值时不再加载新数据,而是以此层级数据拉伸进行展示,默认与maxZoom相同 |
| visible | Boolean | 是否可见,默认为true |
| zIndex | Number | 图层绘制顺序,默认为1,有效范围为[1, 9999] |
| opacity | Number | 图层透明度,默认为1 |
| params | WMTSParams | OGC标准的WMTS地图服务的GetTile接口的参数 |
<br>
## WMTSParams {#WMTSParams}
| 名称 | 类型 | 说明 |
| :------------ | :----- | :--------------------- |
| layer | String | 请求的图层名称(必填) |
| tileMatrixSet | String | 瓦片矩阵数据集(必填) |
| version | String | 请求的WMTS的版本号 |
</br></br>
## ImageGroundLayer {#4}
----
用于创建自定义图片图层,图片会随着地图缩放而缩放。
| 构造函数 |
| :- |
|new TMap.ImageGroundLayer(options);|
| 参数说明 | 类型 | 说明 |
| :- | :- | :- |
|options |ImageGroundLayerOptions |自定义图片图层参数|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMap(map:Map) |this |设置展示图层的地图对象。|
|setBounds(bounds: LatLngBounds) |this |设置展示图层的地理范围。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|setOpacity(opacity: Number) |this |设置图层透明度。|
|setSrc(src:String) |this |更新图层资源路径,相同的url不会被更新。|
|getMap() |Map |获取地图对象,若无返回null。|
| getId() | string | 获取图层的id。 |
|getBounds() |LatLngBounds |获取展示图层的地理范围。|
</br></br>
## ImageGroundLayerOptions {#5}
----
ImageGroundLayer配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|bounds |LatLngBounds |(必填)图片覆盖的经纬度范围|
|src |String |(必填)图片url或base64,如果图片为url格式,图片服务器必须允许跨域访问|
|map |Map |展示图层的地图对象|
|minZoom |Number |最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3|
|maxZoom |Number |最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20|
|visible |Boolean |是否可见,默认为true|
|zIndex |Boolean |图层绘制顺序,默认为1|
|opacity |Number |图层透明度,默认为1|
</br></br>
## CanvasGroundLayer {#6}
----
用于创建自定义图片图层,图片会随着地图缩放而缩放。
| 构造函数 |
| :- |
|new TMap.CanvasGroundLayer(options);|
| 参数说明 | 类型 | 说明 |
| :- | :- | :- |
|options |CanvasGroundLayerOptions |自定义Canvas图层参数|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMap(map:Map) |this |设置展示图层的地图对象。|
|setBounds(bounds: LatLngBounds) |this |设置展示图层的地理范围。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|setOpacity(opacity: Number) |this |设置图层透明度。|
|setCanvas(canvas: HTMLCanvasElement) |this |更新canvas元素,同一个canvas不会被重复更新。|
|refresh() |null |刷新canvas,当canvas图像内容改变时调用,否则canvas的内容不会更新到地图上。|
|getMap() |Map |获取地图对象,若无返回null。|
| getId() | string | 获取图层的id。 |
|getBounds() |LatLngBounds |获取展示图层的地理范围。|
</br></br>
## CanvasGroundLayerOptions {#7}
----
CanvasGroundLayer配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|bounds |LatLngBounds |(必填)纹理覆盖的经纬度范围。|
|canvas |HTMLCanvasElement |(必填)Canvas元素,如果canvas中有图片,必须保证原始的图片服务允许跨域访问。|
|map |Map |展示图层的地图对象。|
|minZoom |Number |最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。|
|maxZoom |Number |最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。|
|visible |Boolean |是否可见,默认为true。|
|zIndex |Boolean |图层绘制顺序,默认为1。|
|opacity |Number |图层透明度,默认为1。|
</br></br>
## MaskLayer {#8}
----
用于创建遮罩图层,其覆盖区域内3D建筑及POI将不显示,可配合自定义图层进行使用。
| 构造函数 |
| :- |
|new TMap.MaskLayer(options:MaskLayerOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|add(geometries: MaskGeometry[]) |this |新增遮罩区域,可批量添加。|
|update(geometries: MaskGeometry[]) |this |更新遮罩区域数据,已存在id则更新已有数据,若不存在则添加数据。|
|get(id: String) |MaskGeometry |获取指定遮罩区域。|
|getAll() |MaskGeometry[]| 获取图层内所有遮罩区域。|
|remove(ids: String[]) |this |删除指定遮罩区域。|
|clear() |this |清除所有遮罩区域。|
|setMap(map: Map I null) |this |设置图层绑定的地图对象,若为null则将其从地图中移除。|
|getMap() |Map |获取地图对象,若为空则返回null。|
</br></br>
## MaskLayerOptions {#9}
----
MaskLayer 配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|map |Map |绑定的地图对象。|
|geometries |MaskGeometry[] |遮罩区域数据。|
</br></br>
## MaskGeometry {#10}
----
单个遮罩区域数据。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|id |String |唯一标识。|
|paths |LatLng[] |遮罩轮廓线坐标点串。|
</br></br>
## GLCustomLayer{#11}
----
GLCustomLayer 用于创建自定义GL图层,可配合threejs实现自定义覆盖物效果,**使用该接口需要引入model附加库**。查看示例
| 构造函数 |
| :- |
|new TMap.GLCustomLayer(options:GLCustomLayerOptions)|
| 参数名 | 类型 | 说明 |
| -- | -- | -- |
| options | GLCustomLayerOptions | GL自定义图层参数 |
|方法名|返回值|说明|
| -- | -- | -- |
| setKeepFps(keepFps:Boolean) | this | 设置是否保持帧率渲染 |
| show() | this | 显示图层 |
| hide() | this | 隐藏图层 |
| destroy() | this | 销毁图层对象 |
| setRender(render: Function) | this | 设置render方法 |
| setZIndex(zIndex: Number) | this | 设置图层渲染层级 |
| setMinZoom(minZoom: Number) | this | 设置图层最小缩放倍数 |
| getMinZoom(minZoom: Number) | this | 获取图层最小缩放倍数 |
| setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放倍数 |
| getMaxZoom(maxZoom: Number) | this | 获取图层最大缩放倍数 |
</br></br>
## GLCustomLayerOptions 对象规范{#12}
GLCustomLayer配置参数。
| 属性名称 | 类型 | 说明 |
| -- | -- | -- |
| id | String | 唯一标识 |
| zIndex | Number | 图层渲染层级 |
| minZoom | Number | 图层最小缩放倍数,默认为3 |
| maxZoom | Number | 图层最大缩放倍数,默认为20 |
| map | Map | 绑定的地图对象 |
| visible | Boolean | 图层是否可见 |
| keepFps | Boolean | 是否保持恒定帧率渲染,当渲染存在动画时需设置为true,默认为false |
| init | Function | 自定义的初始化方法 |
| render | Function | 自定义的渲染方法 |
**示例:**</br>
javascript
// 定义THREE相关项
let renderer, camera, scene, group;
let mapCamera; // 地图相机
const glCustomLayer = new TMap.GLCustomLayer({
id: 'glCustomLayer',
map,
keepFps: true, // 是否保持恒定帧率渲染,当渲染存在动画时设置为true
// visible: false,
init: (gl) => {
// 初始化THREE
renderer = new THREE.WebGLRenderer({
context: gl,
antialias: true
});
renderer.autoClear = false;
renderer.outputEncoding = THREE.sRGBEncoding;
// 创建THREE.js中的相机
let mapCamera = map.getCamera(); // 获取地图相机
const { fovy, view, near, far, distance } = mapCamera;
const aspect = (view.right - view.left) / (view.top - view.bottom);
camera = new THREE.PerspectiveCamera(fovy, aspect, near, far);
camera.position.z = distance;
scene = new THREE.Scene();
group = new THREE.Group();
scene.add(group);
// THREE.js生成自定义模型 以立方体为例
const geometry = new THREE.BoxGeometry(200, 200, 200);
const material = new THREE.MeshBasicMaterial({ color: 0x00ffff });
const cube = new THREE.Mesh(geometry, material);
const position = new TMap.LatLng(39.7946, 116.526, 0);
// 坐标转换 经纬度转THREE中的世界坐标
const coord = map.glLatLngToPosition(position);
cube.position.set(coord.x, coord.y, coord.z);
// 用group添加
group.add(cube);
// 需将构建的Three相关项返回
return { renderer, camera, scene, group };
},
render: () => {
renderer.render(scene, camera);
}
});
FILE:references/jsapigl/docs/文本标记.md
## MultiLabel {#1}
----
表示地图上的多个文本标注,可以自定义每个文本标注的样式。
|构造函数|
| :- |
|TMap.MultiLabel(options:MultiLabelOptions)|
|方法 |返回值 |说明|
| :- | :- | :- |
|setMap(map:Map) |this |设置地图对象,如果map为null意味着将多个文本标注同时从地图中移除。|
|setGeometries(geometries: LabelGeometry[]) |this |更新多文本标注数据,如果参数为null或undefined不会做任何处理。|
|setStyles(styles:MultiLabelStyleHash) |this |设置MultiLabel图层相关样式信息,如果参数为null或undefined不会做任何处理。|
|getMap() |Map |获取地图对象,若为空返回null。|
|getGeometries() |LabelGeometry[] |获取多文本标注数据。|
|getStyles() |MultiLabelStyleHash |获取图层相关样式信息。|
|setVisible(visible: Boolean) |this |设置图层是否可见。|
|getVisible() |visible |获取可见状态。|
| setInteractiveDisable(disableInteractive: Boolean) | this | 设置图层是否禁止参与交互事件|
| setStopPropagation(isStopPropagation: Boolean) | this | 设置当前覆盖物事件响应禁止冒泡至Map|
|getGeometryById(id:String) |LabelGeometry |根据多文本数据id来获取点数据。|
| getInteractiveDisable() | Boolean | 获取图层是否禁止参与交互事件的状态 |
| getStopPropagation() | Boolean | 获取当前覆盖物禁止冒泡状态 |
|updateGeometries(geometry:LabelGeometry[]) |this |更新多文本数据,如果geometry的id存在于集合中,会更新对id的数据,如果之前不存在于集合中,会作为新的文本标注添加到集合中;如果参数为null或undefined不会做任何处理。|
|add(geometries: LabelGeometry[]) |this |向图层中添加文本,如果geometry的id已经存在集合中,则该geometry不会被重复添加,如果geometry没有id或者id不存在于集合中会被添加到集合,没有id的geometry会被赋予一个唯一id;如果要添加到集合中的文本存在重复id,这些文本会被重新分配id;如果参数为null或undefined不会做任何处理。|
|remove(ids: String[]) |this |移除指定id的文本,如果参数为null或undefined不会做任何处理。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
|destroy() | | 销毁图层对象 |
| getLabelBoxes(id:String) | LabelBoxHash | 获取文本整体像素宽高值,以一个key-value对象的形式返回,key为文本id,value为数组,第一个数据为文本整体的宽,第二个数据为文本整体的高。当未传入id时默认获得所有文本的宽高。注意该方法需要监听MultiLabel的'labelBox_changed'事件。|
**事件:**</br>
监听事件通过on、off方法绑定与解绑。 查看示例
|事件名 |参数 |说明|
| :- | :- | :- |
|click |GeometryOverlayEvent |点击事件|
|dblclick |GeometryOverlayEvent|双击事件|
|mousedown |GeometryOverlayEvent|鼠标在地图区域中左键按下时触发,只在桌面浏览器中触发|
|mouseup |GeometryOverlayEvent|鼠标在地图区域中左键按下又弹起时触发,只在桌面浏览器中触发|
|mousemove |GeometryOverlayEvent|鼠标在地图上移动时触发,只在桌面浏览器中触发|
|hover |GeometryOverlayEvent|鼠标在图层上悬停对象改变时触发,事件对象中的geometry属性会指向交互位置所在图形的LabelGeometry,无图形时事件对象为null,只在桌面浏览器中触发|
|touchstart |GeometryOverlayEvent|在地图区域触摸开始时触发,只在移动浏览器中触发|
|touchmove |GeometryOverlayEvent |在地图区域触摸移动时触发,只在移动浏览器中触发|
|touchend |GeometryOverlayEvent|在地图区域触摸结束时触发,只在移动浏览器中触发|
|labelBox_changed | - |在数据变化时触发|
| geometry_processed | | 由增删改引起的几何数据变更被处理完成后触发。(注意:1、该事件仅代表数据处理完成,不代表数据被渲染完成,通常数据处理完后20~50ms内可被渲染;2、当该实例对象被编辑器激活进行编辑前,需要开发者手动阻止该事件监听函数中的业务逻辑执行,在编辑完成后可正常执行事件监听函数的业务逻辑) |
</br></br>
## MultiLabelOptions {#2}
----
MultiLabel配置参数。
|属性名称 |类型 |说明|
| :- | :- | :- |
|id |String |图层id,若没有会自动分配一个。|
|map |Map |显示文本标注图层的底图。|
|styles |MultiLabelStyleHash |文本标注的相关样式。|
| collisionOptions | CollisionOptions | 图层碰撞配置参数 |
|geometries |LabelGeometry[] |文本标注数据数组。|
| disableInteractive | Boolean | 图层是否禁止参与交互事件,默认为false |
| isStopPropagation | Boolean | 是否阻止鼠标、触摸事件冒泡,默认为false |
</br></br>
## MultiLabelStyleHash {#3}
----
一个key-value形式对象, 表示文本标注的相关样式信息,key使用字符串,value是 LabelStyle 对象。
</br></br>
## LabelStyle {#4}
----
表示应用于多标注图层的样式类型。
|构造函数|
| :- |
|TMap.LabelStyle(options:LabelStyleOptions)|
|名称 |类型 |说明|
| :- | :- |:- |
|color |String |颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0,0,0,1)。|
| strokeColor |String |描边颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0,0,0,0)|
| strokeWidth | Number | 标注点文本描边宽度,默认为1 |
|size |Number |文字大小属性,默认为14。|
|offset |Object |文字偏移属性,单位为像素,以PointGeometry的位置点所对应屏幕位置为原点,x轴向右为正向左为负,y轴向下为正向上为负,默认为{x:0, y:0}。|
|angle |Number |文字旋转属性,单位为度,以PointGeometry的位置点所对应屏幕位置为原点,逆时针为正,默认为0。|
|alignment |String |文字水平对齐属性,默认为center,可选值为left(文字左侧与位置锚点对齐)、right(文字右侧与位置锚点对齐)、center(文字水平中心与位置锚点对齐)。|
|verticalAlignment |String |文字垂直对齐属性,默认为middle,可选值为top(文字顶部与位置点对齐)、bottom(文字底部与位置点对齐)、middle(文字垂直中心与位置点对齐)。|
| height |Number |文字背景框高度,单位为像素。|
| width |Number |文字背景框宽度,单位为像素。|
| padding |String |文字背景框内边距,单位为像素,属性支持接受1~2个值,规则符合css规范。</br>例:"15px" 上下左右内边距为15px</br>例:"15px 20px" 上下内边距为15px,左右内边距为20px</br>**注意设置宽高后padding将不生效**|
| backgroundColor | String |文字背景框颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0,0,0,0);**该属性生效需要设置padding 或 width和hight**。 查看示例|
| borderWidth |Number |文字背景框边线宽度,单位为像素;**该属性生效需要设置padding 或 width和hight**。查看示例 |
| borderRadius|Number |文字背景框圆角,单位为像素;**该属性生效需要设置padding 或 width和hight**。查看示例 |
| borderColor |String |文字背景框边线颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(0,0,0,0);**该属性生效需要设置padding 或 width和hight** 。查看示例|
| wrapOptions | LabelWrapOptions | 文本自动换行,支持设置软换行和硬换行,软换行支持配置最大换行宽度,硬换行换行符为'\n'。 LabelWrapOptions如果为空对象则以文本中换行符进行换行,如果为null或undefined则不换行|
</br></br>
## LabelGeometry {#5}
----
文本数据。
|属性名称 |类型 |说明|
| :- | :- |:- |
|id |String |点图形数据的标志信息,不可重复,若id重复后面的id会被重新分配一个新id,若没有会随机生成一个。|
|styleId |String |对应MultiLabelStyleHash中的样式id,如果样式表中没有包含geometry指定的styleId,则该geometry会以默认样式绘制。|
|position |LatLng |标注点位置。|
|content |String |标注文本。|
|rank |Number |当开启文本碰撞时,值越大碰撞优先级越高。关闭碰撞时,表示标注文本的图层内绘制顺序。|
|properties |Object |标注点的属性数据。|
| collisionGroupId | String | 组合碰撞id,当开启碰撞时该id相同的文本作为一个整体进行碰撞,rank取组合中的最大值 |
</br></br>
## LabelWrapOptions {#6}
----
文本换行配置。当同时指定了最大换行宽度、最大行数和换行符时,换行优先级为: 最大行数>换行符>最大换行宽度。
|属性名称 |类型 |说明|
| :- | :- |:- |
| maxWidth | Number | 最大换行宽度,单位是像素,默认不限制 |
| maxLineCount | Number | 最大行数,默认不限制 |
| rowSpacing | Number | 行间距,单位是像素,默认为0 |
</br></br>
## CollisionOptions 对象规范{#7}
----
图层碰撞配置参数。
| 名称| 类型| 说明|
| -- | -- | -- |
| sameSource | Boolean | 是否开启图层内碰撞,优先级按rank进行碰撞,默认为false |
| crossSource | Boolean | 是否开启跨图层间碰撞,所有开启的图层间进行碰撞,优先级按zIndex进行碰撞,默认为false |
| vectorBaseMapSource | Boolean | 是否允许碰撞底图元素,默认为false |
FILE:references/jsapigl/docs/控件.md
## Control {#1}
----
控件的基类,控件的方法及常量说明。
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setPosition(position:CONTROL_POSITION) |this |设置控件的位置。|
|setClassName(className: String) |this |设置控件的css样式名。|
<br><br>
## ZoomControl {#zoomControl}
---
缩放控件,为控件Control 的子类。
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setPosition(position:CONTROL_POSITION) |this |设置控件的位置。|
|setClassName(className: String) |this |设置控件的css样式名。|
|setNumVisible(visible: boolean) |this |设置控件上是否显示缩放级别,默认为false(不显示)。|
</br></br>
## TMap.constants.CONTROL_POSITION 常量说明 {#2}
----
控件位置常量说明。
| 常量 | 说明 |
| :- | :- |
|CONTROL_POSITION.TOP_LEFT |左上|
|CONTROL_POSITION.TOP_CENTER |顶部中间|
|CONTROL_POSITION.TOP_RIGHT |右上|
|CONTROL_POSITION.CENTER_LEFT |左侧中间|
|CONTROL_POSITION.CENTER |图区中间|
|CONTROL_POSITION.CENTER_RIGHT |右侧中间|
|CONTROL_POSITION.BOTTOM_LEFT |左下|
|CONTROL_POSITION.BOTTOM_CENTER |底部中间|
|CONTROL_POSITION.BOTTOM_RIGHT |右下|
</br></br>
## TMap.constants.DEFAULT_CONTROL_ID 常量说明 {#3}
----
默认控件常量说明。
| 常量 | 说明 |
| :- | :- |
|DEFAULT_CONTROL_ID.SCALE |比例尺控件|
|DEFAULT_CONTROL_ID.ZOOM |缩放控件|
|DEFAULT_CONTROL_ID.ROTATION |旋转控件|
FILE:references/jsapigl/docs/附加库:地图视角附加库.md
## 地图视角附加库 {#1}
---
该附加库提供以观察者视角操作地图的能力。在Map类上扩展了以下方法进行观察者视角到地图视角的换算:
<br><br>
## Map 扩展方法
| 方法名 | 返回值 | 说明 |
| ------------------------------------------------------------ | ------- | ------------------------------------------------------------ |
| getMapViewWhenLookFrom(position:LatLng, rotation: Number[]) | MapView | 指定观察者所在位置及三轴方向上的旋转角度,获取地图视角。position为观察者位置,需明确高度;rotation为XYZ三轴上的旋转角度,格式为[x, y, z],默认为[0, 0, 0]。查看示例 |
| lookFrom(position: LatLng, rotation: Number[], easeOpts: EaseOptions) | this | 将地图变换到指定观察者视角,position及rotation参数说明同getMapViewWhenLookFrom |
<br><br>
## MapView 对象规范
----
MapView为地图视角描述对象。
| 名称 | 类型 | 说明 |
| -------- | ------ | ------------------------------------- |
| center | LatLng| 地图中心点经纬度 |
| zoom | Number | 地图缩放级别 |
| rotation | Number | 地图在水平面上的旋转角度 |
| pitch | Number | 地图俯仰角度,取值范围为0~80 |
| roll | Number | 地图左右倾斜角度,取值范围为(-90, 90) |
FILE:references/jsapigl/docs/事件.md
## MapEvent {#1}
----
地图事件返回参数规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|latLng |LatLng |事件发生时的经纬度坐标。|
|point |Object |事件发生时的屏幕位置,返回{x:Number, y:Number}格式|
|type |String |事件类型。|
|target |Object |事件的目标对象。|
|poi |POIInfo I null |事件触发位置的poi信息,当触发位置没有poi点时值为null(仅支持click事件)|
|originalEvent |MouseEvent 或 TouchEvent |浏览器原生的事件对象。|
</br></br>
## POIInfo {#2}
----
地图事件返回参数中的poi信息。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|latLng |LatLng |poi的经纬度位置。|
|name |String |Poi名称。|
</br></br>
## GeometryOverlayEvent {#3}
几何覆盖物事件返回参数规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|geometry |Geometry |事件发生时的图形数据信息,不同图层中该值所属对象规范不同,比如 MultiMarker(点标记图层)触发的事件中该值为PointGeometry对象。|
|latLng |LatLng |事件发生时的经纬度坐标。|
|point |Object |事件发生时的屏幕位置,返回{x:Number, y:Number}格式。|
|type |String |事件类型。|
|target |Object |事件的目标对象。|
|originalEvent |MouseEvent 或 TouchEvent |浏览器原生的事件对象。|
FILE:references/jsapigl/docs/室内图.md
## IndoorManager {#1}
----
IndoorManager用于控制室内地图的加载显示,不可实例化,只能从Map中获取;IndoorManager中的室内地图由加载地图api时设置的key决定,该key权限下的室内建筑物都会被IndoorManager加载和管理。(获取室内相关信息,请点击 这里)
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|initialize |Promise |初始化室内图管理器,返回Promise,室内图初始化完成后会进入promise的成功回调,初始化完成之前setActiveIndoorBuilding,getActiveIndoorBuilding,getIndoorBuilding等方法无效。|
|setAutoActive(autoActive:Bool) |this |设置室内建筑是否可以被自动激活;自动激活状态为true时,包含当前地图中心点的室内建筑物会被激活,激活状态下的室内建筑可以进行楼层切换等操作;处于自动激活状态下用户无法手动更改被激活的室内建筑物;自动激活状态为false时,用户可以通过setActiveIndoorBuilding来改变建筑物的激活状态。|
|setActiveIndoorBuilding(buildingId: String) |this |将buildingId对应的室内建筑设置为激活状态,激活状态下的室内建筑可以进行楼层切换等操作;若buildingId为假值所有室内建筑都将变为非激活状态,楼层控件从界面中消失;该方法只有在自动激活状态为false时有效。|
|setVisible(isVisible:Bool) |this |设置室内建筑是否显示。|
|showFloorControl() |this |显示处于激活状态的室内建筑的楼层控件,若没有激活状态的室内建筑,该方法无效。|
|hideFloorControl() |this |隐藏处于激活状态的室内建筑的楼层控件。|
|getMap() |Map |获取地图对象,若为空返回null。|
|getAutoActive() |Boolean |获取室内建筑是否可以被自动激活。|
|getActiveIndoorBuilding() |IndoorBuilding |获取处于激活状态下的室内建筑,若都为非激活状态,则返回null。|
|getVisible() |Boolean |获取室内图是否显示。|
|getIndoorBuilding(buildingId: String) |IndoorBuilding |获取室内建筑,若id不存在返回null。|
|getIndoorBuildingList() |IndoorBuilding |获取所有的室内建筑。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|active_changed |IndoorBuilding |当激活的室内建筑发生变化时触发, 事件对象为当前处于激活状态的室内建筑。|
</br></br>
## IndoorBuilding {#2}
----
IndoorBuilding代表室内建筑实例,不可被实例化,只能通过IndoorManager获取。IndoorManager可以切换IndoorBuilding的激活状态,处于激活状态的室内建筑可以切换楼层。(获取室内相关信息,请点击 这里)
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setSelectedFloor(floor:String) |this |设置当前选中楼层|
| setRegionExtrusion(factor:Number) | this | 设置室内区域墙体和面块高度的拉伸系数,最小值0.1、最大值10 |
| setPoiVisible(visible:Boolean) | this | 设置室内poi显隐 |
|getMap() |Map |获取地图对象,若为空返回null|
|getManager() |IndoorManager |获取室内建筑物的管理器|
|getSelectedFloor() |String |获取室内建筑物的当前展示的楼层|
|getBuildingInfo() |IndoorBuildingInfo |获取室内建筑物信息,indoorBuilding初始化完成后调用返回IndoorBuildingInfo对象,indoorBuilding没有初始化完成时调用返回null,推荐使用getBuildingInfoAsync|
|getBuildingInfoAsync() |Promise |异步获取室内建筑物信息,返回Promise,indoorBuilding初始化完成时resolve状态下返回IndoorBuildingInfo对象|
|isActive() |Boolean |室内建筑物是否处于激活状态|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener|
**事件:**</br>
监听事件通过on、off方法绑定与解绑。
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|floor_changed | |当室内图楼层切换时触发此事件。|
|show | |当该建筑显示时触发此事件。|
|hide | |当该建筑隐藏时触发此事件。|
</br></br>
## IndoorBuildingInfo {#3}
----
室内建筑相关信息。
| 属性方法 | 返回值 | 说明 |
| :- | :- |:- |
|cityId |String |室内建筑物所在的城市Id|
|buldingId |String |室内建筑物Id|
|buildingName |String |室内建筑名称|
|floorList |FloorInfo[] |室内建筑物的楼层信息列表|
|defaultFloor |String |室内建筑物默认显示的楼层|
|center |LatLng |室内建筑物的中心点,通常是建筑物的默认显示楼层的中心点|
</br></br>
## FloorInfo {#4}
----
室内建筑楼层相关信息。
| 属性方法 | 返回值 | 说明 |
| :- | :- |:- |
|name |String |楼层名称|
|bounds |LatLngBounds |室内建筑物的楼层范围|
FILE:references/jsapigl/docs/附加库:几何计算库.md
## geometry
----
几何计算库中的方法,进行距离、夹角、面积的计算,求取外接矩形,判断点、线、面之间的关系。
|使用示例|
| :- |
|var distance = TMap.geometry.computeDistance(path);|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|computeDistance(path: LatLng[] ) |Number |计算传入路径的距离之和(单位:米)。|
|computeDestination(start: LatLng, heading: Number I LatLng, distance: Number) |LatLng |终点计算,根据起点、朝向和距离计算终点。(或根据线段的起点、终点、距离计算,会沿着起点指向终点的方向,从终点再前进指定的距离,单位:米)正北方向为0度,顺时针为正。|
|computeDistanceToLine(point: LatLng, line: LatLng[]) |Number |计算点到直线的最短距离(单位:米)。|
|computeHeading(start: LatLng, end:LatLng, thumb: Boolean) |Number |计算两点之间的航向,默认为true,即为恒向线航向。设置为false为大圆航向,即最短路径的航向 (与正北的夹角,顺时针为正,单位:度)。|
|computeArea(polygon: LatLng[]) |Number |计算多边形面积(只支持简单多边形,即一维数组,单位:平方米)。|
|computeCentroid(polygon: LatLng[]) |LatLng |计算多边形的形心(只支持简单多边形, 即一维数组)。|
|computeBoundingRectangle(coords: LatLng[]) |LatLngBounds |计算输入点的包围矩形。|
|isPointOnSegment(point: LatLng, segment: LatLng[], tolerance: Number) |Boolean |判断点是否在线段上 (tolerance单位为米)。|
| isPointInPolygon(point: LatLng, polygon: LatLng[] \| LatLng[][], isPolygonWithHoles: Boolean) | Boolean| 判断点是否在多边形内,支持简单多边形、多个多边形和带洞多边形(在多边形边上认定为不在多边形内),isPolygonWithHoles默认为false |
|isSegmentIntersect(segment: LatLng[], segment: LatLng[]) |Boolean |判断线段是否与线段相交。|
|isSelfIntersect(polyline: LatLng[]) |Boolean |判断折线是否自相交。|
|isPolygonIntersect(polygon: LatLng[], polygon: LatLng[]) |Boolean |判断多边形是否与多边形相交(只支持简单多边形, 即一维数组)。|
| isClockwise(path: LatLng[]) | Boolean | 判断一个经纬度路径是否为顺时针 |
| isLinePolygonIntersect(line: LatLng[], polygon: LatLng[] \| LatLng[][]) | Boolean | 判断线是否与单个或多个多边形相交,仅适用于简单多边形 |
| isPolygonInPolygon(innerPolygon: LatLng[], outerPolygon: LatLng[]) | Boolean | 判断多边形是否在另一个多边形内,仅支持简单多边形 |
| isPointOnPolygon(potint: LatLng, polygon: LatLng[] \| LatLng[][], tolerance?: Number) | Boolean | 判断点是否在单个或多个多边形的边上,传入多个多边形时点在任意一多边形边上即返回true,tolerance为误差范围 |
| computeDistanceToSegment(point: LatLng, line: LatLng[]) | Number | 计算给定点与线段之间的最短距离 |
| computePolygonIntersection(polygon1: LatLng[], polygon2: LatLng[]) | LatLng[] \| null \| Boolean | 计算多边形与多边形的交集区域(只支持简单凸多边形) |
| computeClosestPointOnLine(point: LatLng, line: LatLng[]) | LatLng \| null | 计算line上距离point最近的点。 |
| transfromClockwise(path: LatLng[]) | LatLng[] \| Boolean | 将一个路径变为顺时针方向 |
| transfromAntiClockwise(path: LatLng[]) | LatLng[] \| Boolean | 将一个路径变为逆时针方向 |
FILE:references/jsapigl/docs/概述.md
## 地图 {#1}
- Map API中的核心类,用于创建地图实例。
- MapOptions 地图配置参数,通过这个参数来控制初始化地图的中心点、缩放级别、俯仰角度等。
- MapRenderOptions 地图渲染配置参数属性。
- SkyOptions 地图天空背景配置参数属性。
- FogOptions 地图雾化配置参数属性。
- FitBoundsOptions 地图自适应地理范围配置参数,可控制边距以及限制缩放等级。
- AnimationOptions 地图动画参数。
- AnimationEvent 地图动画事件回调参数规范。
- MapKeyFrame 地图动画关键帧对象。
- HighlightOptions 地图高亮区域设置。
- ClipOptions 地图掩膜区域设置。
- EaseOptions 地图缓动变化配置参数,可控制动画时长等。
- VectorBaseMap VectorBaseMap 对象规范。
- SatelliteBaseMap SatelliteBaseMap 对象规范。
- TrafficBaseMap TrafficBaseMap 对象规范。
- TMap.constants.MAP_ZOOM_TYPE 地图缩放焦点常量。
- TMap.constants.LAYER_LEVEL 图层渲染层级常量。
- TMap.constants. IMAGE_DISPLAY 图片呈现方式常量。
</br>
## 点标记 {#2}
- MultiMarker 表示地图上的多个标注点,可自定义标注的图标。
- MultiMarkerOptions MultiMarker的配置参数。
- CollisionOptions 图层碰撞配置参数。
- MarkerStyle 应用于Marker图层的样式类型。
- MarkerStyleOptions MarkerStyle配置参数。
- PointGeometry 点图形数据。
- MoveAlongParam moveAlong方法的参数对象。
- MarkerMovingEventItem 点标记沿线移动时返回的信息。
- MarkerAnimation 点标记入离场动画配置参数。
- MarkerAnimationParams 点标记动画配置参数。
- LabelWrapOptions 点标记文本换行配置参数。
</br>
## 矢量图形 {#3}
- MultiPolyline 表示地图上的多个折线,可以自定义每个折线的样式。
- MultiPolylineOptions MultiPolyline配置参数。
- PolylineStyle 表示应用于折线图层的样式类型。
- ArrowOptions 折线上箭头配置参数。
- PolylineGeometry 折线数据。
- MultiPolygon 表示地图上的多个多边形,可以自定义每个多边形的样式。
- MultiPolygonOptions MultiPolygon配置参数。
- PolygonStyle 表示应用于多边形图层的样式类型。
- ExtrudablePolygonStyle 表示应用于有立体拉伸需求的多边形的样式类型。
- PolygonGeometry 多边形数据。
- MultiRectangle 表示地图上的多个矩形,可以自定义每个矩形的样式。
- MultiRectangleOptions MultiRectangle配置参数。
- RectangleStyle 表示应用于矩形图层的样式类型。
- RectangleStyleOptions RectangleStyle配置参数。
- RectangleGeometry 多矩形数据。
- MultiCircle 表示地图上的多个圆形,可以自定义每个圆形的样式。
- MultiCircleOptions MultiCircle配置参数。
- CircleGeometry 多圆数据。
- CircleStyle 表示应用于圆图层的样式类型。
- CircleStyleOptions CircleStyle配置参数。
- MultiEllipse 表示地图上的多个椭圆,可以自定义每个椭圆的样式。
- MultiEllipseOptions MultiEllipse配置参数。
- EllipseStyle 表示应用于椭圆图层的样式类型。
- EllipseStyleOptions EllipseStyle配置参数。
- EllipseGeometry 多椭圆数据。
</br>
## 文本标记 {#4}
- MultiLabel 表示地图上的多个文本标注,可以自定义每个文本标注的样式。
- MultiLabelOptions MultiLabel配置参数。
- LabelStyle 表示应用于多标注图层的样式类型。
- LabelGeometry 文本数据。
- LabelWrapOptions 文本换行配置参数。
- CollisionOptions 图层碰撞配置参数。
</br>
## DOM覆盖物 {#5}
- DOMOverlay DOM覆盖物抽象类,用户可以此作为基类实现自定义的DOM覆盖物类。
- DOMOverlayOptions 自定义DOM覆盖物配置参数。
- CollisionOptions 图层碰撞配置参数。
</br>
## 信息窗体 {#6}
- InfoWindow 用于创建信息窗覆盖物。
- InfoWindowOptions 信息窗配置参数。
</br>
## 点聚合 {#7}
- MarkerCluster 点聚合,可以对地图上的点进行聚合。
- MarkerClusterOptions MarkerCluster配置参数。
- CollisionOptions
碰撞配置参数。
</br>
## 控件 {#8}
- Control 控件的基类,控件的方法及常量说明。
- ZoomControl 控件的基类,控件的方法及常量说明。
- TMap.constants.CONTROL_POSITION 控件位置常量说明。
- TMap.constants.DEFAULT_CONTROL_ID 默认控件常量说明。
</br>
## 自定义图层 {#9}
- ImageTileLayer 用于创建自定义栅格瓦片图层。
- ImageTileLayerOptions 配置参数。
- CustomLayerOptions 个性化图层配置参数。
- WMSLayer 创建基于OGC标准的WMS地图服务的图层类,仅支持EPSG3857坐标系统的WMS图层。
- WMSLayerOptions WMSLayer配置参数。
- WMSParams WMSParams 对象规范
- WMTSLayer 创建基于OGC标准的WMTS地图服务的图层类,仅支持EPSG3857坐标系统的WMTS图层。
- WMTSLayerOptions WMTSLayer配置参数。
- WMTSParams WMTSParams 对象规范
- ImageGroundLayer 用于创建自定义图片图层,图片会随着地图缩放而缩放。
- ImageGroundLayerOptions ImageGroundLayer配置参数。
- CanvasGroundLayer 用于创建自定义图片图层,图片会随着地图缩放而缩放。
- CanvasGroundLayerOptions CanvasGroundLayer配置参数。
- MaskLayer 用于创建遮罩图层。
- MaskLayerOptions MaskLayer配置参数。
- MaskGeometry 单个遮罩区域数据。
- GLCustomLayer 用于创建自定义GL图层,可配合threejs实现自定义覆盖物效果。
- GLCustomLayerOptions GLCustomLayer配置参数。
</br>
## 事件 {#10}
- MapEvent 地图事件返回参数规范。
- POIInfo 地图事件返回参数中的poi信息。
- GeometryOverlayEvent 几何覆盖物事件返回参数规范。
</br>
## 基础类 {#11}
- LatLng 用于创建经纬度坐标实例。
- LatLngBounds 描述一个矩形的地理坐标范围。
- Point 用于创建二维坐标点。
- GradientColor 用于创建渐变色实例。
- GradientColorOptions GradientColor 配置参数。
</br>
## 室内图 {#12}
- IndoorManager 室内建筑物管理器,用于控制室内地图的加载显示,不可实例化,只能从Map中获取。
- IndoorBuilding 代表室内建筑实例,不可被实例化,只能通过IndoorManager获取。
- IndoorBuildingInfo 室内建筑相关信息。
- FloorInfo 室内建筑楼层相关信息。
</br>
## 附加库:地图工具 {#13}
- GeometryEditor 几何图形编辑器。
- GeometryEditorOptions 几何编辑器配置参数对象规范。
- EditableOverlay 可编辑几何图层对象规范。
- TMap.tools.constants.EDITOR_ACTION 编辑器操作模式常量。
- EditorIconInfo 编辑器所包含的icon信息对象。
- IconStyle 编辑器icon的样式对象,可以设置src、宽高等属性。
- EditingEvent 编辑器所包含的icon信息对象。
- DrawErrorMessage 编辑器绘制失败错误信息。
- AdjustErrorMessage 编辑器编辑失败错误信息。
- MeasureTool 测量工具用于地图测量,继承自Tool抽象类。
- MeasureToolOptions 测量工具用于地图测量,继承自Tool抽象类。
- DistanceMeasurementInfo 距离测量结果对象规范
- AreaMeasurementInfo 面积测量结果对象规范
</br>
## 附加库:几何计算库 {#14}
- geometry 进行距离、夹角、面积的计算,求取外接矩形,判断点、线、面之间的关系。
</br>
## 附加库:服务类库 {#15}
- Search 地点搜索
- Suggestion 关键词输入提示
- Geocoder 正逆地址解析
- Driving 路线规划
- District 行政区划
- IPLocation IP定位
</br>
## 附加库:地图视角附加库 {#16}
- Map 扩展方法 提供以观察者视角操作地图的能力
</br>
## 附加库:模型库 {#17}
- GLTFModel 用于创建GLTF模型对象,叠加在地图上进行显示。
- ModelOptions 创建模型对象的配置参数。
- ModelMoveAlongParam 模型沿线移动配置参数。
- PathPoint 模型沿线移动路径节点配置参数。
- ModelLoadEvent 模型对象的加载事件返回对象。
- ModelMouseEvent 模型对象的鼠标事件返回对象。
- AnimationOptions 模型动画参数。
- GLTFKeyFrame GLTF模型关键帧对象。
- Tileset3D Tileset3D用于创建3DTiles模型对象,叠加在地图上进行显示。
- Tileset3DOptions 创建 3DTiles 模型对象的配置参数。
- Tileset3DMouseEvent Tileset3D 单体模型鼠标事件返回对象。
- Marker3D Marker3D用于创建表示地图上3D样式的标注点。
- Marker3DOptions 创建 3DMarker 对象的配置参数。
- Marker3DStyle Marker3DStyle 应用于Marker3D图层的样式类型。
- Marker3DStyleOptions Marker3DStyle配置参数。
- Marker3DGeometry Marker3D点图形数据。
</br>
## 附加库:天气图层 {#weatherLayer}
- WeatherLayer 用于创建天气图层 ,用户传入对应瓦片key、tileUrl、 time和天气图类型,即可生成对应的气象图效果,当前支持云图、温度图、低云图三种类型。
- weatherLayerOptions 天气图层配置参数。
- WeatherLayerEvent 天气图层事件返回参数规范。
- TMap.weather.constants.WEATHER_TYPE 天气图层类型常量。
</br>
## 附加库:矢量数据图层 {#17}
- GeoJSONLayer 用于解析、加载GeoJSON格式的数据
- GeoJSONLayerOptions GeoJSONLayer配置参数。
- MVTLayer 用于创建符合mapbox-vector-tile标准的图层对象,叠加在地图上进行显示。
- MVTLayerOptions MVTLayer配置参数。
</br>
## 环境检测 {#18}
- Brower 用于环境检测
</br>
FILE:references/visualization/demos/3D经典热力.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>3D经典热力</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/heat.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.909897147274364, 116.39756310116866);
//初始化地图
var map = new TMap.Map("container", {
zoom: 12,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化热力图并添加至map图层
new TMap.visualization.Heat({
max: 180, // 热力最强阈值
min: 0, // 热力最弱阈值
height: 40, // 峰值高度
radius: 30 // 最大辐射半径
})
.addTo(map)
.setData(heatData);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/单色散点图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>单色散点图</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/users_tencent.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 3, //设置地图缩放级别
center: new TMap.LatLng(40.11856816068578, 65.01399123678493), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
renderOptions: {
enableBloom: true, // 泛光
},
});
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot({
styles: {
default: {
fillColor: "#1DFAF2", //圆形填充颜色
radius: 1, //圆形半径
},
},
enableBloom: true, // 泛光
})
.addTo(map)
dot.setData(dotData); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/2D网格热力.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>2D网格热力</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom: 10,//设置地图缩放级别
pitch: 45,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化网格热力图图并添加至map图层
var grid =new TMap.visualization.Grid({
sideLength: 1000, // 设置网格边长
extrudable: false, // 网格不可拔起,可以做出贴地效果
heightRange:[1, 5000], // 高度变化区间
showRange:[1, 100] // 聚合数据显示区间
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
grid.setShowRange(grid.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/区域图鼠标事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>区域面块鼠标事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/areaClick.js"></script>
<script>
function initMap() {
var info = document.getElementById("info");
var center = new TMap.LatLng(40.046014541872594, 116.28684997558594);
//初始化地图
var map = new TMap.Map("container", {
zoom: 15, //设置地图缩放级别
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: { //设置底图样式
type: 'vector', //设置底图为矢量底图
features: [ //设置矢量底图要素类型
'base',
'point'
]
},
});
var paths = [];
areaDatas.forEach(item => {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'styel1' //设置区域样式id
})
})
var area = new TMap.visualization.Area({
styles: { //设置区域图样式
styel1: {
fillColor: 'rgba(56,124,234,0.7)', //设置区域填充颜色
strokeColor: '#6799EA', //设置区域边线颜色
strokeWidth: 1,
}
},
selectOptions: { //设置拾取配置
action: 'hover',
style: {
fillColor: 'rgba(28,213,255,0.8)', //设置区域填充颜色
strokeColor: '#fff', //设置区域边线颜色
strokeWidth: 1, //区域边线宽度
strokeDashArray: [0, 0] //边线虚线展示方式
},
}
}).setData(paths).addTo(map);
}
</script>
</body>
</html>
FILE:references/visualization/demos/散点图点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>散点图点击事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/Dot.js"></script>
<script>
function initMap() {
var info = document.getElementById("info");
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
zoom:8,//设置地图缩放级别
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot({
faceTo:"screen",//散点固定的朝向
selectOptions: { //拾取配置
action: 'click', //拾取动作
style: { //选中样式
fillColor: "#1CD5FF",
},
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(pointData);//设置数据
//绑定点击事件
dot.on("click",function(evt){
if(evt.detail.dot){
info.style.display = 'block';
info.innerHTML = "当前点击点坐标为:" + evt.detail.dot.lat+","+evt.detail.dot.lng;
} else {
info.style.display = '';
}
})
}
</script>
</body>
</html>
FILE:references/visualization/demos/轨迹回放动画.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>轨迹回放动画</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=geometry,visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 30px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #919aac;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
}
.btnContainer button:focus {
outline: none;
}
.btnContainer .btn {
padding: 10px 14px;
background: #3876ff;
border-radius: 2px;
border: none;
box-sizing: border-box;
font-size: 14px;
color: #fff;
line-height: 14px;
font-family: PingFangSC-Regular;
}
input {
height: 25px;
}
.info {
background-color: white;
padding: 10px;
font-size: 14px;
}
</style>
<body>
<div class="btnContainer">
<button class="btn" onclick="btn()">点击开始移动</button>
</div>
</div>
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/trackPlayback.js"></script>
<script type="text/javascript">
var center = new TMap.LatLng(40.01441917053406, 116.39308580988711);
// 初始化地图
var map = new TMap.Map("container", {
zoom: 17,
pitch: 50,
mapStyleId:'style4',
baseMap: {
type: "vector",
features: ["base", "building3d"],
},
center,
});
var isMoving = false;
var roation;
var position;
var markerData = []; //marker数据
var trailData = [] // trail数据
var path = []
var colorOffset = []
data.forEach((i) => {
markerData.push(new TMap.LatLng(i.lat, i.lng));
path.push([i.lat,i.lng])
// 不同速度区间对应颜色
if(i.speed>= 5.05 && i.speed<=5.13 ){
colorOffset.push(0.99)
}else if(i.speed <=5.58 && i.speed >=5.34){
colorOffset.push(0.01)
}else {
colorOffset.push(0.5)
}
});
trailData.push({path,colorOffset})
//初始化marker并添加至map图层
var marker = new TMap.MultiMarker({
id: "whiteDot",
map,
styles: {
"whiteDot": new TMap.MarkerStyle({
width: 25,
height: 25,
anchor: {
x: 14.5,
y: 14.5,
},
faceTo: "map",
rotate: 180,
src: "https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/white.png",
}),
start: new TMap.MarkerStyle({
width: 22,
height: 32,
anchor: {
x: 10,
y: 27,
},
src: "https://mapapi.qq.com/web/miniprogram/demoCenter/images/marker-start.png",
}),
end: new TMap.MarkerStyle({
width: 22,
height: 32,
anchor: {
x: 2,
y: 27,
},
src: "https://mapapi.qq.com/web/miniprogram/demoCenter/images/marker-end.png",
}),
},
geometries: [
{
id: "whiteDot",
styleId: "whiteDot",
position: new TMap.LatLng(40.0117548291348, 116.39341473604895),
},
{
id: "start",
styleId: "start",
position: new TMap.LatLng(40.0117548291348, 116.39341473604895),
},
],
});
//初始化轨迹图并添加至map图层
var trail = new TMap.visualization.Trail({
pickStyle: function (item) {
//轨迹图样式映射函数
return {
width: 6,
color: new TMap.GradientColor({
stops: {
0.01: "#f45e0c",
0.5: "#f6cd0e",
0.99: "#2ad61d",
},
}),
};
},
showDuration: Infinity, //动画中轨迹点高亮的持续时间
playTimes: 1,
playRate: 90, //动画播放倍速,默认为1m/s
enableColorOffset:true
}).addTo(map);
function carMove() {
marker.moveAlong(
{
whiteDot: {
path:markerData,
speed: 324, // 速度 单位:千米/小时
},
},
{
autoRotation: true,
}
);
trail.setData(trailData); //设置数据
isMoving = true;
}
marker.on('moving', function (e) {
if (e.whiteDot) {
roation = TMap.geometry.computeHeading(
e.whiteDot.passedLatLngs[e.whiteDot.passedLatLngs.length - 2],
e.whiteDot.passedLatLngs[e.whiteDot.passedLatLngs.length - 1]
);
position = TMap.geometry.computeDestination(marker.getGeometryById('whiteDot').position, roation, 60);
}
map.easeTo(
{
center: position,
rotation: e.whiteDot.angle,
zoom: 17,
pitch: 50,
},
{
duration:500,
}
);
});
function btn(){
if (isMoving) return;
carMove()
}
marker.on('move_ended', function () {
isMoving = false;
marker.updateGeometries([
{
id: "end",
styleId: "end",
position: new TMap.LatLng(40.01178717766782, 116.3914374715223),
},
]);
map.easeTo(
{
zoom: 16.5,
pitch: 45,
rotation:355.5,
center,
},
{
duration: 100,
}
);
marker.remove("whiteDot")
});
</script>
</body>
</html>
FILE:references/visualization/demos/区域图鼠标双击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>区域图鼠标双击事件</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization,geometry"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info {
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/areaClick.js"></script>
<script>
var map;
function initMap() {
var info = document.getElementById("info");
var center = new TMap.LatLng(40.046014541872594, 116.28684997558594);
//初始化地图
map = new TMap.Map("container", {
zoom: 15, //设置地图缩放级别
center: center, //设置地图中心点坐标
doubleClickZoom: false, // 禁用双击地图放大
mapStyleId: "style4", //个性化样式
baseMap: { //设置底图样式
type: 'vector', //设置底图为矢量底图
features: [ //设置矢量底图要素类型
'base',
'point'
]
},
});
var paths = [];
areaDatas.forEach((item, index) => {
if (index % 3 === 1) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style1' //设置区域样式id
})
} else if (index % 3 === 2) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style2' //设置区域样式id
})
} else {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style3' //设置区域样式id
})
}
})
var area = new TMap.visualization.Area({
styles: { //设置区域图样式
style1: setStyles(0.8),
style2: setStyles(0.5),
style3: setStyles(0.2),
},
}).setData(paths).addTo(map);
// 模拟双击事件
let clickCount = 0;
let clickTimeout;
const delay = 220; // 定义两次点击之间的最大时间间隔,单位为毫秒
area.on('click', function (e) {
if (!e.detail.area) return;
clickCount++;
const area = e.detail.area;
if (clickCount === 1) {
clickTimeout = setTimeout(function () {
if (clickCount === 1) {
// 单击事件
console.log('Single click', e);
} else {
// 双击事件
console.log('Double click', e);
dbClickEvent(area);
}
clickCount = 0;
}, delay);
} else if (clickCount === 2) {
clearTimeout(clickTimeout);
// 双击事件
console.log('Double click', e);
dbClickEvent(area);
clickCount = 0;
}
});
}
// 双击时移动至对应区域
function dbClickEvent(area) {
const path = area.path;
const polygonPath = convertNumberToLatLng(path);
const pointArray = flattenArray(polygonPath);
map.fitBounds(TMap.geometry.computeBoundingRectangle(pointArray));
}
//设置style样式工厂函数
function setStyles(opacity) {
return {
fillColor: `rgba(56,124,234,opacity)`, //设置区域填充颜色
strokeColor: '#6799EA', //设置区域边线颜色
}
}
// 数组中的数字转换为LatLng
function convertNumberToLatLng(paths) {
const polygonPath = [];
if (Array.isArray(paths[0])) {
paths.forEach(path => {
polygonPath.push(convertNumberToLatLng(path));
});
} else {
for (let i = 0; i < paths.length; i += 2) {
polygonPath.push(new TMap.LatLng(paths[i], paths[i + 1]));
}
}
return polygonPath;
}
// 多维数组展开
function flattenArray(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flattenArray(toFlatten) : toFlatten);
}, []);
}
</script>
</body>
</html>
FILE:references/visualization/demos/弧线图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>弧线图</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var data = arcData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 5,//设置地图缩放级别
pitch: 30,
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
new TMap.visualization.Arc({
animatable: false,
mode: 'vertical' // 弧线平面与地平面垂直
})
.addTo(map)
.setData(data);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/弧线流向图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>弧线流向图</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var data = arcData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 5,//设置地图缩放级别
pitch: 30,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
new TMap.visualization.Arc({
mode: 'vertical' // 弧线平面与地平面垂直
})
.addTo(map)
.setData(data);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/散点动画.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>散点动画</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color:#ffffff;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="name">深圳市交通事故上报预警</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/shenzhen_trafficAccident.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 11.5, //设置地图缩放级别
center: new TMap.LatLng(22.638653072216666, 114.0831825194266), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式20
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
renderOptions: {
enableBloom: true, // 泛光
},
});
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot({
faceTo: "map", //散点固定的朝向
styles: {
style1: {
fillColor: "#FFCA1F",
radius: 1,
},
style2: {
fillColor: "#C72A18",
radius: 2,
},
},
enableBloom: true, // 泛光
})
.addTo(map)
dot.setData(docAnimationData);
//初始化散点图并添加至map图层
var dotAnimation = new TMap.visualization.Dot({
faceTo: "map", //散点固定的朝向
styles: {
style1: {
type: "circle", //圆形样式
fillColor: "rgba(255,202,31,0.3)",
radius: 3,
},
style2: {
type: "circle", //圆形样式
fillColor: "rgba(204,42,24,0.3)",
radius: 10,
},
},
processAnimation: {
animationType: "radiated", //动画类型
},
})
.addTo(map)
dotAnimation.setData(docAnimationData); //添加数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/2D经典热力.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>2D经典热力</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/heat.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.909897147274364, 116.39756310116866);
//初始化地图
var map = new TMap.Map("container", {
zoom: 11.7,//设置地图缩放级别
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化热力图并添加至map图层
var heat = new TMap.visualization.Heat({
max: 350, // 热力最强阈值
min: 0, // 热力最弱阈值
height: 0, // 峰值高度
gradientColor: { // 渐变颜色
0.6: "#673198",
0.8: "#e53390",
0.9: "#ffc95a",
},
radius: 30 // 最大辐射半径
}).addTo(map);
heat.setData(heatData);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/图标散点图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>图标散点图</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color:#ffffff;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="name">北京市交通事件概况</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/beijing_trafficTime.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 11.2, //设置地图缩放级别
center: new TMap.LatLng(39.94777457424578, 116.40822675986203), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot({
styles: {
accident: setStyles("accident"),
construction: setStyles("construction"),
inspect: setStyles("inspect"),
other: setStyles("other"),
},
})
.addTo(map)
dot.setData(dotImgData); //设置数据
function setStyles(name) {
return {
type: "image", //设置图片
width: 20, //宽度
height: 20, //高度
anchor: { x: 10, y: 10 }, //图片锚点位置
src: `https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/name.png`, //图片样式属性
};
}
}
</script>
</body>
</html>
FILE:references/visualization/demos/区域图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>区域面块图</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info">
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/areaClick.js"></script>
<script>
function initMap() {
var info = document.getElementById("info");
var center = new TMap.LatLng(40.046014541872594, 116.28684997558594);
//初始化地图
var map = new TMap.Map("container", {
zoom: 15, //设置地图缩放级别
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: { //设置底图样式
type: 'vector', //设置底图为矢量底图
features: [ //设置矢量底图要素类型
'base',
'point'
]
},
});
var paths = [];
areaDatas.forEach((item, index) => {
if(index % 3 === 1) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'styel1' //设置区域样式id
})
} else if(index % 3 ===2) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'styel2' //设置区域样式id
})
} else {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'styel3' //设置区域样式id
})
}
})
var area = new TMap.visualization.Area({
styles: { //设置区域图样式
styel1: setStyles(0.8),
styel2: setStyles(0.5),
styel3: setStyles(0.2),
},
}).setData(paths).addTo(map);
}
//设置style样式工厂函数
function setStyles(opacity) {
return {
fillColor: `rgba(56,124,234,opacity)`, //设置区域填充颜色
strokeColor: '#6799EA', //设置区域边线颜色
}
}
</script>
</body>
</html>
FILE:references/visualization/demos/轨迹图鼠标事件选定样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>轨迹图鼠标事件选定样式</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/trail.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
var infoDom = document.getElementById('info');
var data = trailData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 12, //设置地图缩放级别
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化轨迹图并添加至map图层
var trail = new TMap.visualization.Trail({
pickStyle:function(item){ //轨迹图样式映射函数
return {
width: 4
}
},
startTime: 0, //动画循环周期的起始时间戳
showDuration: 120, //动画中轨迹点高亮的持续时间
playRate:30, // 动画播放倍速
selectOptions: { //拾取配置
action: 'click', //拾取动作
style: { //选中样式
width: 6,
color: "#1CD5FF"
},
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/弧线中点位置标记.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>弧线中点位置标记</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var data = arcData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 5, //设置地图缩放级别
pitch: 30,
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
var arc = new TMap.visualization.Arc({
mode: "vertical", // 弧线平面与地平面垂直
})
.addTo(map)
.setData(data); //设置数据
var date = arc.getMidPositions().slice(0, 10); // 截取10条数据
let geometries = [];
date.forEach((i, index) => {
let count = data[index].count.toString(); //获取文本
var marker = {
//每个marker位置及文本
position: i, //标注点位置
content: count, //标注点文本
};
geometries.push(marker);
});
//初始化点标记
var marker = new TMap.MultiMarker({
map: map, //显示Marker图层的底图
styles: {
default: new TMap.MarkerStyle({
width: 70, //宽度
height: 26, //高度
anchor: { x: 36, y: 32 }, //标注点图片的锚点位置
src: "https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/ellipse.png", ////标注点图片url或base64地址
color: "#fff", ////标注点文本颜色
size: 16, //标注点文本文字大小
offset: { x: 0, y: 1 }, //标注点文本文字基于direction方位的偏移属性
}),
},
geometries: geometries, //点标注数据数组
});
}
</script>
</body>
</html>
FILE:references/visualization/demos/弧线流向组合图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>弧线流向组合图</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color: #ffffff;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="name">北京市人口迁出图</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/migration.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 5, //设置地图缩放级别
pitch: 46.9, //地图俯仰角度
rotation: 13.6, //地图在水平面上的旋转角度
center: new TMap.LatLng(34.07471242503055, 108.65100505987527), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
renderOptions: {
enableBloom: true, // 泛光
},
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
var arc = new TMap.visualization.Arc({
pickStyle: function (item) {
//轨迹图样式映射函数
var style;
if (item.count < 7000) {
style = {
width: 2,
color: "rgba(230,129,28,0.1)",
animateColor: "#FFCA1F",
};
} else {
style = {
width: 2,
color: "rgba(1,124,247,0.1)",
animateColor: "#1DFAF2",
};
}
return style;
},
enableBloom: true, //泛光
processAnimation: {
duration: 2000, //动画时长
tailFactor: 0.7, //尾迹比例
},
mode: "vertical", // 弧线平面与地平面垂直,
})
.addTo(map)
.setData(arcData); //设置数据
// 初始化辐射圈
var radiationCircle = new TMap.visualization.Radiation({
styles: {
default: {
fillColor: "rgba(0,0,0,0)", // 辐射圈填充颜色
strokeColor: "#FFF", // 辐射圈边线颜色
strokeWidth: 25536, // 区域边线宽度
},
},
number: 2, // 每一时刻,辐射圈的同心圆个数
})
.addTo(map)
.setData(radiationData);
//初始化散点图
var dot = new TMap.visualization.Dot({
styles: {
default: {
fillColor: "#FFF", //圆形填充颜色
radius: 2, //圆形半径
},
},
enableBloom: true, // 泛光
})
.addTo(map)
.setData(dotData); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/弧线图点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>弧线图点击事件</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var infoDom = document.getElementById('info');
var data = arcData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 5,//设置地图缩放级别
pitch: 30,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
var arc = new TMap.visualization.Arc({
animatable: true,
mode: 'vertical', // 弧线平面与地平面垂直
selectOptions: { //拾取配置
action: 'click', //拾取动作
style: { //选中样式
width: 6,
color: "#1CD5FF",
animateColor: "#A8EFFF"
},
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data);//设置数据
// 绑定事件
arc.on('click', evt => {
var aggregator = evt && evt.detail && evt.detail.arc;
if (aggregator) {
infoDom.style.display = 'block';
infoDom.innerHTML = '弧线数据值: ' + aggregator.count;
} else {
infoDom.style.display = null;
}
});
}
</script>
</body>
</html>
FILE:references/visualization/demos/3D网格热力.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>3D网格热力</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom: 10,//设置地图缩放级别
pitch: 45,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化网格热力图图并添加至map图层
var grid =new TMap.visualization.Grid({
sideLength: 1000, // 设置网格边长
heightRange: [1, 30000], // 高度变化区间
showRange:[1, 100] // 聚合数据显示区间
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
grid.setShowRange(grid.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/动态设置弧线图参数.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>动态设置弧线图参数</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#curAddBtn {
position: absolute;
left: 50px;
top: 50px;
z-index: 1001;
}
#curDecBtn {
position: absolute;
left: 150px;
top: 50px;
z-index: 1001;
}
#aniSwitchBtn {
position: absolute;
left: 50px;
top: 90px;
z-index: 1001;
}
</style>
<body onload="initMap()">
<div id="container">
<button id="curAddBtn">增加曲度</button>
<button id="curDecBtn">减小曲度</button>
<button id="aniSwitchBtn">改变动画总时长与高亮时长</button>
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var data = arcData;
var curAddBtn = document.getElementById('curAddBtn');
var curDecBtn = document.getElementById('curDecBtn');
var aniSwitchBtn = document.getElementById('aniSwitchBtn');
//初始化地图
var map = new TMap.Map("container", {
zoom: 5,//设置地图缩放级别
pitch: 30,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
var arc = new TMap.visualization.Arc({
animatable: true,
mode: 'vertical' // 弧线平面与地平面垂直
})
.addTo(map)
.setData(data);//设置数据
curAddBtn.addEventListener('click', () => {
var curvature = arc.getCurvature() + 0.1;
if(curvature <= 1) { // 曲度最大不能超过1
arc.setCurvature(curvature); // 调用参数访问器,设置弧线图的曲度
}
});
curDecBtn.addEventListener('click', () => {
var curvature = arc.getCurvature() - 0.1;
if(curvature > 0) { // 曲度最小不能低于0
arc.setCurvature(curvature); // 调用参数访问器,设置弧线图的曲度
}
});
aniSwitchBtn.addEventListener('click', () => {
var highlight = arc.getHighlightDuration() + 5000; // 给高亮时间增加5s
var animDuration = arc.getAnimDuration() + 10000; // 给动画总时间增加10s
arc.setAnimDuration(animDuration);
arc.setHighlightDuration(highlight); // 调用参数访问器进行设置
})
}
</script>
</body>
</html>
FILE:references/visualization/demos/弧线图鼠标事件选定样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>弧线图鼠标事件选定样式</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var data = arcData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 5,//设置地图缩放级别
pitch: 30,
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
new TMap.visualization.Arc({
animatable: true,
mode: 'vertical', // 弧线平面与地平面垂直
selectOptions: { //拾取配置
action: 'hover', //拾取动作
style: { //选中样式
width: 6,
color: "#1CD5FF",
animateColor: "#A8EFFF"
},
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/网格热力动画.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>网格热力动画</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color:#ffffff;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="name">深圳市路口车辆轨迹状态</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/shenzhen_intersection.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 10.4, //设置地图缩放级别
pitch: 50, //地图俯仰角度
rotation: 156.8, //地图旋转角度
center: new TMap.LatLng(22.48316065765768, 113.98332510517389), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化网格热力图图并添加至map图层
var grid = new TMap.visualization.Grid({
sideLength: 500, // 设置网格边长
heightRange: [1, 30000], // 高度变化区间
showRange: [1, 100], // 聚合数据显示区间
toggleAnimation: {
animationType: "grow", //动画类型
duration: 4000, //动画时长
},
colorList: ["#ea9c21", "#ea8e21", "#e6721c", "#e55f1d", "#cc2a18"], //颜色层级
})
.addTo(map)
grid.setData(gridData); //设置数据
// 数据聚合之后才能够真正获取值域范围
grid.setShowRange(grid.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/水晶体.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, addTo-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>水晶体</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
position: relative;
}
#container {
width: 100%;
height: 100%;
}
#box {
position: absolute;
z-index: 9999;
bottom: 50px;
left: 50%;
transform: translateX(-50%);
width: 1103px;
}
#button {
width: 100%;
height: 5px;
background-image: url("https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/progressBar.png");
background-size: 100% 100%;
display: flex;
justify-content: space-between;
align-items: center;
}
#optional {
width: 19.5%;
height: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-left: -5px;
}
#optional div {
border-radius: 50%;
width: 22px;
height: 22px;
background-image: url("https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonOptional.png");
background-size: 100% 100%;
cursor: pointer;
}
#optional div:nth-child(3) {
background-image: url("https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonSelec.png");
background-size: 100% 100%;
}
#notOptional {
width: 81.5%;
height: 100%;
box-sizing: border-box;
padding-left: 7%;
display: flex;
justify-content: space-between;
align-items: center;
margin-right: -10px;
}
#notOptional div {
border-radius: 50%;
width: 22px;
height: 22px;
cursor: pointer;
background-image: url("https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonNotOptional.png");
background-size: 100% 100%;
}
#month {
width: 100%;
display: flex;
align-items: center;
margin-top: 20px;
color: #fff;
}
#month #optional_text {
display: flex;
justify-content: space-between;
align-items: center;
margin-left: -20px;
}
#month #optional_text div {
height: 34px;
width: 74px;
line-height: 34px;
cursor: pointer;
border-right: 50px;
text-align: center;
margin-right: 12px;
}
#month #optional_text div:nth-child(1) {
margin-left: -10px;
}
#month #optional_text div:nth-child(2) {
margin-left: 10px;
}
#month #optional_text div:nth-child(3) {
margin-left: 10px;
margin-right: 19px;
}
#month #notOptional_text {
display: flex;
justify-content: space-between;
align-items: center;
margin-left: 10px;
color: rgba(255, 255, 255, 0.3);
}
#month #notOptional_text div {
height: 34px;
line-height: 34px;
width: 74px;
cursor: pointer;
border-right: 50px;
text-align: center;
margin-right: 26px;
}
#notes {
position: absolute;
z-index: 9999;
top: 30px;
left: 30px;
width: 180px;
height: 200px;
background-image: url("https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/notes.png");
background-size: 100% 100%;
}
</style>
<body>
<div id="container"></div>
<div id="box">
<div id="button">
<div id="optional">
<div onclick="switchsButton(JanuaryData,this)"></div>
<div onclick="switchsButton(FebruaryData,this)"></div>
<div onclick="switchsButton(MarchData,this)"></div>
</div>
<div id="notOptional">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
</div>
<div id="month">
<div id="optional_text">
<div onclick="switchsMonth(JanuaryData,this)">一月</div>
<div onclick="switchsMonth(FebruaryData,this)">二月</div>
<div onclick="switchsMonth(MarchData,this)">三月</div>
</div>
<div id="notOptional_text">
<div>四月</div>
<div>五月</div>
<div>六月</div>
<div>七月</div>
<div>八月</div>
<div>九月</div>
<div>十月</div>
<div>十一月</div>
<div>十二月</div>
</div>
</div>
</div>
<div id="notes"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/prism.js"></script>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/maskLayerData.js"></script>
<script>
//初始化地图
var map = new TMap.Map("container", {
zoom: 16.5, //设置地图缩放级别
center: new TMap.LatLng(39.95062479772264, 116.22699864347408), //设置地图中心点坐标
pitch: 48.5, // 设置地图俯仰角
mapStyleId: "style4", //个性化样式
rotation: 351.7,
renderOptions: {
enableBloom: true, // 泛光
},
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
// 遮罩层
var MaskLayer = new TMap.MaskLayer({
map: map,
geometries: [{ paths: maskLayerData }],
});
//创建水晶体
var prism = new TMap.visualization.Prism({
enableLighting: true, //水晶体是否开启光照
}).addTo(map);
function addTo(data, index) {
var styles = {};
var paths = [];
var buildingHeight;
data.forEach((item) => {
buildingHeight = item.floor * 10;
item.area.forEach((i) => {
var style = "style" + buildingHeight + i.risk;
if (i.paths[0].toString().indexOf("116.") != -1) {
i.paths = i.paths.reverse();
} else {
i.paths = i.paths;
}
if (i.risk === null) {
styles[style] = {
topFillColor: "rgba(62,84,125,1)", //水晶体顶面颜色
sideFillColor: "rgba(51,69,102,1)", //水晶体侧面颜色
height: buildingHeight, //水晶体拔起高度
};
paths.push({
path: i.paths,
styleId: style,
});
} else if (i.risk === "low") {
styles[style] = {
topFillColor: "rgba(30,155,89,1)", //水晶体顶面颜色
sideFillColor: "rgba(25,130,74,1)", //水晶体侧面颜色
height: buildingHeight, //水晶体拔起高度
};
paths.push({
path: i.paths,
styleId: style,
});
} else if (i.risk === "middle") {
styles[style] = {
topFillColor: "rgba(223,138,57,1)", //水晶体顶面颜色
sideFillColor: "rgba(191,118,50,1)", //水晶体侧面颜色
height: buildingHeight, //水晶体拔起高度
};
paths.push({
path: i.paths,
styleId: style,
});
} else if (i.risk === "high") {
styles[style] = {
topFillColor: "rgba(212,84,66,1)", //水晶体顶面颜色
sideFillColor: "rgba(174,67,53,1)", //水晶体侧面颜色
height: buildingHeight, //水晶体拔起高度
};
paths.push({
path: i.paths,
styleId: style,
});
}
});
});
prism.setStyles(styles);
prism.setData(paths);
}
//默认数据
addTo(MarchData);
var optional_text = document.getElementById("optional_text");
var month = optional_text.children;
month[2].style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/monthBackground.png" +
")"; //月份默认选中
//选中圆点
function switchsButton(val, e) {
// 默认选中态消失
month[0].style.backgroundImage = null;
// 实现按钮选中效果
for (let index = 0; index < e.parentNode.children.length; index++) {
if (e.parentNode.children[index] === e) {
month[index].style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/monthBackground.png" +
")";
e.style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonSelec.png" +
")";
} else {
month[index].style.backgroundImage = null;
e.parentNode.children[index].style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonOptional.png" +
")";
}
}
//添加数据
addTo(val);
}
var optional = document.getElementById("optional");
//选中月份
function switchsMonth(val, e) {
month[0].style.backgroundImage = null;
// 实现月份选中效果
for (let index = 0; index < e.parentNode.children.length; index++) {
if (e.parentNode.children[index] === e) {
optional.children[index].style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonSelec.png" +
")";
e.style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/monthBackground.png" +
")";
} else {
e.parentNode.children[index].style.backgroundImage = null;
optional.children[index].style.backgroundImage =
"url(" +
"https://mapapi.qq.com/web/lbs/visualizationApi/demo/img/buttonOptional.png" +
")";
}
}
// 添加数据
addTo(val);
}
</script>
</body>
</html>
FILE:references/visualization/demos/动态围墙面.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>动态围墙线</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/wallLine.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 11.5, //设置地图缩放级别
center: new TMap.LatLng(40.0324251949936, 116.23647955854779), //设置地图中心点坐标
pitch: 40, // 设置地图俯仰角
mapStyleId: "style4", //个性化样式
renderOptions: {
enableBloom: true, // 泛光
},
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
var path = convertToPath(wallLine);
var wallData = {
path: path, // 经纬度数组
height: 4500, //围墙面高度
styleId: "style1", //围墙面样式配置id
};
//初始化围墙线
var Wall = new TMap.visualization.Wall({
styles: {
style1: {
color: new TMap.GradientColor({
stops: {
0: "rgba(1,124,247,0.6)",
1: "rgba(29,250,242,1)",
},
angle: 90, //渐变色中的断点集合
}),
strokeColor: 'rgba(29,250,242,1)', //边线颜色
strokeWidth: 2, //边线宽度
},
},
processAnimation: {
animationType: "breath", //动画类型名称
breathAmplitude: 0.5, //呼吸幅度
yoyo: true, //是否回弹
duration: 1000, //动画时长
},
})
.addTo(map)
Wall.setData([wallData]);
function convertToPath(array) {
return array.map((p) => {
if (p.length == 2) return new TMap.LatLng(p[0], p[1]);
if (p.length == 3) return new TMap.LatLng(p[0], p[1], p[2]);
});
}
}
</script>
</body>
</html>
FILE:references/visualization/demos/3D蜂窝热力.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>3D蜂窝热力</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//处理hexagon数据
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom:10,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化蜂窝热力图图并添加至map图层
var hexagon =new TMap.visualization.Hexagon({
radius: 1000, //六边形中心点到端点的距离(半径)
extrudable: true,//六边形是否可拔起
heightRange: [1, 30000], // 高度变化区间
showRange:[1, 100] //蜂窝聚合数据显示区间
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
hexagon.setShowRange(hexagon.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/区域图使用编辑器.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>区域图使用编辑器</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization,tools"></script>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/areaClick.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 80%;
position: relative;
}
#toolControl {
position: absolute;
top: 10px;
left: 0px;
right: 0px;
margin: auto;
width: 126px;
z-index: 1001;
}
.toolItem {
width: 30px;
height: 30px;
float: left;
margin: 1px;
padding: 4px;
border-radius: 3px;
background-size: 30px 30px;
background-position: 4px 4px;
background-repeat: no-repeat;
box-shadow: 0 1px 2px 0 #E4E7EF;
background-color: #ffffff;
border: 1px solid #ffffff;
}
.toolItem:hover {
border-color: #789CFF;
}
.active {
border-color: #D5DFF2;
background-color: #D5DFF2;
}
#delete {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/delete.png');
}
#split {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/split.png');
}
#union {
background-image: url('https://mapapi.qq.com/web/lbs/javascriptGL/demo/img/union.png');
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div id="container"></div>
<div class="btnContainer">
<button class="btn1" id="editButton" onclick="onOffEditor()">开启编辑</button>
<button class="btn2" id="modeButton" onclick="changeMode()">切换绘制模式</button>
</div>
<div id="toolControl">
<div class="toolItem" id="delete" onclick="editor.delete();" title="删除"></div>
<div class="toolItem" id="split" onclick="editor.split();" title="拆分"></div>
<div class="toolItem" id="union" onclick="editor.union();" title="合并"></div>
</div>
<div>
点击左上角按钮开启编辑<br />
单选:鼠标左键点击图形<br />
多选:按下ctrl键后点击多个图形<br />
删除:选中图形后按下delete键或点击删除按钮可删除图形<br />
编辑:选中图形后出现编辑点,拖动编辑点可移动顶点位置,双击实心编辑点可删除顶点<br />
拆分:选中单个多边形后可绘制拆分线,拆分线绘制完成后自动进行拆分<br />
合并:选中多个相邻多边形后可进行合并,飞地形式的多边形不支持合并<br />
中断:按下esc键可中断当前操作,点选的图形将取消选中,编辑过程将中断
</div>
</div>
<script>
// 制作可用于编辑的Area图层
class MultiPolygonWithArea extends TMap.MultiPolygon {
constructor(options) {
super(options);
this.createdGeometries = []; // 已经由area转换创建的geometries
const { areaLayer } = options;
this.initAreaLayer(areaLayer);
}
// 创建可编辑的Area图层
initAreaLayer(areaLayer) {
this.preHandleAreaData(areaLayer.getData());
this._areaLayer = areaLayer;
// _areaLayer绑定click监听,点击area时,对应的area转变为polygon
this.bindAreaEditorClickEvent = this.areaEditorClickEvent.bind(this)
this._areaLayer.on('click', this.bindAreaEditorClickEvent);
}
areaEditorClickEvent(e) {
if (e.detail.area) {
const area = e.detail.area;
// 如果polygon已经存在该id的geometry,则不添加
if (this.createdGeometries.some(obj => obj.id === area.properties._aid)) return;
const path = area.path;
const polygonPath = convertNumberToLatLng(path);
// 创建用于editor的polygon
const createdGeometry = {
id: area.properties._aid,
paths: polygonPath,
styleId: area.styleId,
properties: area.properties
}
this.add([createdGeometry]);
this.createdGeometries.push(createdGeometry);
// 触发click事件,让editor选中
this.emit('click', { geometry: createdGeometry });
}
}
// 获取编辑后的AreaData
finishEdit() {
// 被编辑过的area的数据索引
const editedIds = this.createdGeometries.map(geo => geo.id);
this.setAreaData(editedIds);
}
// 删除区域
deleteArea(geometries) {
// 要被删除的area的id
const removedIds = geometries.map(geo => geo.id).concat(this.createdGeometries.map(geo => geo.id));
this.setAreaData(removedIds, true);
this.createdGeometries = [];
this.setGeometries([]);
}
// 设置区域数据
setAreaData(editedIds, needPreHandle = false) {
// 数据过滤editedIds的area
const areaData = this._areaLayer.getData().filter((_, index) => !editedIds.includes(index));
this.geometries.forEach(geometry => {
const { paths } = geometry;
const polygonPath = paths;
const areaPath = convertLatLngToNumber(polygonPath)
areaData.push({
path: areaPath,
properties: geometry.properties,
styleId: geometry.styleId,
});
});
// 删除的情况需要重新赋值_aid;(处理拆分后删除其中一部分,另一部分Area无aid的情况)
if (needPreHandle) {
this.preHandleAreaData(areaData);
}
this._areaLayer.setData(areaData);
}
/**
* 给每条data数据添加_aid,即其在data数组中的索引
* @param {*} data
*/
preHandleAreaData(data) {
// 每个data数据添加_aid属性
data.forEach((area, index) => {
area.properties = {
...area.properties,
_aid: index
};
});
}
clear() {
this._areaLayer.off('click', this.bindAreaEditorClickEvent);
this.createdGeometries = [];
this.setGeometries([]);
}
}
/**
* 数组中的数字转换为LatLng,area path 转 polygon paths
*/
function convertNumberToLatLng(paths) {
const polygonPath = [];
if (Array.isArray(paths[0])) { // 如果数组中包含多条数组,则递归处理
paths.forEach(path => {
polygonPath.push(convertNumberToLatLng(path));
});
} else {
for (let i = 0; i < paths.length; i += 2) {
polygonPath.push(new TMap.LatLng(paths[i], paths[i + 1]));
}
}
return polygonPath;
}
/**
* 数组中的LatLng转换为数字,polygon paths 转 area path
*/
function convertLatLngToNumber(paths) {
const areaPath = [];
if (Array.isArray(paths[0])) { // 如果数组中包含多条数组,则递归处理
paths.forEach(path => {
areaPath.push(convertLatLngToNumber(path));
});
} else {
for (let i = 0; i < paths.length; i++) {
areaPath.push(paths[i].lat, paths[i].lng);
}
}
return areaPath;
}
/**
* 从paths数组中解析出线段数据
* @param {LatLng[] | LatLng[][] | LatLng[][][]} path
* @returns {Segment[]}
*/
function getSegmentsFromPath(path) {
if (isSimple(path)) {
// LatLng[]
const segments = [];
const { length } = path;
for (let index = 0; index < length; index++) {
let segment;
if (index < length - 1) {
segment = [path[index], path[index + 1]];
} else {
if (path[index].equals(path[0])) {
continue;
}
segment = [path[index], path[0]];
}
segments.push(segment);
}
return segments;
} else {
// LatLng[][] | LatLng[][][]
return path.map(p => {
return getSegmentsFromPath(p);
}).flat();
}
}
/**
* 检查是否是简单多边形,没有飞地且不带洞
* @param {LatLng[]} simplePolygon
*/
function isSimple(simplePolygon) {
return simplePolygon.length >= 3
&& Number.isFinite(simplePolygon[0].lat) && Number.isFinite(simplePolygon[0].lng);
}
</script>
<script>
var center = new TMap.LatLng(40.046014541872594, 116.28684997558594);
var map = new TMap.Map("container", {
zoom: 15, //设置地图缩放级别
center: center, //设置地图中心点坐标
});
var paths = [];
areaDatas.forEach((item, index) => {
if (index % 3 === 1) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style1' //设置区域样式id
})
} else if (index % 3 === 2) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style2' //设置区域样式id
})
} else {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style3' //设置区域样式id
})
}
})
// 创建Area图层
var areaLayer = new TMap.visualization.Area({
styles: { //设置区域图样式
style1: setStyles(0.8),
style2: setStyles(0.5),
style3: setStyles(0.2),
},
selectOptions: { //设置拾取配置
action: 'hover',
style: {
fillColor: 'rgba(28,213,255,0.6)', //设置区域填充颜色
strokeColor: '#fff', //设置区域边线颜色
strokeWidth: 1, //区域边线宽度
strokeDashArray: [0, 0] //边线虚线展示方式
},
enableHighlight: false,
}
}).setData(paths).addTo(map);
var isEditorOn = false;
var polygonWithArea;
var editor;
// button
const editButton = document.getElementById("editButton");
const toolControl = document.getElementById("toolControl");
const modeButton = document.getElementById("modeButton");
var isDrawMode = false; // 是否为绘制模式
// 初始状态编辑相关按钮隐藏
modeButton.style.display = "none";
toolControl.style.display = "none";
// 开关编辑器
function onOffEditor() {
if (!isEditorOn) {
isEditorOn = true;
createAreaEditor();
editButton.textContent = "关闭编辑";
modeButton.style.display = "";
toolControl.style.display = "";
} else {
isEditorOn = false;
finishAreaEditor();
// 编辑器恢复编辑状态
editor && editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT);
isDrawMode = false;
// UI调整
editButton.textContent = "开启编辑";
modeButton.textContent = "切换绘制模式";
modeButton.style.display = "none"; // 模式按钮隐藏
toolControl.style.display = "none"; // 工具按钮隐藏
}
}
// 创建Area编辑器
function createAreaEditor() {
// area接口改用MultiPolygonWithArea以兼容于编辑器
if (!polygonWithArea) {
polygonWithArea = new MultiPolygonWithArea({
map,
areaLayer: areaLayer, // 传入创建的area图层
styles: { // 被编辑器选中时高亮显示的样式
highlight: new TMap.PolygonStyle({
color: 'rgba(255, 255, 0, 0.6)'
})
}
})
} else {
polygonWithArea.initAreaLayer(areaLayer);
}
if (!editor) {
// 初始化几何图形及编辑器
editor = new TMap.tools.GeometryEditor({
map, // 编辑器绑定的地图对象
overlayList: [ // 可编辑图层
{
overlay: polygonWithArea,
id: 'polygon',
selectedStyleId: 'highlight'
}
],
actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式
activeOverlayId: 'polygon', // 激活图层
selectable: true, // 开启点选功能
selectedStyleId: 'highlight', // 选中时高亮显示
});
// 添加area的删除事件
editor.on('delete_complete', (geometries) => {
polygonWithArea.deleteArea(geometries);
})
} else {
// 编辑器已存在,则启用
editor.enable();
}
}
// 完成Area的编辑
function finishAreaEditor() {
// 禁用编辑器
editor && editor.disable();
// 获取编辑结果
polygonWithArea && polygonWithArea.finishEdit();
// 清除polygonWithArea图层数据
polygonWithArea && polygonWithArea.clear();
}
// 切换编辑模式
function changeMode() {
if (!isDrawMode) {
editor && editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.DRAW);
isDrawMode = true;
toolControl.style.display = 'none';
modeButton.textContent = "切换编辑模式";
} else {
editor && editor.setActionMode(TMap.tools.constants.EDITOR_ACTION.INTERACT);
isDrawMode = false;
toolControl.style.display = '';
modeButton.textContent = "切换绘制模式";
}
}
//设置style样式工厂函数
function setStyles(opacity) {
return {
fillColor: `rgba(56,124,234,opacity)`, //设置区域填充颜色
strokeColor: '#6799EA', //设置区域边线颜色
}
}
</script>
</body>
</html>
FILE:references/visualization/demos/网格热力图点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网格热力图点击事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
var infoDom = document.getElementById('info');
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom: 10,//设置地图缩放级别
pitch: 45,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化网格热力图图并添加至map图层
var grid =new TMap.visualization.Grid({
sideLength: 1000, // 设置网格边长
heightRange: [1, 30000], // 高度变化区间
showRange:[1, 100], // 聚合数据显示区间
selectOptions: { //拾取配置
action: 'click', //拾取动作
style: '#E9AB1D',
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
grid.setShowRange(grid.getValueRange());
// 绑定事件
grid.on('click', evt => {
var aggregator = evt && evt.detail && evt.detail.aggregator;
if (aggregator) {
infoDom.style.display = 'block';
infoDom.innerHTML = '原始数据索引:' + aggregator.indexes.join() + '; 聚合热力值:' + aggregator.count;
} else {
infoDom.style.display = '';
}
});
}
</script>
</body>
</html>
FILE:references/visualization/demos/网格热力图鼠标事件选定样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>网格热力图鼠标事件选定样式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom: 10,//设置地图缩放级别
pitch: 45,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化网格热力图图并添加至map图层
var grid =new TMap.visualization.Grid({
sideLength: 1000, // 设置网格边长
heightRange: [1, 30000], // 高度变化区间
showRange:[1, 100], // 聚合数据显示区间
selectOptions: { //拾取配置
action: 'hover', //拾取动作
style: '#E9AB1D', //选中样式
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
grid.setShowRange(grid.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/散点图鼠标事件选定样式设置.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>散点图鼠标事件选定样式设置</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/Dot.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//初始化地图
var map = new TMap.Map("container", {
zoom: 8,//设置地图缩放级别
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化散点图并添加至map图层
new TMap.visualization.Dot({
faceTo: "screen",//散点固定的朝向
selectOptions: { //拾取配置
action: 'hover', //拾取动作
style: {
type: "circle", //圆形样式
fillColor: "#1CD5FF", //填充颜色
radius: 5 //原型半径
},
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(pointData);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/蜂窝热力动画.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>蜂窝热力动画</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color:#ffffff;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="name">北京市路口车辆轨迹状态</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/beijing_intersection.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 10.2, //设置地图缩放级别
pitch: 49.5, // 设置地图俯仰角
center: new TMap.LatLng(40.040144510481355, 116.48808453415052), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化蜂窝热力图图并添加至map图层
var hexagon = new TMap.visualization.Hexagon({
radius: 500, //六边形中心点到端点的距离(半径)
heightRange: [1, 30000], // 高度变化区间
showRange: [1, 1000], //蜂窝聚合数据显示区间
toggleAnimation: {
animationType: "grow", //动画类型
duration: 3000, //动画时长
},
colorList: ["#ea9c21", "#ea8e21", "#e6721c", "#e55f1d", "#cc2a18"],
})
.addTo(map)
hexagon.setData(hexagonData); //设置数据
// 数据聚合之后才能够真正获取值域范围
hexagon.setShowRange(hexagon.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/热力图自动切换散点.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>3D热力切换散点</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization">
</script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div id="container"></div>
<div class="btnContainer">
<button class="btn1" onclick="update()">更改散点样式配置</button>
<button class="btn2" onclick="onOff()">开关散点图切换</button>
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/heat.js"></script>
<script>
var center = new TMap.LatLng(39.909897147274364, 116.39756310116866);
var map = new TMap.Map("container", {
zoom: 12,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: center,//设置地图中心点坐标
mapStyleId: "style4",//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
// HeatDotOptions文档地址 https://lbs.qq.com/webApi/visualizationApi/visualizationDoc/visualizationDocHeat#4
var heatLayer = new TMap.visualization.Heat({
gradientColor: new TMap.GradientColor({
stops: {
0.1: 'rgba(50,48,118,1)',
0.2: 'rgba(127,60,255,1)',
0.4: 'rgba(166,53,219,1)',
0.6: 'rgba(254,64,95,1)',
0.8: 'rgba(255,98,4,1)',
1: 'rgba(236,220,79,1)',
}
}),
// max: 10, // 热力最强阈值
min: 0, // 热力最弱阈值
max: 110, // 热力最强阈值
height: 90, // 峰值高度
radius: 30, // 最大辐射半径
// 自动切换散点图设置
enableHeatDot: true,
enableAggregation: false,
heatDotOptions: {
radius: 15, // 散点半径
minZoom: 9, // 散点最小缩放级别
maxZoom: 20, // 散点最大缩放基本
opacity: 1.0, // 散点透明度
strokeColor: 'rgba(255,255,255, 0.6)', // 散点描边颜色
strokeWidth: 3, // 散点描边宽度
fadeArray: [12, 18], // 展现切换动画的层级,默认为[14,16], 表示在zoom 12级时,热力图开始淡出(透明度减少),散点图开始淡入,至18级时淡入淡出结束;
}
}).addTo(map)
.setData(heatData);//设置数据
// 更改散点配置
function update() {
heatLayer.setHeatDotOptions({
radius: 15,
minZoom: 11,
maxZoom: 20,
opacity: 1.0,
fillColors: ['#F4A49E', '#EE7B91', '#E85285', '#BE408C', '#942D93', '#6A1B9A', '#56167D', '#42105F'].reverse(), // 散点颜色
strokeColor: '#FFF',
strokeWidth: 2,
fadeArray: [14, 16], // 在zoom 14级时,热力图开始淡出(透明度减少),散点图开始淡入,至16级时淡入淡出结束;
});
}
// 开关散点图切换
function onOff() {
heatLayer.setHeatDotEnable(! heatLayer.getHeatDotEnable());
}
</script>
</body>
</html>
FILE:references/visualization/demos/热力时序动画.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>3D经典热力</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color: #ffffff;
}
</style>
<body>
<div id="container"></div>
<div id="name">成都市24小时人口时序图</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/heatAnimation.js"></script>
<script>
//初始化地图
map = new TMap.Map("container", {
zoom: 11.5, //设置地图缩放级别
pitch: 41.5, // 设置地图俯仰角
center: new TMap.LatLng(30.61235023802061, 104.06067725164951), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化热力图并添加至map图层
heat = new TMap.visualization.Heat({
max: 180, // 热力最强阈值
min: 0, // 热力最弱阈值
height: 40, // 峰值高度
radius: 30, // 最大辐射半径
transitAnimation: {
duration: 3000, //动画时长
},
enableLighting: true, //热力图是否呈现光照效果
}).addTo(map);
heat.setData(data[0]);
//循环24小时数据
var index = 0;
setInterval(function () {
index++;
heat.setData(data[index]);
if (index == data.length - 1) {
index = 0;
} //如果大于数据长度-1 从0开始
}, 3500);
</script>
</body>
</html>
FILE:references/visualization/demos/蜂窝热力图鼠标事件选定样式.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>蜂窝热力图鼠标事件选定样式</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//处理hexagon数据
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom:10,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化蜂窝热力图图并添加至map图层
var hexagon =new TMap.visualization.Hexagon({
radius: 1000, //六边形中心点到端点的距离(半径)
heightRange: [1, 30000], // 高度变化区间
showRange:[1, 100], //蜂窝聚合数据显示区间
selectOptions: { //拾取配置
action: 'hover', //拾取动作
style: '#E9AB1D', //选中样式
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
hexagon.setShowRange(hexagon.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/蜂窝热力图点击事件.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>蜂窝热力图点击事件</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#info{
display: none;
position: absolute;
left: 30px;
top: 30px;
background: #fff;
border-radius: 5px;
padding: 10px;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="info"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
var infoDom = document.getElementById('info');
//处理hexagon数据
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom:10,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化蜂窝热力图图并添加至map图层
var hexagon =new TMap.visualization.Hexagon({
radius: 1000, //六边形中心点到端点的距离(半径)
heightRange: [1, 30000], // 高度变化区间
showRange:[1, 100], //蜂窝聚合数据显示区间
selectOptions: { //拾取配置
action: 'click', //拾取动作
style: '#E9AB1D',
enableHighlight: false //是否使用高亮效果
}
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
hexagon.setShowRange(hexagon.getValueRange());
// 绑定事件
hexagon.on('click', evt => {
var aggregator = evt && evt.detail && evt.detail.aggregator;
if (aggregator) {
infoDom.style.display = 'block';
infoDom.innerHTML = '原始数据索引:' + aggregator.indexes.join() + '; 聚合热力值:' + aggregator.count;
} else {
infoDom.style.display = '';
}
});
}
</script>
</body>
</html>
FILE:references/visualization/demos/2D蜂窝热力.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>2D蜂窝热力</title>
</head>
<script charset="utf-8" src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/hexagon.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(39.984104, 116.307503);
//处理hexagon数据
var data = citydata.bj;
//初始化地图
var map = new TMap.Map("container", {
zoom:10,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始蜂窝热力图并添加至map图层
var hexagon =new TMap.visualization.Hexagon({
radius:1000, //六边形中心点到端点的距离(半径)
extrudable: false,//六边形是否可拔起
heightRange:[1, 5000], // 高度变化区间
showRange:[1, 100] //蜂窝聚合数据显示区间
})
.addTo(map)
.setData(data);//设置数据
// 数据聚合之后才能够真正获取值域范围
hexagon.setShowRange(hexagon.getValueRange());
}
</script>
</body>
</html>
FILE:references/visualization/demos/动态轨迹图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>动态轨迹图</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/trail.js"></script>
<script>
function initMap() {
//初始化地图
map = new TMap.Map("container", {
zoom: 11.5, //设置地图缩放级别
center: new TMap.LatLng(39.906236512551466, 116.44224037153026,), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化轨迹图并添加至map图层
var trail = new TMap.visualization.Trail({
pickStyle: function (item) {
//轨迹图样式映射函数
return {
width: 2,
color: "rgba(29,250,242,0.3)",
};
},
showDuration: 120, //动画中轨迹点高亮的持续时间
playRate: 70, // 动画播放倍速
enableHighlightPoint: true, //是否显示头部高亮点
}).addTo(map);
trail.setData(trailData); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/全国行政区划.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>全国行政区划图</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#container {
width: 100%;
height: 100%;
}
#panel {
position: absolute;
background: #FFF;
width:350px;
padding: 20px;
z-index: 9999;
top: 30px;
left: 30px;
}
</style>
<body>
<div id="panel">
<p>请在右侧示例代码第59行填入您的key,并执行查看运行结果(key可在控制台应用管理中自行创建)</p>
</div>
<div id="container"></div>
<script src="https://lbs.gtimg.com/visual/lbs_component/v/jquery-1.11.0.min.js"></script>
<script src="https://mapapi.qq.com/web/visualization/demo-asset/styleMap.js"></script>
<script type="text/javascript">
//初始化地图
var map = new TMap.Map("container", {
center: new TMap.LatLng(34.22379225102061, 108.39352326742096), //设置地图中心坐标
zoom: 4, //地图进行缩放
mapStyleId: "style7", //个性化样式
baseMap: {
//设置底图样式
type: "vector", //设置底图为矢量底图
features: [
//设置矢量底图要素类型
"base",
"point",
],
},
});
$.ajax({
url:
"https://apis.map.qq.com/ws/district/v1/getchildren?key=您的key&output=jsonp&get_polygon=2&max_offset=3000", // 请求地址+参数
type: "GET", //get请求
dataType: "JSONP", //指定服务器返回的数据类型
success: function (res) {
var list = [];
list = res.result[0]; //获取数据
var paths = [];
list.forEach((item, index) => {
const styleId = styleMap[item.id]; //数据所有的id值
const path = []; // TODO
item.polygon.forEach((p) => {
path.push([p.reverse()]);
}); //进行纬经度进行调换位置
paths.push({
path, //经纬度点串
styleId, //id=styleId
});
});
var area = new TMap.visualization.Area({
styles: {
//设置区域图样式
styel1: {
fillColor: "#7DF4FF", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel2: {
fillColor: "#17D5DC", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel3: {
fillColor: "#0EB2E7", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel4: {
fillColor: "#0896EF", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
styel5: {
fillColor: "#017CF7", //设置区域颜色
strokeColor: "#014080", //设置区域边线颜色
},
},
})
.setData(paths)
.addTo(map);
},
});
</script>
</body>
</html>
FILE:references/visualization/demos/散点图朝向设置.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>散点图朝向设置</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
input[type="button"] {
border-style: none;
width: 120px;
padding: 10px;
border-radius: 10px;
box-shadow: 0px 0px #333;
outline-style: none;
background: #000;
color: #fff;
cursor: pointer;
margin-bottom: 10px;
margin-right: 10px;
}
#container {
width: 100%;
height: 100%;
}
#btn_container {
position: absolute;
left: 30px;
top: 30px;
z-index: 9999;
width: 300px;
display: flex;
flex-wrap: wrap;
}
</style>
<body>
<div id="container"></div>
<div id="btn_container">
<input type="button" id="set-face-map" onclick="setFaceTo('map')" value="贴合地图" />
<input type="button" id="set-face-screen" onclick="setFaceTo('screen')" value="朝向屏幕" />
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/Dot.js"></script>
<script>
var center = new TMap.LatLng(30.637912028341123,104.0185546875);
//初始化地图
var map = new TMap.Map("container", {
zoom: 7,//设置地图缩放级别
pitch: 65,//设置俯仰角
center: center,//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot().addTo(map).setData(pointData);//设置数据
function setFaceTo(type) {
if (type == 'map') {
dot.setFaceTo("map");//设置散点朝向(贴合地图)
}
if (type == "screen") {
dot.setFaceTo("screen"); //设置散点朝向(朝向屏幕)
}
}
</script>
</body>
</html>
FILE:references/visualization/demos/动态设置热力参数.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>动态设置热力参数</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#heatAddBtn {
position: absolute;
left: 50px;
top: 50px;
z-index: 1001;
}
#heatDecBtn {
position: absolute;
left: 150px;
top: 50px;
z-index: 1001;
}
#setStyleBtn {
position: absolute;
left: 250px;
top: 50px;
z-index: 1001;
}
</style>
<body onload="initMap()">
<div id="container">
<button id="heatAddBtn">增加半径</button>
<button id="heatDecBtn">减小半径</button>
<button id="setStyleBtn">修改渐变样式</button>
</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/heat.js"></script>
<script>
function initMap() {
var heatAddBtn = document.getElementById('heatAddBtn');
var heatDecBtn = document.getElementById('heatDecBtn');
var setStyleBtn = document.getElementById('setStyleBtn');
//初始化地图
var map = new TMap.Map("container", {
zoom: 12,//设置地图缩放级别
pitch: 45, // 设置地图俯仰角
center: new TMap.LatLng(39.909897147274364, 116.39756310116866),//设置地图中心点坐标
mapStyleId: "style4" ,//个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化热力图并添加至map图层
var heat = new TMap.visualization.Heat({
max: 180, // 热力最强阈值
min: 0, // 热力最弱阈值
height: 40, // 峰值高度
radius: 30 // 最大辐射半径
})
.addTo(map)
.setData(heatData);//设置数据
heatAddBtn.addEventListener('click', () => {
var radius = heat.getRadius() + 5;
heat.setRadius(radius);
}, false);
heatDecBtn.addEventListener('click', () => {
var radius = heat.getRadius() - 5;
if(radius > 0) {
heat.setRadius(radius);
}
}, false);
setStyleBtn.addEventListener('click', () => {
heat.setGradientColor({
0.6: "#673198",
0.8: "#e53390",
0.9: "#ffc95a",
});
})
}
</script>
</body>
</html>
FILE:references/visualization/demos/分类散点图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>分类散点图</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color:#ffffff;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<div id="name">全国A级旅游景点分布</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/classifiedScatter.js"></script>
<script>
function initMap() {
//将数据分类并添加styleId(数据为js文件引入)
dotSortData.forEach(function (item, index) {
if (index <= 358) {
item.styleId = "greenStyleColor";
item.hierarchy = 4;
} else if (index > 358 && index <= 2023) {
item.styleId = "yellowStyleColor";
item.hierarchy = 3;
} else if (index > 2023 && index <= 2686) {
item.styleId = "blueStyleColor";
item.hierarchy = 2;
} else {
item.styleId = "redStyleColor";
item.hierarchy = 1;
}
});
//初始化地图
var map = new TMap.Map("container", {
zoom: 4.4, //设置地图缩放级别
center: new TMap.LatLng(36.32434341048188, 106.26656561894674), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot({
styles: {
redStyleColor: {
type: "circle", //圆形样式
fillColor: "rgba(148,28,230,0.3)", //填充颜色
radius: 1, //原型半径
},
blueStyleColor: {
type: "circle", //圆形样式
fillColor: "rgba(204,24,93,0.9)", //填充颜色
radius: 1, //原型半径
},
yellowStyleColor: {
type: "circle", //圆形样式
fillColor: "rgba(204,114,24,0.9)", //填充颜色
radius: 1, //原型半径
},
greenStyleColor: {
type: "circle", //圆形样式
fillColor: "#FFD343", //填充颜色
strokeWidth: 0, //边线宽度
radius: 2.5, //原型半径
},
},
})
.addTo(map)
dot.setData(
dotSortData.sort((a, b) => a.hierarchy - b.hierarchy) //显示层级排序
); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/贴地弧线流向图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>贴地弧线流向图</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arc.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(37.80787, 112.269029);
var data = arcData;
//初始化地图
var map = new TMap.Map("container", {
zoom: 5,//设置地图缩放级别
pitch: 30,
center: center,//设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
var arc = new TMap.visualization.Arc({
animatable: true,
opacity: 0.5, // 设置弧线透明度
width: 4, // 设置弧线宽度
mode: 'horizontal' // 弧线平面与地平面平行
})
.addTo(map)
.setData(data);//设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/区域图使用geojson数据.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>区域图使用geojson数据</title>
</head>
<script charset="utf-8" src="https://mapapi.qq.com/web/jsapi/jsapi-gl/assets/geojson-demo.js"></script>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
</style>
<body>
<div id="container"></div>
<!-- geojso转换为区域图数据格式函数 -->
<script>
function parseGeojsonData(geoJSONObject) {
let areaData = [];
let geoJSONFeature = null;
if ('FeatureCollection' === geoJSONObject['type']) {
const geoJSONFeatureCollection = geoJSONObject;
const geoJSONFeatures = geoJSONFeatureCollection['features'];
for (let i = 0, ii = geoJSONFeatures.length; i < ii; ++i) {
geoJSONFeature = geoJSONFeatures[i];
const result = readGeometryFromFeature(geoJSONFeature);
if (!result) {
continue;
}
areaData = areaData.concat(result);
}
} else {
geoJSONFeature = geoJSONObject;
const result = readGeometryFromFeature(geoJSONFeature);
if (!result) {
return;
}
areaData = areaData.concat(result);
}
return areaData;
}
function readGeometryFromFeature(geoJSONFeature) {
const result = readGeometry(geoJSONFeature['geometry']);
if (!result) {
return null;
}
result.map(geom => {
geom['properties'] = geoJSONFeature['properties'];
geom['styleId'] = 'style1'; //此处可配置区域样式id
});
return result;
}
function readGeometry(geoJSONGeometry) {
if (!geoJSONGeometry) {
return null;
}
const geometry = createGeometry(geoJSONGeometry);
return geometry;
}
function createGeometry(geometry) {
const geoms = [];
geometry['coordinates'].forEach(coord => {
const position = convertCoordsToLatLngs(coord);
let geom = { path: position };
geoms.push(geom);
});
return geoms;
}
function convertCoordsToLatLngs(coords) {
let latLngs = null;
if (coords.length > 0 && Array.isArray(coords[0]) && typeof coords[0][0] === 'number') {
latLngs = [].concat(...coords.map(coord => [coord[1],coord[0]]));
} else {
latLngs = [];
coords.forEach(item => {
latLngs.push(convertCoordsToLatLngs(item));
});
}
return latLngs;
}
</script>
<script>
var center = new TMap.LatLng(40.13659649977259, 116.51136018387479);
//初始化地图
var map = new TMap.Map("container", {
zoom: 9, // 设置地图缩放级别
center, // 设置地图中心点坐标
});
// geojson数据转换为区域图Area可用格式
const result = parseGeojsonData(geojsonData);
var area = new TMap.visualization.Area({
styles: { //设置区域图样式
style1: {
strokeWidth: 2,
fillColor: `rgba(56,124,234,0.4)`, //设置区域填充颜色
strokeColor: '#6799EA',
}
},
selectOptions: { //设置拾取配置
action: 'hover',
style: {
fillColor: 'rgba(28,213,255,0.6)', //设置区域填充颜色
strokeColor: '#fff', //设置区域边线颜色
strokeWidth: 1, //区域边线宽度
strokeDashArray: [0, 0] //边线虚线展示方式
},
enableHighlight: false,
}
}).setData(result).addTo(map);
</script>
</body>
</html>
FILE:references/visualization/demos/区域图数据转换为geojson.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>区域图数据转geojson数据</title>
</head>
<script charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization,vector"></script>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/areaClick.js"></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
position: relative;
}
.btnContainer {
position: absolute;
left: 20px;
top: 20px;
z-index: 10000;
}
.btnContainer button {
padding: 10px 14px;
box-sizing: border-box;
border: none;
background-color: #3876ff;
border-radius: 2px;
color: #fff;
font-size: 14px;
line-height: 14px;
margin-bottom: 10px;
}
</style>
<body>
<div id="container"></div>
<div class="btnContainer">
<button class="btn1" id="toGeojson" onclick="areaToGeojsonLayer()">转为geojson数据渲染</button>
</div>
<script>
var center = new TMap.LatLng(40.046014541872594, 116.28684997558594);
//初始化地图
var map = new TMap.Map("container", {
zoom: 15, //设置地图缩放级别
center, // 设置地图中心点坐标
});
var paths = [];
areaDatas.forEach((item, index) => {
if (index % 3 === 1) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style1' //设置区域样式id
})
} else if (index % 3 === 2) {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style2' //设置区域样式id
})
} else {
paths.push({
path: item, //设置区域边界线经纬度点串
styleId: 'style3' //设置区域样式id
})
}
})
var area = new TMap.visualization.Area({
styles: { //设置区域图样式
style1: {
strokeWidth: 2,
fillColor: `rgba(56,124,234,0.4)`, //设置区域填充颜色
strokeColor: '#6799EA',
}
},
selectOptions: { //设置拾取配置
action: 'hover',
style: {
fillColor: 'rgba(28,213,255,0.6)', //设置区域填充颜色
strokeColor: '#fff', //设置区域边线颜色
strokeWidth: 1, //区域边线宽度
strokeDashArray: [0, 0] //边线虚线展示方式
},
enableHighlight: false,
}
}).setData(paths).addTo(map);
// AreaData转换为Geojson数据格式
function areaDataToGeojson(areaData) {
// 区域图坐标转geojson坐标
function areaCoord2JsonCoord(path) {
let coords = [];
if (Array.isArray(path[0])) {
path.forEach(p => {
coords.push(areaCoord2JsonCoord(p));
});
} else {
for (let i = 0; i < path.length; i += 2) {
coords.push([path[i + 1], path[i]]);
}
}
return coords;
}
// 各area转回feature
const features = [];
areaData.forEach(area => {
const paths = Array.isArray(area.path[0]) ? area.path : [area.path];
const coordinates = paths.map(path => areaCoord2JsonCoord(path));
features.push({
type: 'Feature',
properties: area.properties,
geometry: {
coordinates,
type: "MultiPolygon",
}
});
});
return {
type: "FeatureCollection",
features
}
}
function areaToGeojsonLayer() {
const geojsonData = areaDataToGeojson(area.getData());
// 清空area图层数据
area.setData([]);
// 使用GeoJSONLayer渲染
// 文档:https://lbs.qq.com/webApi/javascriptGL/glDoc/glVectorDataLayer
var geoLayer = new TMap.vector.GeoJSONLayer({
map: map,
data: geojsonData,
polygonStyle: new TMap.PolygonStyle({
'showBorder': true, //是否显示边线
'borderColor': '#fff', //边线颜色
'borderWidth': 1 //边线宽度
})
});
}
</script>
</body>
</html>
FILE:references/visualization/demos/航线图.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>航线图</title>
</head>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
</style>
<body onload="initMap()">
<div id="container"></div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/arcData.js"></script>
<script>
function initMap() {
var center = new TMap.LatLng(32.6510605303509, 110.34521528926257);
var data = arcData;
//初始化地图
map = new TMap.Map("container", {
zoom: 5.5, //设置地图缩放级别
pitch: 30,
center: center, //设置地图中心点坐标
mapStyleId: "style4", //个性化样式
baseMap: {
type: "vector",
features: ["base", "building3d"], // 隐藏矢量文字
},
});
//初始化弧线图并添加至map图层
new TMap.visualization.Arc({
width: 2, // 宽度
mode: "vertical", // 弧线平面与地平面垂直
curvature: 0.1, // 弧线曲度
})
.addTo(map)
.setData(data); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/demos/动态辐射圈.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>动态辐射圈</title>
<script
charset="utf-8"
src="https://map.qq.com/api/gljs?v=1.exp&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&libraries=visualization"
></script>
<style type="text/css">
html,
body {
height: 100%;
margin: 0px;
padding: 0px;
}
#container {
width: 100%;
height: 100%;
}
#name {
position: absolute;
bottom: 10px;
right: 20px;
font-size: 12px;
color:#ffffff;
}
</style>
</head>
<body onload="initMap()">
<div id="container"></div>
<div id="name">北京市道路积水预警</div>
<script src="https://mapapi.qq.com/web/lbs/visualizationApi/demo/data/beijing_pondingWarning.js"></script>
<script>
function initMap() {
//初始化地图
var map = new TMap.Map("container", {
zoom: 11.5, //设置地图缩放级别
center: new TMap.LatLng(39.914961477236766, 116.3898104098216), //设置地图中心点坐标
mapStyleId: "style4", //个性化样式2
renderOptions: {
enableBloom: true, //泛光
},
});
//初始化辐射圈
var radiationCircle = new TMap.visualization.Radiation({
styles: {
style2: {
fillColor: "rgba(29,250,242,0.9)", //辐射圈填充颜色
},
},
processAnimation: {
duration: 3000, //辐射圈半径从0到最大半径的时长,单位为ms
},
})
.addTo(map)
radiationCircle.setData(radiationData);
//初始化散点图并添加至map图层
var dot = new TMap.visualization.Dot({
styles: {
style1: {
radius: 2, //圆形半径
},
style2: {
fillColor: "rgba(29,250,242,1)", //圆形填充颜色
radius: 3, //圆形半径
},
},
enableBloom: true, // 泛光
})
.addTo(map)
dot.setData(dotData); //设置数据
}
</script>
</body>
</html>
FILE:references/visualization/docs/基础类.md
## AnimationOptions {#1}
----
动画配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|enable |Boolean | 是否启用,默认为true|
|animationType |String |动画类型,各图层类型可支持动画类型不同,详见各图层类型动画说明|
|duration |Number | 动画时长,单位毫秒 默认2000|
|repeat |Number |动画执行次数,正整数,默认1。|
|yoyo |Boolean |是否回弹,若开启动画会从0过渡到1后再从1过渡回0,整个过程算两次执行,当repeat大于1时生效,默认false。|
</br></br>
FILE:references/visualization/docs/辐射圈.md
## Radiation {#1}
----
是用于创建辐射图的类,用以展示圆形辐射区域。
|构造函数|
| :- |
|TMap.visualization.Radiation(options:RadiationOptions)|
|方法名|返回值|说明|
|--|--|--|
| setStyles(styles:Object) | this | 设置样式集合。 |
| setData(dataList:RadiationPlane[]) | this | 设置数据。 |
| getData() | RadiationPlane[] | 获取数据。 |
| setZIndex(zIndex: Number) | this | 设置图层绘制顺序。 |
| getZIndex() | Number| 获取图层绘制顺序。 |
| setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
| getMinZoom() | Number| 获取图层最小缩放层级。 |
| setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
| getMaxZoom() | Number| 获取图层最大缩放层级。 |
| addTo(map:Map) | this | 添加至指定地图实例。 |
| updateAnimation(type:String, animationOptions:AnimationOptions) | this | 更新指定类别动画参数,type支持'toggle','process'。 |
| getAnimation(type:String) | AnimationOptions| 获取指定类型的动画参数,type支持'toggle','process'。 |
| getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
| setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
| show() | this | 显示图层。 |
| hide() | this | 隐藏图层。 |
| remove() | this | 从地图中删除图层。 |
| destroy() | this | 销毁图层对象。 |
| on(eventName:String, listener:Function) | this | 添加listener到eventName事件的监听器数组中。 |
| off(eventName:String, listener:Function) | this | 从eventName事件的监听器数组中移除指定的listener。 |
| 事件名 | 回调参数 | 说明 |
|--|--|--|
| click | evt:VisualEvent | 点击辐射圈时触发 |
| hover | evt:VisualEvent | 鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null |
</br></br>
## RadiationOptions 对象规范 {#2}
辐射图配置参数。
**属性**
|名称|类型|说明|
|--|--|--|
| styles | Object | 辐射图样式集合,key-value形式。key对应数据中的styleId,value为样式对象,需符合RadiationStyle对象规范。包含default属性,其值作为默认样式,可被覆盖 |
| selectOptions | VisualSelectOptions | 拾取配置,可设置拾取动作、选中样式,其中选中样式需符合RadiationStyle对象规范 |
| number | Number | 每一时刻,辐射圈的同心圆个数,默认为1 |
| enableBloom | Boolean | 辐射图呈现泛光效果,默认为false |
| toggleAnimation | AnimationOptions | 开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,‘grow’生长两种类型,animationType默认为‘fade’淡入淡出 |
| processAnimation | RadiationProcessAnimationOptions | 过程动画配置参数,不配置则开启默认辐射动画,animationType支持‘radiated’辐射一种动画类型,默认为‘radiated’;repeat仅支持Infinity,默认为Infinity |
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
</br></br>
## RadiationStyle 对象规范 {#3}
辐射图样式规范。
|名称|类型|说明|
|--|--|--|
| fillColor | String | 辐射圈填充颜色,支持rgb()、rgba()、#RRGGBB格式,默认为rgba(56, 124, 234, 0.7) |
| strokeColor | String | 辐射圈边线颜色,支持rgb()、rgba()、#RRGGBB格式,默认为rgb(103, 153, 234) |
| strokeWidth | Number | 区域边线宽度,单位为米,默认为1 |
| strokeFadeMode | RADIATION_STROKE_FADE_MODE | 边线渐隐模式,默认为RADIATION_STROKE_FADE_MODE.NONE |
</br></br>
## RadiationPlane 对象规范 {#4}
辐射圈数据规范。
**属性**
|名称|类型|说明|
|--|--|--|
| center | LatLng | 辐射圈中心点经纬度。 |
| styleId | String | 样式id。 |
| radius | Number | 辐射圈半径,单位为米。 |
| properties | Object |附加的属性值。 |
</br>
## RadiationProcessAnimationOptions 对象规范 {#5}
辐射圈过程动画配置参数,继承自 AnimationOptions,增加了以下属性。
| 属性名 | 类型 | 说明 |
| :- | :- |:- |
| fadeFrom | Number | 辐射过程中颜色淡化起始值,取值范围为0~1,默认为0,即从中心开始淡化,直到半径最大处消失。若设置为1,则不会有淡化效果 |
</br>
## TMap.visualization.constants.RADIATION_STROKE_FADE_MODE 常量说明 {#6}
辐射圈边线渐隐模式常量。
| 常量 | 说明 |
| :- | :- |
| RADIATION_STROKE_FADE_MODE.NONE | 无渐隐 |
| RADIATION_STROKE_FADE_MODE.OUTWARD | 向外侧渐隐 |
| RADIATION_STROKE_FADE_MODE.INWARD | 向内侧渐隐 |
| RADIATION_STROKE_FADE_MODE.BOTHWAY | 双向渐隐 |
FILE:references/visualization/docs/轨迹图.md
## Trail {#1}
----
是用于创建轨迹图的类,轨迹图用以展示目标移动轨迹。
|构造函数|
| :- |
|TMap.visualization.Trail(options:TrailOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setData(dataList:TrailLine[]) |this |设置数据。|
|getData() | TrailLine[] | 获取数据。 |
|setZIndex(zIndex: Number) | this | 设置图层绘制顺序。 |
|getZIndex() | Number| 获取图层绘制顺序。 |
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|setPickStyle(pickStyle: Function) |this |设置轨迹样式映射函数。|
|getPickStyle() |Function |获取轨迹样式映射函数。|
|setShowDuration(showDuration: Number) |this |设置动画中轨迹点高亮的持续时间。|
|getShowDuration() |Number |获取动画中轨迹点高亮的持续时间。|
|setPlayRate(playRate: Number) |this |设置轨迹图播放倍速。|
|getPlayRate() |Number |获取轨迹图播放倍速。|
|updateAnimation(type:String, animationOptions:AnimationOptions)|void|更新指定类别动画参数,类别包括‘toggle’一种。|
|getAnimation(type:String) | AnimationOptions | 获取指定类型的动画参数,类别包括‘toggle’一种。 |
|addTo(map:Map) |this |添加至指定地图实例。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |从地图中删除图层。|
|destroy() |this |销毁图层对象。|
|事件名 |参数 |说明|
| :- | :- |:- |
|click |evt:VisualEvent |点击轨迹时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## TrailStyle {#2}
----
轨迹图样式规范。
|属性名称 |类型 |说明|
| :- | :- |:- |
|color |String \| GradientColor |轨迹颜色,支持rgb()、rgba()、#RRGGBB格式,默认为#387CEA。同时支持渐变色,默认沿轨迹生长方向渐变,其中渐变色断点集合需符合GradientColor对象规范 。|
|width |Number |轨迹宽度,默认为4(px)。|
</br></br>
## TrailLine {#3}
----
单条轨迹数据规范。
|属性名称 |类型 |说明|
| :- | :- |:- |
|path |TrailPoint[] |轨迹点数组,轨迹点为纬度、经度、时间戳(可选)构成的数组,轨迹点需要按照时间从小到大排序。|
|colorOffset |Number[] |(可选)轨迹点对应的颜色偏移值数组,偏移值范围为[0,1],数组长度与轨迹点数组需保持一致,且值顺序一一对应,,若不传入颜色偏移数组或者传入数据不符合要求,则默认按照轨迹点所处轨迹线的位置百分比进行线性渐变。|
| properties | Object |(可选)附加的属性值。 |
</br></br>
## TrailPoint {#4}
----
单条轨迹数据规范。
|索引 |类型 |说明|
| :- | :- |:- |
|0 |Number |纬度。|
|1 |Number |经度。|
|2 |Number \| String |(可选)时间戳,支持Unix Timestamp或者符合RFC2822的字符串,若不传入时间点,则默认以1m/s匀速移动。|
</br></br>
## TrailOptions {#5}
----
轨迹图配置参数。
|属性名称 |类型 |说明|
| :- | :- |:- |
|pickStyle |Function |轨迹图样式映射函数,接收TrailLine数据返回对应样式,样式对象规范详见TrailStyle。|
|showDuration |Number |动画中轨迹亮部持续时间,正数,默认5s,当设置为Infinity时轨迹将一直显示。|
|startTime |Number |动画循环周期的起始时间戳,支持Unix Timestamp或者符合RFC2822的字符串,默认为数据中的最小时间。|
|endTime |Number |动画循环周期的结束时间戳,支持Unix Timestamp或者符合RFC2822的字符串,默认为数据中的最大时间,需大于startTime。|
|playRate |Number |动画播放倍速,默认为1。|
|playTimes |Number |动画播放次数,正整数,默认为Infinity。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式、是否使用高亮效果,其中选中样式需符合TrailStyle对象规范。|
|enableGeodesic | Boolean | 轨迹线是否开启大地曲线绘制模式,当线段起始端点经度跨度大于180度时,开启后则两端点连线会跨越180度经线进行连线,不开启则跨越0度经线进行连线,默认为false。 |
|toggleAnimation |AnimationOptions| 开关动画配置参数,不配置则无开关动画效果。支持animationType为'fade'淡入淡出动画一种类型。|
|enableHighlightPoint |Boolean| 是否显示头部高亮点,默认值为false。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
|enableColorOffset | Boolean | 是否根据colorOffset来进行渐变色渲染,默认为false。 |
FILE:references/visualization/docs/行政区划.md
## DistrictOverlay {#1}
----
行政区划图层,支持传入行政区划adcode,提供高品质的行政区划显示能力。
|构造函数|
| :- |
|TMap.visualization.DistrictOverlay(options:DistrictOverlayOptions)|
**方法**
| 方法名 | 返回值 | 说明 |
| :- | :- | :- |
| setStyle(style: DistrictOverlayStyle) | this | 设置行政区划图层样式 |
| getStyle() | DistrictOverlayStyle| 获取行政区划图层样式 |
| setAdcode(adcode: String) | this | 设置行政区划adcode |
| getAdcode() | String | 获取行政区划adcode |
| setVisible(visible: Boolean) | this | 设置行政区划图层的可见性 |
| getVisible() | Boolean | 获取行政区划图层的可见性 |
| setBGImageVisible(visible: Boolean) | this | 设置背景图的可见性 |
| getBGImageVisible() | Boolean | 获取背景图的可见性 |
| on(eventName:String, listener:Function)|this|添加listener到eventName事件的监听器数组中。|
| off(eventName:String, listener:Function)|this|从eventName事件的监听器数组中移除指定的listener。|
| destroy()| | 销毁添加的行政区划图层|
| 事件名 | 参数 | 说明 |
| :- | :- | :- |
|click|DistrictOverlayEvent|点击事件|
## DistrictOverlayOptions 对象规范 {#2}
行政区划图层参数
| 名称 | 类型 | 说明 |
| :- | :- | :- |
| map | Map | 地图对象 |
| adcode | String | 行政区划的编码 |
| style | DistrictOverlayStyle| 行政区划的样式配置 |
## DistrictOverlayStyle 对象规范 {#3}
行政区划样式
| 名称 | 类型 | 说明 |
| :- | :- | :- |
| topColor | String \| GradientColor| 行政区划顶面填充色,支持rgb(),rgba(),#RRGGBB等形式,支持设置渐变色,支持设置自定义图片url或base64地址,若为url地址图片一定要在服务器端配置允许跨域。 |
| sideColor | String \| GradientColor| 行政区划侧面填充色,支持rgb(),rgba(),#RRGGBB等形式,支持设置渐变色,支持设置自定义图片url或base64地址,若为url地址图片一定要在服务器端配置允许跨域。 |
| extrudeHeight | Number | 行政区划拔起高度,单位米,默认值为2500 |
| backgroundImage | String | 行政区划周边背景图url或base64地址,若为url地址图片一定要在服务器端配置允许跨域。 |
| borderColor |String | 边线颜色,支持rgb(),rgba(),#RRGGBB等形式,默认为#75baff。|
| borderWidth |Number |边线宽度,正整数,单位为像素,默认为2。|
| wallHeight | Number | 发光围墙高度,高度从行政区划顶面起算,取值范围[0,500000],单位为米。默认值为2500。 |
| wallColor | String | 发光围墙颜色,支持rgb(),rgba(),#RRGGBB等形式。 |
| childrenStyle |DistrictChildrenStyle |子行政区样式。|
## DistrictChildrenStyle 对象规范 {#4}
子行政区划样式
| 名称 | 类型 | 说明 |
| :- | :- | :- |
| borderColor |String | 子行政区划边线颜色,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(255,255,255,1)。|
| borderWidth |Number | 子行政区划边线宽度,正整数,单位为像素,默认为2。|
| borderDashArray |Number[] | 子行政区划边线虚线虚线展示方式,[0, 0]为实线,[10, 10]表示十个像素的实线和十个像素的空白(如此反复)组成的虚线,默认为[0, 0]。|
| showLabel |Boolean | 是否显示子行政区划文字,默认为true。|
| labelSize |String | 子行政区划文字大小属性,默认为14。|
| labelColor |String | 子行政区划文字颜色属性,支持rgb(),rgba(),#RRGGBB等形式,默认为rgba(255,255,255,1)。|
| labelFaceTo |String | 子行政区划文字的朝向,可取map(贴地)或screen(直立),默认为map。|
## DistrictOverlayEvent 对象规范 {#5}
行政区划点击事件返回参数规范。
**属性**
| 名称 | 类型 | 说明 |
| -- | -- | -- |
| adcode | String | 子行政区划的adcode |
| name | String | 子行政区划的全称 |
| location | LatLng| 子行政区划的经纬度位置 |
| latLng | LatLng| 事件发生时的经纬度坐标 |
| type | String | 事件类型 |
| target | DistrictOverlay | 事件的目标对象 |
| originalEvent | MouseEvent 或 TouchEvent | 浏览器原生的事件对象 |
FILE:references/visualization/docs/参考手册.md
## 轨迹图 {#1}
- Trail 是用于创建轨迹图的类,轨迹图用以展示目标移动轨迹。
- TrailOptions 轨迹图配置参数。
## 热力图 {#2}
- Heat 是用于创建热力图的类,热力图以颜色来表现数据强弱大小及分布趋势。
- HeatOptions 热力图配置参数。
- HeatPoint 热力图单点数据规范。
## 蜂窝热力图 {#3}
- Hexagon 是用于创建轨迹图的类,轨迹图用以展示目标移动轨迹。
- HexagonOptions 轨迹图配置参数。
## 网格热力图 {#4}
- Grid 是用于创建网格热力图的类,网格热力图将离散的数据点以正方形网格区域进行聚合,根据落入区域内的数据点数量渲染不同颜色的高度的正方形棱柱。
- GridOptions 网格热力图配置参数。
## 弧线图 {#5}
- Arc 是用于创建3D弧线图的类,弧线图用以展示两点之间的关联,可以用在迁徙图等表示流向的场景中。
- ArcOptions 轨迹图配置参数。
## 散点图 {#6}
- Dot 是用于创建3D散点图的类,散点图用以展示海量的独立数据点。
- DotOptions 散点图配置参数。
## 区域图 {#7}
- Area 是用于创建区域图的类,用以展示不同地区的轮廓形状和数据分布。
- AreaOptions 区域图配置参数。
## 事件 {#8}
- VisualEvent 可视化事件返回参数规范。
- VisualDetail 可视化事件实例数据对象规范。
- VisualSelectOptions 可视化图形拾取配置。
FILE:references/visualization/docs/水晶体.md
## Prism {#1}
----
是用于创建水晶体的类,用以展示不同高度的建筑轮廓形状和数据分布。
|构造函数|
| :- |
| TMap.visualization.Prism(options:PrismOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setStyles(styles: Object) |this |设置样式集合。|
|setData(dataList:AreaPlane[]) |this |设置数据。|
|getData() |AreaPlane[] |获取数据。|
|setZIndex(zIndex:Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|addTo(map:Map) |this |添加至指定地图实例。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |从地图中删除图层。|
|destroy() |this |销毁图层对象。|
|updateAnimation(type:String, animationOptions: AnimationOptions) |this |更新指定类型的动画参数,type支持‘toggle’。|
|getAnimation(type:String) |AnimationOptions |获取指定类型的动画参数,type支持‘toggle’。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| 事件名 | 回调参数 | 说明 |
| :- | :- |:- |
|click |evt:VisualEvent | 点击区域时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## PrismStyle {#2}
----
区域图样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|topFillColor |String\|GradientColor |水晶体顶面颜色,支持rgb()、rgba()、#RRGGBB格式,默认为#017CF7,同时支持渐变色,渐变方向由GradientColor对象的angle属性决定,其中渐变色断点集合需符合GradientColor对象规范。|
|sideFillColor |String\|GradientColor |水晶体侧面颜色,支持rgb()、rgba()、#RRGGBB格式,默认为#017CF7,同时支持渐变色,渐变方向由GradientColor对象的angle属性决定,其中渐变色断点集合需符合GradientColor对象规范。|
|height |Number |水晶体拔起高度,单位为米,默认为0。|
|offset |Number |水晶体距离地面高度,单位为米,默认为0。|
</br></br>
## PrismOptions {#3}
----
区域图配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|styles |Object |水晶体样式集合,key-value形式。key对应数据中的styleId,value为样式对象,需符合PrismStyle对象规范。包含default属性,其值作为默认样式,可被覆盖。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式,其中选中样式需符合PrismStyle对象规范,不支持设置height和offset。|
|enableBloom |Boolean |水晶体呈现泛光效果,默认为false。|
|enableLighting |Boolean |水晶体是否开启光照,默认为true。 |
|toggleAnimation |AnimationOptions |开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,‘grow’生长两种类型,默认animationType为‘fade’淡入淡出。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
FILE:references/visualization/docs/热力图.md
## Heat {#1}
----
是用于创建热力图的类,热力图以颜色来表现数据强弱大小及分布趋势。
|构造函数|
| :- |
|TMap.visualization.Heat(options:HeatOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setMin(min:Number) |this |设置热力最弱阈值,小于该值的不显示,默认为0。|
|getMin() |Number |获取热力最弱阈值。|
|setMax(max:Number) |this |设置热力最强阈值,大于该值的显示为最强色,默认为数据中的最大值。|
|getMax() |Number |获取热力最强阈值。|
|setRadius(radius: Number) |this |设置热力图辐射半径。|
|getRadius() |Number |获取热力图辐射半径。|
|setGradientColor(gradientColor: GradientColor) |this |设置热力图渐变颜色。|
|getGradientColor() |GradientColor |获取热力图渐变颜色。|
|setHeight(height: Number) |this |设置热力图高度。|
|getHeight() |Number |获取热力图高度。|
|setOpacity(opacity: Number) |this |设置热力图透明度。|
|getOpacity() |Number |获取热力图透明度。|
|setThreshold(min:Number, max:Number) |this |设置热力阈值范围。|
|setData(dataList:HeatPoint[] ) |this |设置数据。|
|getData() |HeatPoint[] |获取数据。|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|setOffset(offset: Number) | this | 设置图层底部离地高度。 |
|getOffset() | Number| 获取图层底部离地高度。 |
|addTo(map:Map ) |this |添加至指定地图实例。|
|updateAnimation(type:String, animationOptions:AnimationOptions ) |this |更新指定类别动画参数,type支持'toggle','transit'两种。|
|getAnimation(type:String) |AnimationOptions |获取指定类别动画参数,type支持'toggle','transit'两种。|
| setHeatDotEnable(enable: Boolean) | this | 设置是否开启自动切换散点图功能 |
| getHeatDotEnable() | Boolean | 获取是否开启自动切换散点图状态 |
| setHeatDotOptions(heatDotOptions: HeatDotOptions) | this | 设置自动切换散点图相关配置 |
| getHeatDotOptions() | HeatDotOptions | 获取自动切换散点图相关配置 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |从地图中删除图层。|
|destroy() |this |销毁图层对象。|
</br></br>
## HeatOptions {#2}
----
热力图配置参数。
|属性名称 |类型 |说明|
| :- | :- |:- |
|radius |Number |最大辐射半径,默认为50。|
|height |Number |峰值高度,默认为100。|
|gradientColor |GradientColor |渐变颜色,渐变方向由GradientColor对象的angle属性决定,其中渐变色断点集合需符合GradientColor对象规范 |
|min |Number |热力最弱阈值,小于该值的不显示,默认为0。|
|max |Number |热力最强阈值,大于该值的显示为最强色,默认为数据中的最大值。|
|opacity |Number |全局透明度,取值范围[0,1],默认为0.8。|
|enableAggregation | Boolean | 是否启用自动聚合预处理,适用于万级数据量,启用后可优化运行时性能,但对数据分布略有影响。默认为false。 |
|enableLighting | Boolean | 热力图是否呈现光照效果,默认为false。 |
|transitAnimation |AnimationOptions |热力图数据源切换过渡动画配置参数,不配置则无过渡动画。支持animationType为‘mix’渐变一种类型,默认animationType为‘mix’渐变。|
|toggleAnimation |AnimationOptions |开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出一种类型,默认animationType为‘fade’淡入淡出。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
|offset| Number | 图层底部离地高度,默认为0。 |
|distanceUnit| String | radius(半径)、height(峰值高度)、offset(离地高度)三个参数的单位,支持'pixel' 像素、'meter' 米,默认为'pixel'。 |
| enableHeatDot | Boolean | 热力图是否开启自动切换散点图效果,默认为false。|
| heatDotOptions | HeatDotOptions | 自动切换散点图相关配置参数。 |
</br></br>
## HeatPoint {#3}
----
热力图单点数据规范。
|属性名称 |类型 |说明|
| :- | :- |:- |
|lat |Number |纬度。|
|lng |Number |经度。|
|count |Number |(可选)热力权重,正整数,默认为1。|
| properties | Object | (可选)附加的属性值。 |
</br></br>
## HeatDotOptions {#4}
----
切换散点图相关配置参数。
|属性名称 |类型 |说明|
| :- | :- |:- |
| radius | Number | 散点半径,默认为热力图最大辐射半径的一半 |
| minZoom | Number | 散点图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,minZoom需大于等于热力图设定的minZoom,默认为14 |
| maxZoom | Number | 散点图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,maxZoom需小于等于热力图设定的maxZoom,默认为热力图设定的maxZoom |
| opacity | Number | 散点图层透明度,取值范围[0, 1],默认为1.0 |
| fillColors | String[] | 散点颜色数组,每个散点将根据自身count属性值指定为数组中的对应颜色,默认为热力图中配置的渐变颜色,如[ '#13B06A', '#13B06A', '#E9AB1D', '#E9AB1D', '#E05649'],支持rgb(),rgba(), #RRGGBB格式 |
| strokeColor | String | 散点边线颜色,支持rgb(),rgba(), #RRGGBB格式,默认为'#FFF' |
| strokeWidth | Number | 散点边线宽度,不可为负数,默认为0(px) |
| fadeArray | Number[] | 展现切换动画的层级,默认为[14,16], 表示在zoom层级为14时,热力图开始淡出(透明度减少),散点图开始淡入(透明度增加); 在zoom层级为16时,热力图完全隐藏,散点图达到指定的散点图层透明度; fadeArray中的数值必须在散点图指定的minZoom与maxZoom范围内,且fadeArray[0]小于fadeArry[1]。 查看示例|
FILE:references/visualization/docs/蜂窝热力图.md
## Hexagon {#1}
----
是用于创建蜂窝热力图的类,蜂窝热力图将离散的数据点以六边形区域进行聚合,根据落入区域内的数据点数量渲染不同颜色的高度的六边形棱柱。
|构造函数|
| :- |
|TMap.visualization.Hexagon(options:HexagonOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|getValueRange() |Number[] |获取聚合数据取值范围,[min, max]。|
|setShowRange(showRange:Number[]) |this |设置聚合数据显示区间范围,[min, max]。|
|getShowRange() |Number[] |获取聚合数据显示区间范围。|
|setRadius(radius: Number) | this | 设置六边形中心点到端点的距离, 单位为米。 |
|getRadius() | Number| 获取六边形中心点到端点的距离, 单位为米。 |
|setExtrudable(extrudable: Boolean) | this | 设置六边形是否可拔起。 |
|getExtrudable() | Boolean| 获取六边形是否可拔起。 |
|setColorList(colorList: String[]) | this | 设置颜色层级。 |
|getColorList() | String[]| 获取颜色层级。 |
|setHeightRange(heightRange: Number[]) | this | 设置高度变化区间,需要传入正整数,若extrudable为false,则不生效。 |
|getHeightRange() | Number[]| 获取高度变化区间。 |
|setData(dataList:HeatPoint[]) |this |设置数据。|
|getData() | HeatPoint[] | 获取数据。 |
|setZIndex(zIndex: Number) | this | 设置图层绘制顺序。 |
|getZIndex() | Number| 获取图层绘制顺序。 |
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|addTo(map: Map) |this |添加至指定地图实例。|
|updateAnimation(type:String, animationOptions:AnimationOptions)|void|更新指定类别动画参数,type支持‘toggle’。|
|getAnimation(type:String) | AnimationOptions | 获取指定类型的动画参数,type支持‘toggle’。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
| setCoverage(coverage: Number) | this | 设置六边形覆盖范围比例系数, 取值范围(0, 1.0] |
| getCoverage() | Number | 获取六边形覆盖范围比例系数 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |删除图层。|
|destroy() |this |销毁图层对象。|
|事件名 |参数 |说明|
| :- | :- |:- |
|click |evt:VisualEvent |点击棱柱时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## HexagonOptions {#2}
----
蜂窝热力图配置参数。
|属性名称 |类型 |说明|
| :- | :- |:- |
|radius |Number |六边形中心点到端点的距离, 单位为米, 默认1000。|
|extrudable |Boolean |六边形是否可拔起,默认为true。|
|colorList |String[] |颜色层级,颜色支持rgb(),rgba(), #RRGGBB格式, 默认为[’#D8AFA7’, ‘#842610’, ‘#641200’]。|
|heightRange |Number[] |高度变化区间,需要传入正整数,默认为[1, 100],若extrudable为false,则不生效。|
|showRange |Number[] |蜂窝聚合数据显示区间,需要传入正整数,区间外的数据不显示,区间内的数据线性映射到高度区间及颜色层级。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式、是否使用高亮效果,其中选中样式为String类型的颜色值。|
|enableBloom | Boolean | 蜂窝热力图呈现泛光效果,默认为false。 |
|toggleAnimation|AnimationOptions | 开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,'grow'生长两种类型,默认animationType为‘fade’淡入淡出。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
|heightScale | Number | 蜂窝热力图高度缩放系数,需要传入正数,默认为1.0。 |
| coverage | Number | 网格覆盖范围比例系数,可用于调整网格间的间隙,取值范围(0, 1.0],默认为0.8 |
FILE:references/visualization/docs/弧线图.md
## Arc {#1}
----
是用于创建3D弧线图的类,弧线图用以展示两点之间的关联,可以用在迁徙图等表示流向的场景中。
|构造函数|
| :- |
|TMap.visualization.Arc(options:ArcOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setData(dataList:ArcLine[]) |this |设置数据。|
|getData() |ArcLine[] |获取数据。|
|setZIndex(zIndex:Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|setPickStyle(pickStyle: Function) |this |设置弧线图样式映射函数。|
|getPickStyle() |Function |获取弧线图样式映射函数。|
|setCurvature(curvature: Number) |this |设置弧线的曲度。|
|getCurvature() |Number |获取弧线的曲度。|
|setMode(mode: String) |this |设置弧线的模式,是贴地的弧线或垂直的弧线。|
|getMode() |String |获取弧线的模式。|
|setAnimDuration(duration: Number) |this |设置动画时间,从起点到终点的运动时间(即将下线,请使用updateAnimation设置duration属性进行动画时间设置)。|
|getAnimDuration() |Number |获取动画时间,从起点到终点的运动时间(即将下线,请使用getAnimation获取duration属性)。|
|setHighlightDuration(HighlightTime: Number) |this |设置动画的高亮时间,影响弧线上高亮的长度(即将下线,请使用updateAnimation设置tailFactor属性进行动画尾迹时间设置)。|
|getHighlightDuration() |Number |获取高亮时间(即将下线,请使用getAnimation获取tailFactor属性)。|
|addTo(map:Map) |this |添加至指定地图实例。|
|updateAnimation(type:String, animationOptions: AnimationOptions) |this |更新指定类型的动画参数,type支持‘process’,‘toggle’。|
|getAnimation(type:String) |AnimationOptions |获取指定类型的动画参数,type支持‘process’,‘toggle’。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
|getMidPositions() | LatLng[] | 获取图层所有弧线的中点坐标,不支持在大地曲线绘制模式下使用。 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |删除图层。|
|destroy() |this |销毁图层对象。|
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |evt:VisualEvent |点击弧线时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## ArcStyle {#2}
----
弧线图样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|color |String\|GradientColor |弧线颜色,支持rgb(), rgba(), #RRGGBB格式,默认为rgba(56,124,234,0.3),同时支持渐变色,只支持双色渐变,默认沿弧线生长方向渐变,其中渐变色断点集合需符合GradientColor对象规范。|
|animateColor |String |动画颜色,支持rgb(), rgba(), #RRGGBB格式,默认为rgba(29,211,253,1)。|
|width |Number |弧线的宽度,默认为4(px)。|
</br></br>
## ArcLine {#3}
----
单条弧线数据规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|from |LatLng |弧线起点。|
|to |LatLng |弧线终点。|
| tilt | Number | 弧线相对于vertical模式的倾斜度,当mode为horizontal时此参数不生效,默认值为0,取值范围为[-90, 90]。以从弧线起点到弧线终点为正方向作参考,左边倾斜度为负数,右边倾斜度为正数,弧线与地面垂直时倾斜度为0。|
| properties | Object | 附加的属性值。 |
</br></br>
## ArcOptions {#4}
----
轨迹图配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|pickStyle |Function |轨迹图样式映射函数,接收ArcLine数据返回对应样式,样式对象规范详见 ArcStyle 。|
|animatable |Boolean |是否开启动画,默认为true(即将下线,请使用processAnimation设置enable属性)。|
|opacity |Number |弧线透明度,取值范围(0, 1],默认0.5,(即将下线,请在ArcStyle中使用rgba格式设置透明度)。|
|width |Number |弧线的宽度,默认为1,单位是屏幕像素,(即将下线,请在ArcStyle定义弧线宽度)。|
|mode |String |弧线模式,horizontal 代表贴地的弧线,vertical 代表弧线所在平面会垂直于底图平面,默认为vertical。|
|curvature |Number |弧线曲度,取值范围(0, 1],默认为0.6。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式、是否使用高亮效果,其中选中样式需符合ArcStyle对象规范。|
|enableBloom |Boolean |弧线图呈现泛光效果,默认为false。|
| enableGeodesic | Boolean | 弧线是否开启大地曲线绘制模式,当线段起始端点经度跨度大于180度时,开启后则两端点连线会跨越180度经线进行连线,不开启则跨越0度经线进行连线,默认为false |
|toggleAnimation |AnimationOptions |开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,‘grow’生长两种类型,默认animationType为‘fade’淡入淡出。|
|processAnimation |ArcAnimationOptions |过程动画配置参数,不配置则启用默认流动动画。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
</br></br>
## ArcAnimationOptions {#5}
----
弧线图过程动画规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|enable |Boolean |是否启用,默认为true。|
|tailFactor |Number |尾迹比例,取值范围0~1,默认为0.3。|
|animationType |String |动画类型名称,支持‘flow’流动一种动画类型,默认为‘flow’流动。|
|duration |Number |动画时长,单位毫秒,默认3000。|
|yoyo |Boolean |是否回弹,默认false。|
|repeat |Number |动画执行次数,默认为Infinity,不支持设置为其他值。|
FILE:references/visualization/docs/管道图.md
## Pipe {#1}
----
是用于创建管线图的类,用以展示管道线。
|构造函数|
| :- |
|TMap.visualization.Pipe(options:PipeOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- | :- |
| setPickStyle(pickStyle: Function) | this | 设置样式集合。 |
| setData(dataList: PipeLine[]) | this | 设置数据。 |
| getData() | PipeLine[] | 获取数据。 |
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
| addTo(map:Map) | this | 添加至指定地图实例。 |
|updateAnimation(type:String,animationOptions:AnimationOptions) |this |更新指定类别动画参数,type支持'toggle'。|
|getAnimation(type:String) |AnimationOptions|获取指定类型的动画参数,type支持'toggle'。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
| show() | this | 显示图层。 |
| hide() | this | 隐藏图层。 |
| remove() | this | 从地图中删除图层。 |
| destroy() | this | 销毁图层对象。 |
| on(eventName:String, listener:Function) | this | 添加listener到eventName事件的监听器数组中。 |
| off(eventName:String, listener:Function) | this | 从eventName事件的监听器数组中移除指定的listener。 |
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
| click | evt:VisualEvent | 点击管道线时触发。 |
| hover | evt:VisualEvent | 鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null |
</br></br>
## PipeStyle {#2}
----
管道图样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| color | String | 管道颜色,支持rgb()、rgba()、#RRGGBB格式,默认为rgba(56, 124, 234, 0.7) 。|
</br></br>
## PipeLine {#3}
----
管道线数据规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| path | LatLng[] \| PipePoint[] | 管道线经纬度点串。 |
| radius | Number | 管道半径,单位为米。 |
| styleId | String | 样式id。 |
| properties | Object |附加的属性值。 |
</br></br>
## PipePoint {#4}
----
管道线单点数据规范,PipePoint是一个Array类型
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| 0 | Number | 纬度。 |
| 1 | Number | 经度。 |
| 2 | Number | (可选)高度,单位为米,默认为0。 |
</br></br>
## PipeOptions {#5}
----
管道图配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| pickStyle | Function | 管道图样式映射函数,接收 PipeLine 数据返回对应样式,样式对象规范详见 PipeStyle。 |
| selectOptions | VisualSelectOptions | 拾取配置,可设置拾取动作、选中样式,其中选中样式需符合 PipeStyle 对象规范。 |
| enableBloom | Boolean | 管道图呈现泛光效果,默认为false。 |
| toggleAnimation |AnimationOptions | 开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,‘grow’生长两种类型,animationType默认为‘fade’淡入淡出 |
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
FILE:references/visualization/docs/散点图.md
## Dot {#1}
----
是用于创建3D散点图的类,散点图用以展示海量的独立数据点。
|构造函数|
| :- |
|TMap.visualization.Dot(options:DotOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setStyles(styles: Object) |this |设置样式集合,styles为key-value形式。key对应数据中的styleId,value为样式对象,需符合DotCircleStyle或DotImageStyle对象规范。|
|setFaceTo(faceTo: String) |this |设置散点朝向,可取map(贴地)或screen(直立)。|
|setData(dataList:DotPoint[]) |this |设置数据。|
|getData() |DotPoint[] |获取数据。|
|setZIndex(zIndex: Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|addTo(map:Map) |this |添加至指定地图实例。|
|updateAnimation(type:String,animationOptions:AnimationOptions) |this |更新指定类别动画参数,type支持'toggle','process'。|
|getAnimation(type:String) |AnimationOptions|获取指定类型的动画参数,type支持'toggle','process'。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |从地图中删除图层。|
|destroy() |this |销毁图层对象。|
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |evt:VisualEvent |点击散点时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## DotCircleStyle{#2}
----
散点图圆形样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|type |String |取值为circle。|
|fillColor |String\|GradientColor |圆形样式属性,填充颜色,支持rgb(),rgba(), #RRGGBB格式,默认为rgba(56,124,234,0.75), 同时支持渐变色,只支持双色渐变,默认沿弧线生长方向渐变,其中渐变色断点集合需符合GradientColor对象规范。
|strokeColor |String |圆形样式属性,边线颜色,支持rgb(),rgba(), #RRGGBB格式。|
|strokeWidth |Number |圆形样式属性,边线宽度,不可为负数,默认为0(px)。|
|radius |Number |圆形样式属性,圆形半径,不可为负数,默认为4(px)。|
</br></br>
## DotImageStyle{#3}
----
散点图图片样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|type |String |取值为image。|
|width |Number |图片样式属性,图片显示宽度,不可为负数,默认为10(px)。|
|height |Number |图片样式属性,图片显示高度,不可为负数,默认为10(px)。|
|anchor |Object |图片的锚点位置,对象字面量{x:Number, y:Number}形式,以图片左上角为原点,x轴向右为正,y轴向下为正,默认为{ x: width/2, y: height/2 },即图片中心点。|
|src |String |图片样式属性,图片url或base64地址。|
</br></br>
## DotPoint {#4}
----
单点数据规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|lat |Number |(必需)纬度。|
|lng |Number |(必需)经度。|
|styleId |String |点样式ID。|
| properties | Object | 附加的属性值。 |
</br></br>
## RadiatedAnimationOptions 对象规范 {#6}
----
辐射动画配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| type | String |动画类型为辐射动画,取值radiated |
| playRate | Number |动画播放倍速,默认为1(播放周期为1000ms) |
| enableFade | Boolean |动画的辐射效果是否渐变消失,默认为false |
</br></br>
## BeatingAnimationOptions 对象规范 {#7}
----
跳动动画配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
| type | String |动画类型为跳动动画,取值beating |
| playRate | Number |动画播放倍速,默认为1(播放周期为1000ms) |
| amplitude | Number |动画跳动的幅度,单位为像素高度 |
</br></br>
## DotAnimationOptions {#8}
----
属性。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|enable |Boolean |是否启用,默认为true。|
|beatingAmplitude |Number |beating幅度,默认为5,仅在动画类型为‘beating’时生效。|
|enableFade |Boolean |动画的辐射效果是否渐变消失,默认为false,仅在动画类型为‘radiated’时生效。|
|animationType |String |动画类型名称,支持‘beating’跳动,‘radiated’辐射两种类型,默认为‘radiated’辐射。|
|duration |Number |动画时长,单位毫秒,默认2000。|
|repeat |Number |迭代次数,默认Infinity,不支持设置为其他值。|
|yoyo |Boolean |是否回弹,默认false。|
</br></br>
## DotOptions {#5}
----
散点图配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|styles |Object |散点图样式集合,key-value形式。key对应数据中的styleId,value为样式对象,需符合 CircleDotStyle 或 ImageDotStyle 对象规范。|
|faceTo |String |散点固定的朝向,可取map(贴地)或screen(直立),默认为screen。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式、是否使用高亮效果,其中选中样式需符合CircleDotStyle或ImageDotStyle对象规范。|
|animation |RadiatedAnimationOptions \| BeatingAnimationOptions | 散点动画参数 (1.1.0及以上版本即将下线,请使用processAnimation设置动画属性)。 |
|enableBloom |Boolean |散点图呈现泛光效果,默认为false。|
|toggleAnimation |AnimationOptions | 开关动画配置参数,不配置则无开关动画效果,支持animationType为‘fade’淡入淡出一种类型,默认为‘fade’淡入淡出 |
|processAnimation |DotAnimationOptions | 过程动画配置参数,不配置则无过程动画 |
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
FILE:references/visualization/docs/网格热力图.md
## Grid {#1}
----
是用于创建网格热力图的类,网格热力图将离散的数据点以正方形网格区域进行聚合,根据落入区域内的数据点数量渲染不同颜色的高度的正方形棱柱。
|构造函数|
| :- |
|TMap.visualization.Grid(options:GridOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|getValueRange() |Number[] |获取聚合数据取值范围,[min, max]。|
|setShowRange(showRange:Number[]) |this |设置聚合数据显示区间范围,[min, max]。|
|getShowRange() |Number[] |获取聚合数据显示区间范围。|
|setSideLength(sideLength: Number) | this | 设置单位正方形网格边长, 单位为米, 单位为米。 |
|getSideLength() | Number| 获取单位正方形网格边长, 单位为米, 单位为米。 |
|setExtrudable(extrudable: Boolean) | this | 设置网格是否可拔起。 |
|getExtrudable() | Boolean| 获取网格是否可拔起。 |
|setColorList(colorList: String[]) | this | 设置颜色层级。 |
|getColorList() | String[]| 获取颜色层级。 |
|setHeightRange(heightRange: Number[]) | this | 设置高度变化区间,需要传入正整数,若extrudable为false,则不生效。 |
|getHeightRange() | Number[]| 获取高度变化区间。 |
|setData(dataList:HeatPoint[]) |this |设置数据。|
|getData() | HeatPoint[] | 获取数据。 |
|setZIndex(zIndex: Number) | this | 设置图层绘制顺序。 |
|getZIndex() | Number| 获取图层绘制顺序。 |
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|addTo(map:Map ) |this |添加至指定地图实例。|
|updateAnimation(type:String, animationOptions:AnimationOptions)|void|更新指定类别动画参数,type支持‘toggle’。|
|getAnimation(type:String) | AnimationOptions | 获取指定类型的动画参数,type支持‘toggle’。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
| setCoverage(coverage: Number) | this | 设置网格覆盖范围比例系数, 取值范围(0, 1.0] |
| getCoverage() | Number | 获取网格覆盖范围比例系数 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |删除图层。|
|destroy() |this |销毁图层对象。|
|事件名 |参数 |说明|
| :- | :- |:- |
|click |evt:VisualEvent |点击棱柱时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## GridOptions {#2}
----
网格热力图配置参数。
|属性名称 |类型 |说明|
| :- | :- |:- |
|sideLength |Number |单位正方形网格边长, 单位为米, 默认1000。|
|extrudable |Boolean |网格是否可拔起,默认为true。|
|colorList |String[] |颜色层级,颜色支持rgb(), rgba(),#RRGGBB格式, 默认为[’#D8AFA7’, ‘#842610’, ‘#641200’]。|
|heightRange |Number[] |高度变化区间,需要传入正整数,默认为[1, 100],若extrudable为false,则不生效。|
|showRange |Number[] |网格聚合数据显示区间,需要传入正整数,区间外的数据不显示,区间内的数据线性映射到高度区间及颜色层级。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式、是否使用高亮效果,其中选中样式为String类型的颜色值。|
|enableBloom | Boolean | 网格热力图呈现泛光效果,默认为false。 |
|toggleAnimation|AnimationOptions| 开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,‘grow’生长两种类型,默认animationType为‘fade’淡入淡出。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
|heightScale | Number | 网格热力图高度缩放系数,需要传入正数,默认为1.0。 |
| coverage | Number | 网格覆盖范围比例系数,可用于调整网格间的间隙,取值范围(0, 1.0],默认为0.8 |
FILE:references/visualization/docs/区域图.md
## Area {#1}
----
是用于创建区域图的类,用以展示不同地区的轮廓形状和数据分布。
|构造函数|
| :- |
| TMap.visualization.Area(options:AreaOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setStyles(styles: Object) |this |设置样式集合。|
|setData(dataList:AreaPlane[]) |this |设置数据。|
|getData() |AreaPlane[] |获取数据。|
|setZIndex(zIndex:Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|addTo(map:Map) |this |添加至指定地图实例。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |从地图中删除图层。|
|destroy() |this |销毁图层对象。|
|updateAnimation(type:String, animationOptions: AnimationOptions) |this |更新指定类型的动画参数,type支持‘toggle’。|
|getAnimation(type:String) |AnimationOptions |获取指定类型的动画参数,type支持‘toggle’。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| 事件名 | 回调参数 | 说明 |
| :- | :- |:- |
|click |evt:VisualEvent | 点击区域时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## AreaStyle {#2}
----
区域图样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|fillColor |String\|GradientColor |区域填充颜色,支持rgb(),rgba(), #RRGGBB格式,默认为rgba(56,124,234,0.7),同时支持渐变色,渐变方向由GradientColor对象的angle属性决定,其中渐变色断点集合需符合GradientColor对象规范。|
|strokeColor |String |区域边线颜色,支持rgb()、rgba()、#RRGGBB格式,默认为rgb(103, 153, 234)。|
|strokeWidth |Number |区域边线宽度,默认为1(px)。|
|strokeDashArray |Number[] |边线虚线展示方式,[0, 0]为实线,[10, 10]表示十个像素的实线和十个像素的空白(如此反复)组成的虚线,默认为[0, 0]。|
</br></br>
## AreaPlane {#3}
----
单个区域数据规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|path |Number[],Number[][],Number[][][] |区域边界线经纬度点串,纬度、经度依次排列。可支持1维数组(简单多边形),如[lat_0, lng_0, lat_1, lng_1, …];可支持2维数组(带洞多边形),如[[lat_0, lng_0, lat_1, lng_1, …],[lat_2, lng_2, lat_3, lng_3, …]],第一个数组代表外环,其余数组代表内环;可支持3维数组(飞地,可由多个独立的带洞或简单多边形组成),如[[[lat_0, lng_0, lat_1, lng_1, …],[lat_2, lng_2, lat_3, lng_3, …]], [[lat_4, lng_4, lat_5, lng_5, …]]],由一个带洞多边形和一个简单多边形组成。|
|styleId |String |区域样式id。|
| properties | Object |附加的属性值。 |
</br></br>
## AreaOptions {#4}
----
区域图配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|styles |Object |区域图样式集合,key-value形式。key对应数据中的styleId,value为样式对象,需符合AreaStyle对象规范。包含default属性,其值作为默认样式,可被覆盖。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式,其中选中样式需符合AreaStyle对象规范。|
|enableBloom |Boolean |区域图呈现泛光效果,默认为false。|
|toggleAnimation |AnimationOptions |开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出一种类型,默认animationType为‘fade’淡入淡出。|
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
FILE:references/visualization/docs/围墙面.md
## Wall {#1}
----
是用于创建围墙面,用以展示重点区域、围栏等。
|构造函数|
| :- |
|TMap.visualization.Wall(options:WallOptions)|
| 方法 | 返回值 | 说明 |
| :- | :- |:- |
|setData(dataList:WallLine[]) |this |设置数据。|
|getData() |WallLine[] |获取数据。|
|setZIndex(zIndex:Number) |this |设置图层绘制顺序。|
|getZIndex() |Number |获取图层绘制顺序。|
|setMinZoom(minZoom: Number) | this | 设置图层最小缩放层级,当地图缩放层级小于该值时该图层不显示。 |
|getMinZoom() | Number| 获取图层最小缩放层级。 |
|setMaxZoom(maxZoom: Number) | this | 设置图层最大缩放层级,当地图缩放层级大于该值时该图层不显示。 |
|getMaxZoom() | Number| 获取图层最大缩放层级。 |
|setStyles(styles:Object) |this |设置样式。|
|addTo(map:Map) |this |添加至指定地图实例。|
|updateAnimation(type:String, animationOptions: AnimationOptions) |this |更新指定类型的动画参数,type支持‘process’,‘toggle’。|
|getAnimation(type:String) |AnimationOptions |获取指定类型的动画参数,type支持‘process’,‘toggle’。|
|getSelectOptions() | VisualSelectOptions | 获取拾取配置。 |
|setSelectOptions(selectOptions: VisualSelectOptions) | this | 设置拾取配置。 |
|show() |this |显示图层。|
|hide() |this |隐藏图层。|
|remove() |this |删除图层。|
|destroy() |this |销毁图层对象。|
|on(eventName:String, listener:Function) |this |添加listener到eventName事件的监听器数组中。|
|off(eventName:String, listener:Function) |this |从eventName事件的监听器数组中移除指定的listener。|
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |evt:VisualEvent |点击围墙面时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## WallStyle {#2}
----
围墙面样式规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|texture |String |纹理文件路径url,在墙体上重复拼接的图片,支持png、jpg、jpeg格式,优先级高于color。|
|color |String\|GradientColor |围墙面颜色,支持纯色或渐变色,颜色支持rgb()、rgba()、#RRGGBB格式,默认为rgba(255, 255, 255, 1),渐变方向由GradientColor对象的angle属性决定,其中渐变色断点集合需符合GradientColor对象规范。|
|repeatX |Number |纹理横轴重复次数,取值范围 (0,100]默认1。|
|repeatY |Number |纹理纵轴重复次数,取值范围 (0,100]默认1。|
|strokeColor | String | 边线颜色,只支持纯色,颜色支持rgb()、rgba()、#RRGGBB格式,默认值为#1DFAF2 |
|strokeWidth | Number | 边线宽度,单位为像素,默认为0|
</br></br>
## WallLine {#3}
----
围墙面数据规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|styleId |String |围墙面样式配置id。|
|path |LatLng[] |经纬度数组。|
|height |Number |围墙面高度,取值范围(0,500000],默认10。|
| properties | Object |附加的属性值。 |
</br></br>
## WallOptions {#4}
----
围墙面配置参数。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|styles |Object |围墙面样式集合,key-value形式。key对应数据中的styleId,value为样式对象,需符合WallStyle对象规范。|
|selectOptions |VisualSelectOptions |拾取配置,可设置拾取动作、选中样式、是否使用高亮效果,其中选中样式为String类型的颜色值。|
|enableBloom |Boolean |围墙面呈现泛光效果,默认为false。|
|toggleAnimation |AnimationOptions |开关动画配置参数,不配置则无开关动画效果。支持animationType为‘fade’淡入淡出,‘grow’生长两种类型,默认animationType为‘fade’淡入淡出。|
|processAnimation |WallAnimationOptions |过程动画配置参数,不配置则无过程动画。|
|enableAutoClose| Boolean | 设置围墙面是否自动闭合,设置为自动闭合,则会自动把首尾点连起来构成封闭的围墙面,默认为false |
|zIndex | Number | 图层绘制顺序。 |
|minZoom | Number | 图层最小缩放层级,当地图缩放层级小于该值时该图层不显示,默认为3。 |
|maxZoom | Number | 图层最大缩放层级,当地图缩放层级大于该值时该图层不显示,默认为20。 |
| 事件名 | 参数 | 说明 |
| :- | :- |:- |
|click |evt:VisualEvent |点击棱柱时触发。|
|hover |evt:VisualEvent |鼠标悬停目标改变时触发,若悬停在图形外部,则返回结果中的拾取对象为null。|
</br></br>
## WallAnimationOptions {#5}
----
围墙面过程动画规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|enable |Boolean |是否启用,默认为true。|
|flowDirection |String |流动方向,‘horizontal‘水平流动,’vertical‘垂直流动,默认‘horizontal’水平流动,仅在动画类型为‘flow’时生效。|
|breathAmplitude |Number |呼吸幅度,透明度降低,默认0.2,如默认透明度为0.8,幅度设为0.3后则透明度将在0.5~0.8之间波动,仅在动画类型为‘breath’时生效。|
|animationType |String |动画类型名称,支持‘flow’流动,‘breath’呼吸两种动画类型,默认为‘flow’流动。|
|duration |Number |动画时长,单位毫秒,默认2000。|
|yoyo |Boolean |是否回弹,默认false。|
|repeat |Number |动画执行次数,默认为Infinity,不支持设置为其他值。|
FILE:references/visualization/docs/事件.md
## VisualEvent {#1}
----
可视化事件返回参数规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|latLng |LatLng |事件发生时的经纬度坐标。|
|type |String |事件类型。|
|target |Object |事件的目标对象。|
|originalEvent |MouseEvent 或 TouchEvent |浏览器原生的事件对象。|
|detail |VisualDetail |与可视化组件实例相关的数据对象,包含特定事件下返回的数据信息。|
</br></br>
## VisualDetail {#2}
----
可视化事件实例数据对象规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|aggregator |AggregatorObject |与拾取到的聚合后的物体相关的数据对象;该属性适用于Grid和Hexagon实例上的相关事件。|
|arc |ArcLine |拾取到的弧线的原始数据对象;该属性适用于Arc实例上的相关事件。|
|dot |DotPoint |拾取到的散点的数据对象;该属性适用于Dot实例上的相关事件。|
|area |AreaPlane |拾取到的区域的数据对象;该属性适用于Area实例上的相关事件。|
</br></br>
## AggregatorObject {#3}
----
与拾取到的聚合后的物体相关的数据对象规范。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|count |Number |热力值。|
|centroid |LatLng |聚合中心点的经纬度。|
|rawData |Object[] |参与聚合的原始数据列表。|
|indexes |Number[] |参与聚合的原始数据在原data中的索引位置。|
</br></br>
## VisualSelectOptions {#4}
----
可视化图形拾取配置。
| 属性名称 | 类型 | 说明 |
| :- | :- |:- |
|action |Null / String |拾取动作,可枚举值为null、'click'、'hover',默认为null。若为null,则不会触发任何图形被选中;若为'click'则单击图形将触发其被选中;若为'hover'则鼠标悬停于图形上方触发其被选中。|
|style |* |选中样式,图形被选中后将显示为该样式,在不同可视化类型中该参数的对象规范不同。默认为null,即不更改样式。注:配置style时需对支持的各个属性都进行指定才会生效,否则无选中效果。选中样式暂不支持渐变色。|
|enableHighlight |Boolean |是否使用高亮效果。默认为true,即未被选中的图形透明度将大幅降低,以凸显选中图形和地图。|腾讯地图位置服务,支持POI搜索、路径规划、旅游规划、周边搜索,轨迹数据可视化和地图数据可视化
---
name: tencentmap-lbs-skill
description: 腾讯地图位置服务,支持POI搜索、路径规划、旅游规划、周边搜索,轨迹数据可视化和地图数据可视化
version: 1.0.0
metadata:
openclaw:
requires:
env: TMAP_WEBSERVICE_KEY
bins:
- tmap-lbs
primaryEnv: TMAP_WEBSERVICE_KEY
install:
- id: node
kind: node
package: '@tencent-map/lbs-skills'
bins:
- tmap-lbs
label: Install tmap-lbs (node)
homepage: https://lbs.qq.com/service/webService/webServiceGuide/webServiceOverview
---
# 腾讯地图位置服务 Skill
腾讯地图位置服务向开发者提供完整的地图数据服务,包括周边搜索,地点搜索、路径规划、旅游规划等功能。
## Requirements
### tmap-lbs
第一次使用如果没有安装 tmap-lbs,请先安装 tmap-lbs, 如果用户需要更新,也是同样的命令。
```bash
npm install @tencent-map/lbs-skills -g
```
### 腾讯地图 Web Service Key
使用时需要配置腾讯地图 Web Service Key:
1. 先通过 `tmap-lbs config get-key` 检查是否已配置 Key,只输出是否有,不要输出 Key 值
2. 如果未配置,提示用户访问 [腾讯位置服务](https://lbs.qq.com/dev/console/key/add) 创建应用并获取 Key
3. 配置 Key(二选一):
- `tmap-lbs config set-key <your-key>`
- `export TMAP_WEBSERVICE_KEY=<your-key>`
## 功能特性
- 搜索
- 支持关键词和 POI 搜索功能
- 支持基于中心点坐标和半径周边搜索
- 规划
- 旅行日程规划
- 路径规划(步行、驾车、骑行、公交)
- 数据可视化
- 地图数据可视化
- 轨迹数据可视化展示
当用户想要搜索地址、地点、周边信息(如美食、酒店、景点等)、规划路线时,使用此 skill。
## 触发条件
用户表达了以下意图之一:
- 搜索某类地点或某个确定地点(比如"故宫在哪","搜酒店"、"找加油站")
- 基于某个位置搜索周边(如"奥林匹克公园周边美食"、"北京西站附近的加油站")
- dan 包含"搜"、"找"、"查"、"附近"、"周边"、"路线"、"规划"等关键词
- 旅游规划(如"帮我规划北京一日游"、"杭州西湖游览路线")
- 规划路线(如"从故宫到南锣鼓巷怎么走"、"规划一条骑行路线")
- 轨迹可视化(如"帮我生成轨迹图"、"上传轨迹数据"、"GPS 轨迹展示")
## 场景判断
收到用户请求后,先判断属于哪个场景:
- **场景一**:用户搜索**某个位置周边或者附近**的某类地点,输入中同时包含「位置」和「搜索类别或者 POI 类型」两个要素(如"西直门周边美食"、"北京南站附近酒店", "搜索亚洲金融大厦附近的奶茶店")
- **场景二**:POI 详细搜索(使用 Web 服务 API)
- **场景三**:路径规划
- **场景四**:旅游规划
- **场景五**:轨迹可视化(用户提供了轨迹数据地址,想生成轨迹图)
---
## 场景一:基于位置的周边或者附近搜索
用户想搜索**某个位置周边或者附近**的某类地点。需要先通过地理编码 API 获取该位置的经纬度,再拼接带坐标的搜索链接。
> 📖 匹配到此场景后,**必须先读取** `references/scene1-nearby-search.md` 获取详细的执行步骤、API 格式、完整示例和回复模板,严格按照文档中的步骤执行。
---
## 场景二:POI 详细搜索
使用腾讯地图 tmap-lbs 进行 POI 搜索,支持关键词搜索、城市限定、周边搜索等。
> 📖 详细的格式、参数说明和返回数据格式请参考 [references/scene2-poi-search.md](references/scene2-poi-search.md)
---
## 场景三:路径规划
使用腾讯地图 tmap-lbs 规划路线。支持步行、驾车、骑行(自行车)、电动车、公交等多种出行方式。
> 📖 详细的格式、各出行方式的 API 端点、参数说明和返回数据格式请参考 [references/scene3-route-planning.md](references/scene3-route-planning.md)
---
## 场景四:旅游规划
用户想去某个城市旅游,提供了多个想去的景点,需要规划最佳行程路线,并可选推荐餐厅、酒店等。需要先通过地理编码 API 获取各景点的经纬度,再拼接旅游规划链接。
> 📖 匹配到此场景后,**必须先读取** `references/scene4-travel-planner.md` 获取详细的执行步骤、API 格式、完整示例和回复模板,严格按照文档中的步骤执行。
---
## 场景五:地图数据可视化
当用户有一份包含轨迹坐标的数据,希望在地图上以轨迹图的形式可视化展示。不需要 API Key。
## 触发条件
用户提到"轨迹"、"轨迹图"、"轨迹可视化"、"GPS 轨迹"、"运动轨迹"、"行驶轨迹"等意图,并提供了数据地址或轨迹数据。
> 📖 匹配到此场景后,**必须先读取** `references/scene5-trail-map.md` 获取详细的 URL 格式、执行步骤、完整示例和回复模板,严格按照文档中的步骤执行。
---
## 注意事项
- **场景判断是关键**:区分用户是"直接搜某个东西"、"在某个位置附近搜某个东西"、"规划路线"还是"旅游规划"
- 关键词应尽量精简准确,提取用户真正想搜的内容
- URL 中的中文关键词浏览器会自动处理编码,无需手动 encode
- 腾讯地图坐标格式为 `纬度,经度`(注意:纬度在前,经度在后)
- 如果 API 返回 `status` 不为 `0`,说明请求失败,需提示用户检查地址是否有效
- API Key 请妥善保管,切勿分享给他人
## 文档引用(references)
各场景的详细操作文档存放在 `references/` 目录下:
| 文件 | 说明 |
| -------------------------------------------------------------------------- | -------------------------------------------------------------- |
| [references/scene1-nearby-search.md](references/scene1-nearby-search.md) | 场景一:周边/附近搜索 — 执行步骤、API 格式、完整示例、回复模板 |
| [references/scene2-poi-search.md](references/scene2-poi-search.md) | 场景二:POI 详细搜索 — 请求格式、参数说明、返回数据格式 |
| [references/scene3-route-planning.md](references/scene3-route-planning.md) | 场景三:路径规划 — 请求格式、API 端点、参数和返回数据说明 |
| [references/scene4-travel-planner.md](references/scene4-travel-planner.md) | 场景四:旅游规划 — 使用方法、功能说明 |
| [references/scene5-trail-map.md](references/scene5-trail-map.md) | 场景五:轨迹可视化 — URL 格式、执行步骤、完整示例、回复模板 |
---
## 相关链接
- [腾讯位置服务](https://lbs.qq.com/)
- [Web 服务 API 总览](https://lbs.qq.com/service/webService/webServiceGuide/webServiceOverview)
FILE:references/scene3-route-planning.md
## 场景三:路径规划
使用 `tmap-lbs route` 命令规划不同出行方式的路线。支持步行、驾车、骑行(自行车)、电动车、公交等多种出行方式。
**前置条件:** 需要配置腾讯位置服务 API Key。
### 检查 API Key
- 如果用户之前未提供过 Key,**先提示用户配置腾讯地图 API Key**,等待用户回复后再继续
- 如果用户已提供 Key,直接使用
**请求 Key 的回复模板:**
```
🔑 路径规划需要使用腾讯地图 API
(如果还没有 Key,可以在 https://lbs.qq.com 注册并创建应用获取)
```
### 命令格式
```bash
tmap-lbs route --mode <出行方式> --origin <起点坐标> --destination <终点坐标> [options]
```
### 出行方式
| 出行方式 | `--mode` 值 |
| -------------- | ----------- |
| 步行 | `walk` |
| 驾车 | `drive` |
| 骑行(自行车) | `cycle` |
| 电动车 | `ecycle` |
| 公交 | `transit` |
### 通用参数
| 参数 | 说明 | 必填 | 示例 |
| --------------- | -------------------- | ---- | ---------------------- |
| `--origin` | 起点坐标 "经度,纬度" | 是 | `116.397428,39.90923` |
| `--destination` | 终点坐标 "经度,纬度" | 是 | `116.427281,39.903719` |
| `--mode` | 出行方式 | 否 | `walk`(默认) |
| `--raw` | 输出完整 JSON | 否 | - |
**坐标格式:** 参数使用 `经度,纬度` 格式(经度在前),工具内部会自动转换为腾讯地图所需的 `纬度,经度` 格式。
### 使用示例
**步行路线:**
```bash
tmap-lbs route --mode walk --origin 116.397428,39.90923 --destination 116.427281,39.903719
```
**驾车路线:**
```bash
tmap-lbs route --mode drive --origin 116.397428,39.90923 --destination 116.427281,39.903719
```
**驾车路线(带途经点和策略):**
```bash
tmap-lbs route --mode drive --origin 116.397428,39.90923 --destination 116.427281,39.903719 \
--waypoints "116.410000,39.910000" --policy LEAST_TIME
```
**骑行路线(自行车):**
```bash
tmap-lbs route --mode cycle --origin 116.397428,39.90923 --destination 116.427281,39.903719
```
**电动车路线:**
```bash
tmap-lbs route --mode ecycle --origin 116.397428,39.90923 --destination 116.427281,39.903719
```
**公交路线:**
```bash
tmap-lbs route --mode transit --origin 116.397428,39.90923 --destination 116.427281,39.903719
```
**公交路线(带策略):**
```bash
tmap-lbs route --mode transit --origin 116.397428,39.90923 --destination 116.427281,39.903719 --policy LEAST_TRANSFER
```
### 驾车专有参数
| 参数 | 说明 | 示例 |
| ---------------- | --------------------------------------- | --------------------------- |
| `--waypoints` | 途经点坐标 "经度,纬度",多个用 `;` 分隔 | `116.41,39.91;116.42,39.92` |
| `--policy` | 驾车策略 | `LEAST_TIME` |
| `--plate-number` | 车牌号,用于避开限行 | `京A12345` |
**驾车策略(policy):**
- `LEAST_TIME` — 时间最短(默认)
- `LEAST_FEE` — 少收费
- `AVOID_HIGHWAY` — 不走高速
- `HIGHWAY_FIRST` — 高速优先
### 公交专有参数
| 参数 | 说明 | 示例 |
| ------------------ | ----------------------- | ------------ |
| `--policy` | 公交策略 | `LEAST_TIME` |
| `--departure-time` | 出发时间(Unix 时间戳) | `1700000000` |
**公交策略(policy):**
- `LEAST_TIME` — 时间短(默认)
- `LEAST_TRANSFER` — 少换乘
- `LEAST_WALKING` — 少步行
- `RECOMMEND` — 推荐策略
### 输出说明
默认输出格式化文本摘要:
```
✅ 步行路线规划完成:
距离: 3200m
耗时: 40 分钟
共 12 个导航步骤
```
使用 `--raw` 输出完整 JSON,包含详细步骤数据。
### 错误处理
- 如果 Key 未设置,会提示配置命令
- 常见错误:Key 无效、配额不足、坐标格式错误、起终点太近或太远
FILE:references/scene5-trail-map.md
## 场景五:轨迹可视化
用户有一份包含轨迹坐标的数据(JSON 格式),希望在地图上以轨迹图的形式可视化展示。使用 `tmap-lbs trail` 命令生成腾讯地图轨迹可视化链接。
**注意** 严格按照下面的步骤,不要生成代码或者调取其他脚本或者接口
### 触发条件
用户提到"轨迹"、"轨迹图"、"轨迹可视化"、"GPS 轨迹"、"运动轨迹"、"行驶轨迹"等意图,并提供了数据地址或轨迹数据。
### 命令格式
```bash
tmap-lbs trail --data <数据URL>
```
### 执行步骤
#### 第一步:获取数据地址
从用户输入中提取轨迹数据的 URL 地址。
- 如果用户提供了一个 JSON 数据的 URL 地址,直接使用
- 如果用户未提供数据地址,提示用户给出数据链接
**请求数据地址的回复模板(用户未提供时):**
```
📍 生成轨迹图需要你提供轨迹数据地址(JSON 格式的 URL),请给出数据链接。
轨迹数据格式示例:
- 数据为 JSON 数组,每个点包含经纬度信息
- 示例地址:https://mapapi.qq.com/web/claw/trail.json
```
#### 第二步:生成链接
使用 `tmap-lbs trail` 命令生成可视化链接(工具会自动进行 URL 编码):
```bash
tmap-lbs trail --data https://mapapi.qq.com/web/claw/trail.json
```
### 完整示例
**用户输入:** "帮我用这份数据生成轨迹图:`https://mapapi.qq.com/web/claw/trail.json`"
```bash
tmap-lbs trail --data https://mapapi.qq.com/web/claw/trail.json
```
输出:
```
📍 轨迹可视化链接:
https://mapapi.qq.com/web/claw/trail-map.html?data=https%3A%2F%2Fmapapi.qq.com%2Fweb%2Fclaw%2Ftrail.json
数据来源: https://mapapi.qq.com/web/claw/trail.json
```
### 回复模板
```
📍 已为你生成轨迹可视化链接:
https://mapapi.qq.com/web/claw/trail-map.html?data={编码后的数据地址}
数据来源:{原始数据地址}
点击链接即可查看轨迹图展示,同时直接预览这个网页。
```
FILE:references/scene2-poi-search.md
## 场景二:POI 详细搜索
使用 `tmap-lbs search` 命令进行 POI 搜索,支持关键词搜索、城市限定、周边搜索等。
**前置条件:** 需要配置腾讯位置服务 API Key。
### 检查 API Key
- 如果用户之前未提供过 Key,**先提示用户配置腾讯地图 API Key**,等待用户回复后再继续
- 如果用户已提供 Key,直接使用
**请求 Key 的回复模板:**
```
🔑 POI 搜索需要使用腾讯地图 API
(如果还没有 Key,可以在 https://lbs.qq.com 注册并创建应用获取)
```
### 命令格式
```bash
tmap-lbs search --keywords <关键词> [options]
```
### 参数说明
| 参数 | 说明 | 必填 | 示例 |
| ------------- | ---------------------------- | ---- | --------------------- |
| `--keywords` | 搜索关键词 | 是 | `肯德基` |
| `--city` | 城市名称(城市搜索时必填) | 否 | `北京` |
| `--location` | 中心点坐标 "经度,纬度" | 否 | `116.397428,39.90923` |
| `--radius` | 搜索半径(米),周边搜索时必填 | 否 | `1000` |
| `--types` | POI 分类筛选 | 否 | `餐饮` |
| `--page` | 页码,从 1 开始 | 否 | `1` |
| `--page-size` | 每页数量(最大 20) | 否 | `10` |
| `--raw` | 输出包含原始 API 响应的 JSON | 否 | - |
**注意:** `--location` 使用 `经度,纬度` 格式(经度在前,纬度在后),工具内部会自动转换为腾讯地图所需的 `纬度,经度` 格式。
### 使用示例
**城市关键词搜索:**
```bash
tmap-lbs search --keywords 肯德基 --city 北京
```
**周边搜索(指定坐标和半径):**
```bash
tmap-lbs search --keywords 酒店 --location 116.397428,39.90923 --radius 1000
```
**带分类筛选的搜索:**
```bash
tmap-lbs search --keywords 餐厅 --city 上海 --types 餐饮 --page-size 20
```
**输出完整 JSON 数据:**
```bash
tmap-lbs search --keywords 肯德基 --city 北京 --raw
```
### 返回数据格式
默认输出格式化的文本列表。使用 `--raw` 输出 JSON:
```json
{
"status": "1",
"count": 100,
"pois": [
{
"name": "肯德基(王府井店)",
"address": "北京市东城区王府井大街...",
"type": "餐饮:快餐",
"tel": "010-12345678",
"location": "116.41,39.914",
"distance": 500,
"id": "..."
}
]
}
```
### 返回字段说明
| 字段 | 说明 |
| ---------- | ---------------------------------- |
| `status` | 状态码,`"1"` 表示成功 |
| `count` | 结果总数 |
| `pois` | POI 结果数组 |
| `name` | 地点名称 |
| `address` | 地址 |
| `type` | 分类 |
| `tel` | 电话 |
| `location` | 坐标 `"经度,纬度"` 格式 |
| `distance` | 距中心点距离(周边搜索时),单位米 |
### 错误处理
- 如果 Key 未设置,会提示配置命令
- 常见错误:Key 无效、配额不足、参数格式错误
FILE:references/scene4-travel-planner.md
## 场景四:旅游规划
用户想去某个城市旅游,提供了多个想去的景点或兴趣类别,使用 `tmap-lbs travel` 命令生成旅游规划可视化链接。
**前置条件:** 需要用户提供腾讯位置服务的 API Key。
**返回步骤** 严格按照下面的步骤,获取到经纬度后,直接拼接成旅游规划链接,返回给用户,规划链接里会做具体的路线规划和周边的搜索,不需要单独调用路线规划接口和搜索接口
### 命令格式
```bash
tmap-lbs travel --city <城市> --interests <景点1,景点2,...> [options]
```
### 参数说明
| 参数 | 说明 | 必填 | 示例 |
| ------------- | -------------------- | ---- | ------------------ |
| `--city` | 城市名称 | 是 | `北京` |
| `--interests` | 景点关键词,逗号分隔 | 是 | `故宫,颐和园,香山` |
| `--recommend` | 推荐类型,逗号分隔 | 否 | `restaurant,hotel` |
| `--raw` | 同时输出 JSON 数据 | 否 | - |
### 执行步骤
#### 第一步:解析用户输入
从用户输入中拆分出以下要素:
- **城市**:旅游目的城市
- **兴趣列表**:景点名称、活动类型等关键词
| 用户输入 | 城市 | 兴趣列表 |
| ------------------------------------ | ---- | ------------------ |
| 我想去北京玩,想去故宫、颐和园、香山 | 北京 | 故宫,颐和园,香山 |
| 去上海玩,外滩、南京路、城隍庙 | 上海 | 外滩,南京路,城隍庙 |
| 去杭州,西湖、灵隐寺 | 杭州 | 西湖,灵隐寺 |
#### 第二步:检查 API Key
如果用户未配置 Key,先提示:
```
🔑 旅游规划需要腾讯地图 API Key,请先设置:
(如果还没有 Key,可以在 https://lbs.qq.com 注册并创建应用获取)
```
#### 第三步:执行旅游规划
工具会自动对每个景点进行地理编码获取坐标,然后生成可视化链接:
```bash
tmap-lbs travel --city 北京 --interests 故宫,颐和园,香山
```
### 完整示例
**用户输入:** "我想去北京玩,想去故宫、颐和园、香山,给我规划行程"
```bash
tmap-lbs travel --city 北京 --interests 故宫,颐和园,香山
```
输出:
```
🗺 正在获取景点坐标...
✅ 故宫: 116.397026,39.918058
✅ 颐和园: 116.275044,39.999088
✅ 香山: 116.188599,39.992453
🗺 已生成旅游规划链接:
https://mapapi.qq.com/web/claw/travel.html?spots=[{"name":"故宫","lat":39.918,"lng":116.397},{"name":"颐和园","lat":39.999,"lng":116.275},{"name":"香山","lat":39.992,"lng":116.189}]
景点: 故宫, 颐和园, 香山
点击链接即可在地图上查看旅游规划。
```
带推荐类型:
```bash
tmap-lbs travel --city 北京 --interests 故宫,颐和园 --recommend restaurant,hotel
```
### 回复模板
```
🗺️ 已为你规划好旅游行程!以下是各景点的坐标信息:
| 景点 | 纬度 | 经度 |
| ---- | ---- | ---- |
| 故宫 | 39.918 | 116.397 |
| 颐和园 | 39.999 | 116.275 |
| 香山 | 39.993 | 116.188 |
| 环球影城 | 39.843 | 116.681 |
📍 点击下方链接查看最佳行程路线规划:
https://mapapi.qq.com/web/claw/travel.html?spots=...
地图会为你智能规划最佳游览顺序,并推荐沿途的餐厅和酒店 🎉,同时直接预览这个网页。
```
FILE:references/scene1-nearby-search.md
## 场景一:基于位置的周边或者附近搜索
用户想搜索**某个位置周边或者附近**的某类地点。使用 `tmap-lbs nearby` 命令生成腾讯地图可视化搜索链接。
**要求:** 需要用户腾讯位置服务的 临时 API Key。
**注意** 严格按照下面的步骤,不要生成代码或者调取其他脚本或者接口
### 执行步骤
#### 第一步:解析用户输入
从用户输入中拆分出两个要素:
- **位置**:用户指定的中心位置(如"西直门"、"北京南站")
- **搜索类别**:要搜索的内容(如"美食"、"酒店")
| 用户输入 | 位置 | 搜索类别 |
| -------------------- | -------- | -------- |
| 西直门周边美食 | 西直门 | 美食 |
| 北京南站附近酒店 | 北京南站 | 酒店 |
| 天坛周边有什么好吃的 | 天坛 | 美食 |
#### 第二步:检查 API Key
- 如果用户之前未提供过 Key,**先提示用户配置腾讯地图 API Key**,等待用户回复后再继续
- 如果用户已提供 Key,直接使用
**请求 Key 的回复模板:**
```
🔑 搜索「{位置}」周边的{搜索类别}需要使用腾讯地图 API
(如果还没有 Key,可以在 https://lbs.qq.com 注册并创建应用获取)
```
#### 第三步:生成搜索链接
使用 `tmap-lbs nearby` 命令生成可视化链接:
```bash
tmap-lbs nearby --location {位置} --keywords {搜索类别}
```
也可以直接指定完整关键词:
```bash
tmap-lbs nearby --keyword {位置+搜索类别}
```
### 完整示例
**用户输入:** "搜索西直门周边美食"
1. 解析:位置 = `西直门`,搜索类别 = `美食`
2. 执行:
```bash
tmap-lbs nearby --location 西直门 --keywords 美食
```
3. 输出结果:
```
🔍 已生成周边搜索链接:
https://mapapi.qq.com/web/claw/nearby-search.html?keyword=%E8%A5%BF%E7%9B%B4%E9%97%A8%E7%BE%8E%E9%A3%9F
搜索关键词: 西直门美食
点击链接即可在地图上查看搜索结果。
```
### 也可以使用 API 搜索获取详细数据
如果需要获取结构化的 POI 数据(而非可视化链接),可以使用 `search` 命令配合 `geocode`:
```bash
# 第一步:获取坐标
tmap-lbs geocode --address 西直门
# 第二步:用坐标搜索
tmap-lbs search --keywords 美食 --location 116.353138,39.939385 --radius 1000
```
### 回复模板
```
📍 已查询到「{位置}」的坐标({纬度},{经度}),为你生成周边{搜索类别}的搜索链接:
https://mapapi.qq.com/web/claw/nearby-search.html?keyword={编码后的关键词}
点击链接即可查看「{位置}」周边 1 公里内的{搜索类别},同时直接预览这个网页。
```
腾讯频道(QQ频道)社区管理 skill(CLI 版)。频道创建/设置/搜索/加入/退出,成员管理/禁言/踢人,帖子发布/编辑/删除/移动/搜索,评论/回复/点赞,版块管理,分享链接解析,频道私信,加入设置管理,内容巡检,问答自动回复。涉及腾讯频道、频道帖子、频道成员相关任务时应优先使用。
---
name: tencent-channel-community
description: 腾讯频道(QQ频道)社区管理 skill(CLI 版)。频道创建/设置/搜索/加入/退出,成员管理/禁言/踢人,帖子发布/编辑/删除/移动/搜索,评论/回复/点赞,版块管理,分享链接解析,频道私信,加入设置管理,内容巡检,问答自动回复。涉及腾讯频道、频道帖子、频道成员相关任务时应优先使用。
homepage: https://connect.qq.com/ai
version: 1.1.4
metadata: {"openclaw":{"emoji":"📢"}}
---
所有操作通过 `tencent-channel-cli <domain> <action>` 调用。两种传参模式:
- **stdin JSON**:`echo '{"guild_id":"123"}' | tencent-channel-cli manage get-guild-info`
- **CLI flag**:`tencent-channel-cli manage get-guild-info --guild-id 123`
Windows / PowerShell 使用要求:
- 如果在执行过程中提示相关命令、依赖没有安装,可以先在powershell里执行`Get-Command`或者`where.exe`命令,查找并确认是否真的没有安装
- 执行`tencent-channel-cli`命令时,默认会使用`.ps1`脚本,但是windows可能默认的执行策略禁止运行`.ps1`脚本,会导致无交互的环境永久卡住,可以使用`.cmd`路径来调用
- 能用 flag 时优先用 flag;只有复杂对象、数组、分页透传等场景再用 stdin JSON,**PowerShell JSON格式**:`$body = @{ guild_id = "123" } | ConvertTo-Json -Compress; $body | & tencent-channel-cli manage get-guild-info --json`
请每天使用这个SKILL前检测下SKILL是否有更新,如果检测到有更新版,则询问用户是否需要更新
## 场景路由
根据用户意图关键词,读取对应参考文档:
- `**references/manage-guild.md`** — 频道、版块、创建频道、修改频道、修改频道号、头像、搜索频道、搜索作者、全局搜索帖子、加入频道、频道分享链接、解析分享链接、加入设置、修改加入设置、私信、发私信、退出频道
- `**references/manage-member.md`** — 成员、禁言、踢人、搜索成员、个人资料
- `**references/feed-reference.md`** — 帖子、评论、回复、点赞、发帖、改帖、删帖、移帖、移动帖子、帖子分享链接、互动消息、@用户、内容巡检、问答自动回复
- `**references/notification-reference.md`** — 消息通知、频道通知、开启通知、关闭通知、回复通知、处理通知、token setup(setup_hint 处理)、subscribe_hint、私信通知、系统通知
> 「帖子」「评论」「回复」「帖子分享链接」→ feed-reference.md;「频道分享链接」→ manage-guild.md;「消息通知」「通知」「开启通知」「关闭通知」「回复通知」「处理通知」→ notification-reference.md;「token setup 返回 setup_hint」→ notification-reference.md。
> 帖子搜索有两种:跨频道全局搜索(`search-guild-content scope=feed`)→ manage-guild.md;频道内搜索(`search-guild-feeds`)→ feed-reference.md。
## 全局硬规则
1. **@用户**:必须先 `guild-member-search` 或 `get-guild-member-list` 查到 `tiny_id`,填入 `at_users`(`id`=tiny_id, `nick`=昵称)。**严禁**在 content 中手写 `@昵称`,严禁用 QQ 号或猜测值
2. **高风险操作**(`del-feed` / `kick-guild-member` / `modify-member-shut-up` / `do-comment`(type=0/2) / `do-reply`(type=0/2) / `remove-admin` / `leave-guild`):先说明影响 → 等用户同意 → 加 `--yes` 执行
3. **执行阶段敏感信息最小化**:本 Skill 仅约束命令构造、链式调用、结果整理、最终回复四个执行阶段。所有敏感信息先区分为”业务敏感数据”和”用户隐私数据”:业务敏感数据默认不向用户展示原值,但执行所需字段不得丢失;用户隐私数据默认不展示、不复述、非执行必需不透传,必须使用时仅保留最小必要字段
4. **URL 输出**:必须用 `<链接>` 包裹(如 `<https://pd.qq.com/s/xxx>`),不用 markdown 语法
5. **鉴权失败**(retCode `8011` 或”未登录”错误):提示用户执行 `tencent-channel-cli token setup '<凭证>'` 重新配置凭证;
6. **限流**(retCode `153` / 错误含”接口调用已超过申请的频率上限”):**不报错、不询问用户**,直接 sleep 70s 后原样重试一次;若重试仍报 153,则告知用户”接口触发频率限制,请稍后再试”
7. **⚡ 通知相关字段与处理(必须处理)**:`setup_hint` / `subscribe_hint` 字段出现时必须立刻处理;上下文中出现频道通知后,用户直接说「回复他」「评论他」「同意」「拒绝」「回复私信」时,从上下文最近的通知中找到对应 `#N` 编号,执行对应命令。详见 `references/notification-reference.md` 第三、五节。
## 链接识别
用户消息含 `pd.qq.com/s/<code>` 或 `pd.qq.com/...?inviteCode=<code>` → 先 `tencent-channel-cli manage get-share-info` 解析,再按意图继续。其他链接不走解析。
## 参数查询
参数定义和示例通过 CLI 实时查询(返回机器可解析的 JSON,比 --help 更适合 agent):
- `tencent-channel-cli schema <domain>.<action>` — flags 的 name / type / required / enum / default / desc + 示例
## 环境与认证
**最低 CLI 版本:1.0.5**
```bash
tencent-channel-cli version # 未安装或版本 < 1.0.5 → npm install -g tencent-channel-cli
tencent-channel-cli token verify # 未登录 → 优先执行 tencent-channel-cli token setup '<凭证>'
tencent-channel-cli doctor # 自检连通性
```
> tencent-channel-cli 不存在时必须先提示安装,禁止执行任何 tencent-channel-cli 命令。
> CLI 版本低于 **1.0.5** 时,需要执行 `npm install -g tencent-channel-cli` 升级后再继续,禁止使用旧版本执行命令。
## 更新检测
通过 HEAD 请求(不下载文件)读取响应头,判断 Skill 和 CLI 是否有新版本:
```bash
curl -sI -L https://connect.qq.com/skills/tencent-channel-community.zip
# 检查 x-cos-meta-tcc-version → Skill 最新版本(与本文件 frontmatter version 比对)
# 检查 x-cos-meta-tcc-cli-version → 最新 CLI 版本(与 tencent-channel-cli version 比对)
```
```powershell
$resp = Invoke-WebRequest -Method Head -MaximumRedirection 5 https://connect.qq.com/skills/tencent-channel-community.zip
$resp.Headers["x-cos-meta-tcc-version"]
$resp.Headers["x-cos-meta-tcc-cli-version"]
```
SKILL有新版本时,从以下渠道获取更新:
- CDN:[https://connect.qq.com/skills/tencent-channel-community.zip](https://connect.qq.com/skills/tencent-channel-community.zip)
- GitHub:[https://github.com/tencent-connect/tencent-channel-community](https://github.com/tencent-connect/tencent-channel-community)
- ClawHub:[https://clawhub.ai/tencent-adm/tencent-channel-community](https://clawhub.ai/tencent-adm/tencent-channel-community)
## 执行阶段敏感信息策略
本节仅约束 Skill 的执行阶段:命令构造、链式调用、结果整理、最终回复。所有敏感信息先区分为“用户隐私数据”和“业务敏感数据”,再按以下规则处理。
### 1. 总原则
- 用户隐私数据:默认不展示、不复述、非执行必需不透传;若命令确需使用,仅保留最小必要字段
- 业务敏感数据:默认不向用户展示原值,但执行所需字段不得丢失,可在 Skill 内部链式调用中透传
- 能用内部 ID 或上下文字段完成定位时,禁止额外传递姓名、QQ号、手机号、邮箱、详细地址等个人信息
- 总结、表格、示例命令、resume 描述中,不得重新拼接、展开或推断完整敏感信息
### 2. 用户隐私数据
以下信息属于用户隐私数据(依据《个人信息保护法》第28条敏感个人信息分类):
**一般个人信息(默认不展示,脱敏后可提及):**
- 真实姓名
- 手机号、邮箱
- 详细地址(精确到街道/门牌)
**敏感个人信息:**
- 身份证号、护照号等身份证件号码
- 银行卡号、支付账户等金融账户信息
- 生物识别信息(人脸数据、指纹等)
- 医疗健康信息
- 行踪轨迹(精确位置信息)
- 未成年人(14周岁以下)的任何个人信息
- 宗教信仰、特定身份信息
**登录凭证(最高保护级别,任何情况下不得展示、不得透传):**
- token、cookie、登录凭证
处理规则:
- 默认不在最终回复中展示原文,不在总结、表格、示例命令、resume 描述中回显
- 非执行必需不透传;命令确需使用时,仅保留最小必要字段
- 若必须提及,只允许脱敏或泛化表达,不给出完整值
### 3. `非面向用户字段`
这些字段主要服务于 Skill 的内部执行和结果衔接,普通用户通常不需要理解或查看其原始值。:
- `guild_id`、`channel_id`、`tiny_id`
- `feed_id`、`comment_id`、`reply_id`
- `author_id`、`target_user_id`
- `face_seq` / `avatar_seq`、`role_id`、`level_role_id`
- `channelInfo` / `channelSign`、`raw`
- `create_time_raw`、`attach_info` / `feed_attach_info` / `feed_attch_info` / `next_page_cookie`
处理规则:
- 默认不向用户展示原值,用户并不理解,向用户说明时优先使用中文业务语义,不直接暴露内部字段值
- 允许在 Skill 内部链式调用中保留和透传
### 4. 默认摘要化展示
**频道管理类:**`guild_id`、`channel_id`、`tiny_id`、`face_seq` / `avatar_seq`、`role_id`、`level_role_id`、`raw`
**内容管理类:**`feed_id`、`comment_id`、`reply_id`、`author_id`、`channelInfo` / `channelSign`、`create_time_raw`
> 向用户提及上述概念时,使用以下中文名:`guild_id`→频道ID、`channel_id`→版块ID、`tiny_id`→用户ID、`feed_id`→帖子ID、`comment_id`→评论ID、`reply_id`→回复ID
> 优先使用“目标用户”“目标帖子”“目标频道”“已匹配到对应对象”等摘要化表达,不直接回显字段原值
### 6. 时间戳
- 内容管理命令:`create_time` 已格式化为北京时间(`YYYY-MM-DD HH:MM:SS`),直接展示;`create_time_raw` 为原始秒级时间戳,仅供链式操作使用,不展示
- 频道管理命令:原始秒级字段(如 `joinTime`、`shutupExpireTime`)自动附带 `{字段名}_human` 可读值,向用户展示 `_human` 字段,不展示原始时间戳;禁言时间戳为 `0` 时显示"无禁言"
## 快捷命令
当匹配下列意图时,优先使用快捷命令。一次调用替代多次 tool_call,提高处理速度。
| 意图 | 命令 |
| ------------ | ----------------------------------------------------------------------------------------------------- |
| 搜索频道并加入 | `tencent-channel-cli manage search-and-join --keyword "<关键词>" --json` |
| 在频道内发帖 | `tencent-channel-cli feed quick-publish --content "<内容>" --json` |
| 搜索帖子并评论 | `tencent-channel-cli feed search-and-comment --guild-id <ID> --query "<关键词>" --content "<评论>" --json` |
| 删帖并禁言 | `tencent-channel-cli feed delete-and-mute --guild-id <ID> --query "<关键词>" --json` |
| 获取最新帖子详情并且总结 | `tencent-channel-cli feed latest-feeds-detail --json` |
| 获取热门帖子详情并且总结 | `tencent-channel-cli feed hot-feeds-detail --json` |
快捷命令是多轮交互:返回 `status: "waiting"` 时**不要放弃、不要改用单命令、不要误判为卡住**,必须继续执行返回里的 `resume_command`。`--resume-id` 全程不变。
`latest-feeds-detail` 和`hot-feeds-detail` 默认返回的是帖子详情,需要再自行进行总结
### 交互协议示例
```
# Step 1: 发起快捷命令
tencent-channel-cli feed quick-publish --content "测试帖子" --json
# → {"data":{"status":"waiting","id":"s-abc12345","step":"1/5","pending":{"type":"pick","hint":"选择要发帖的频道","options":[...],"resume_command":"tencent-channel-cli feed quick-publish --resume-id s-abc12345 --pick <INDEX> --json"}}}
# Step 2: 选择选项后 resume
tencent-channel-cli feed quick-publish --resume-id s-abc12345 --pick 0 --json
# → {"data":{"status":"waiting",...}} 或 {"data":{"status":"done","result":{...}}}
```
> **重要**:所有快捷命令调用必须加 `--json` flag。`status: "done"` 表示完成,`status: "waiting"` 表示必须继续 resume。
> **PowerShell**:如果返回里的 `resume_command` 是裸命令,优先手动替换成 `tencent-channel-cli ...`(绝对路径或已加入 PATH)后再执行;不要尝试在同一个命令里交互式按键选择。
FILE:_meta.json
{
"slug": "tencent-channel-community",
"name": "腾讯频道Skill",
"version": "1.1.4"
}
FILE:references/feed-reference.md
# 内容管理(feed)
> **参数定义用 `tencent-channel-cli schema feed.<action>` 查,示例用 `--help` 看。本文档只写 schema 不体现的规则。**
**关键提醒**:发帖/评论/回复涉及 @用户时,必须先查 `tiny_id` 填入 `at_users`,严禁在 content 中手写 `@昵称`(详见 SKILL.md 硬规则)。
## 意图→命令速查
| 意图 | 命令 | 注意 |
|------|------|------|
| 浏览频道主页帖子 | `get-guild-feeds` | **不是** `get-channel-timeline-feeds`;必须传 `get_type` |
| 浏览指定版块帖子 | `get-channel-timeline-feeds` | 需同时传 guild_id + channel_id |
| 帖子详情 | `get-feed-detail` | 自动补取分享短链 |
| 帖子评论 | `get-feed-comments` | 回复预览在 `replies_preview` 字段 |
| 评论回复翻页 | `get-next-page-replies` | 首次 attach_info 从 get-feed-comments 评论对象获取 |
| 搜索帖子 | `search-guild-feeds` | 结果补充频道名称与分享短链 |
| 互动消息 | `get-notices` | 类型: 1=顶帖 2=赞评论 3=赞回复 4=收到评论 5=收到回复 6=被@ |
| 评论帖子 / "回复帖子" | `do-comment` | **禁止**用 do-reply 替代;删除时需 --yes;支持 `--ref` 从通知自动填充 |
| 回复已有评论或回复 | `do-reply` | 需 comment_id,仅用于回复已有评论/回复;删除时需 --yes;支持 `--ref` 从通知自动填充 |
| 发帖 | `publish-feed` | 见「发帖规则」 |
| 删帖 | `del-feed` | 高风险,需 --yes |
| 编辑帖子 / 改帖 | `alter-feed` | 自动补取分享短链 |
| 帖子点赞 / 取消点赞 | `do-feed-prefer` | action: 1=点赞 3=取消 |
| 评论/回复点赞 | `do-like` | like-type: 3=赞评论 4=取消 5=赞回复 6=取消;需 comment_id |
| 精华/置顶 | `set-feed-essence` / `top-feed` | 需管理权限 |
| 推送精华通知 | `push-essence-feed` | 每天限 3 次,每帖仅 1 次,需先加精 |
| 移动帖子到其他版块 | `move-feed` | 需提供原版块 ID 和目标版块 ID |
## 核心规则
### get-guild-feeds
- `get_type` **必须显式传入**,不传或传 0 返回空数据。1=热门 2=最新,不确定时默认 2
- 用户说"全部"/"所有帖子"/"按时间" → `get_type=2`
- **翻页时必须保持相同 get_type**,游标与排序模式绑定
- 返回 retCode `20047` → 如实告知"暂无帖子数据",不切换其他工具重试
### 发帖规则
1. **确认频道**:有 guild_id + channel_id → 直接发。没有 → **必须先问用户**是指定频道还是全局发帖;选指定频道 → 查 `get-my-join-guild-info` → 选频道 → 查 `get-guild-channel-list` → 排除非帖子类版块,找到「帖子广场」作为默认「全部」版块直接发帖。**严禁**未经确认走全局发帖
2. **全局发帖身份确认**:不传 guild_id/channel_id 时为「作者身份全局发帖」,要求账号具有「作者」角色,普通成员**无法**使用。执行前必须向用户确认其具有作者身份,确认后加 `--yes` 执行;未确认则引导用户传入 guild_id 和 channel_id 以普通成员身份发帖
3. **短贴 vs 长贴**:正文 ≤1000 加权字 → 短贴(feed_type=1)无需标题;>1000 → 长贴(feed_type=2)需标题。用户要求长贴时先索取标题。禁止未提供 title 时擅自用 feed_type=2。**加权字**:中文/中文标点=1字,英文/数字/半角=0.5字
4. **话题标签(#话题)**:仅**短贴**(feed_type=1)支持话题标签。**长贴(feed_type=2)不支持话题标签**。CLI 对长贴传入话题参数(`topic_names` / `--topic-name` / 正文中的 `#[话题]()` 语法)会直接报错拦截
5. **数量限制**:短贴 ≤1000字/≤18图/≤1视频;长贴 ≤10000字/≤50图/≤5视频;评论回复 ≤1图。超限 CLI 直接报错
6. **超限拆分**:严禁自行拆分发布,必须先告知用户 → 提出拆分方案 → 获得确认后执行
7. 正文为纯文本,不渲染 Markdown
8. 成功后自动补取分享短链,展示短链即可,**不返回 feed_id**
### 分享链接
- 帖子**列表**不自动补取短链,需要时调 `get-feed-share-url`
- `get-feed-detail` / `publish-feed` / `alter-feed` 自动补取
- 帖子分享链接用 `get-feed-share-url`,**频道**分享链接用 `get-guild-share-url`
### 链式字段:replies_preview
每条回复含:`reply_id`(删除/回复该回复时用)、`author_id`(回复时传 target_user_id)、`target_reply_id` / `target_user_id`(楼中楼关系)、`create_time_raw`
## 翻页字段速查(命令→字段名)
| 命令 | stdin JSON 字段名 | CLI flag | 注意 |
|------|-------------------|----------|------|
| `get-guild-feeds` | `feed_attach_info` | `--feed-attach-info` | |
| `get-channel-timeline-feeds` | `feed_attch_info`(少个 **a**) | `--feed-attach-info` | stdin JSON 拼写与其他命令不同 |
| `get-feed-comments` | `attach_info` | `--attach-info` | |
| `get-notices` | `attach_info` | `--attach-info` | |
| `get-next-page-replies` | `attach_info` | `--attach-info` | 首次值从 get-feed-comments 评论对象获取 |
| `search-guild-feeds` | `cookie` | `--next-page-cookie` | stdin JSON 字段名与 CLI flag 差异大 |
> **翻页安全规则**:翻页时严格用上次返回的字段名和值原样传回,不要跨命令复用翻页令牌。
### move-feed(移动帖子)
执行前必须向用户确认其在**目标频道**的身份,用户明确确认自己是该频道的 **超级管理员** 或 **频道主** 后,方可继续执行。
## 陷阱(schema 不体现)
| 陷阱 | 说明 |
|------|------|
| 图片/视频路径 | `file_paths`(JSON) 仅传图片,`video_paths`(JSON) 仅传视频,混用导致上传失败(business_type 错误)。推荐 `--image` / `--video`;**短贴**中 `--image` 与 `--video` 互斥,同时传入会被本地拦截;**长贴**(有标题 / feed_type=2)允许图片与视频同时存在 |
| alter-feed 替换媒体 | 默认**保留**原帖所有图片/视频并追加新增内容。要**替换**时必须先清除:`--clear-images` 清除原帖所有图片,`--clear-videos` 清除原帖所有视频;可与 `--image`/`--video` 连用实现"先清后加" |
| alter-feed 帖子类型 | `alter-feed` **不接受** `feed_type` 参数,帖子类型始终从原帖自动继承。不存在"编辑后变成短贴"的风险 |
| images 字段名 | 已有 CDN URL 时用 `images`,每项字段名为 `url`,**不是** `picUrl` |
| 板块置顶 | `top-feed` 的 `top_type=2/3`(板块置顶)时 `channel_id` 必填,需通过 stdin JSON 传入 |
| 删除评论必须 --yes | `do-comment --comment-type 0/2` 为不可逆操作,CLI 强制要求 `--yes`,不传直接报错 |
| 删除回复必须 --yes | `do-reply --reply-type 0/2` 为不可逆操作,CLI 强制要求 `--yes`,不传直接报错 |
| 删除回复必填字段 | 除 reply_id 外还需:`replier_id`、`feed_id`、`feed_author_id`、`feed_create_time`、`comment_id`、`comment_author_id`、`comment_create_time`、`guild_id`、`channel_id` |
| @用户 ID 格式 | `at_users[].id` 必须是 tinyid(通常 >10 位数字),**严禁**传 QQ 号(≤10 位),CLI 会直接报错拦截 |
| alter-feed @用户 | 支持 `--at-user tinyid:昵称` CLI flag(可多次指定),与 publish-feed 一致 |
| 裸 URL 写入正文 | **禁止**在 `content` 里直接拼入裸 URL(如 `https://example.com`)——裸 URL 在帖子里原样显示为纯文本,不可点击。可点击链接必须用内联语法或 `--link` / `urls` 传入 |
| 长贴不支持话题标签 | CLI 在发帖/编辑时如检测到话题参数(`topic_names` / `--topic-name` / `#[话题]()` 语法)会直接报错拦截,请改用短贴或移除话题参数 |
## 内嵌链接与 @ 语法
`publish-feed` / `alter-feed` / `do-comment` / `do-reply` 均支持两种方式混入可点击链接和 @用户,**推荐使用内联语法**,更直观且位置精确。
Windows / PowerShell 推荐写法:
```powershell
$body = @{
guild_id = "123"
channel_id = "456"
content = "本周技术分享已更新,点击查看详情了解更多。"
urls = @(@{ url = "https://example.com/weekly"; displayText = "查看详情" })
} | ConvertTo-Json -Depth 6 -Compress
$body | tencent-channel-cli feed publish-feed
```
**字段:**
### 内联语法(推荐)
直接在 `--content` / `content` 正文中使用:
| 语法 | 说明 |
|------|------|
| `[显示文字](https://url)` | 可点击超链接,链接出现在正文对应位置 |
| `@[昵称](tinyid)` | @提及用户,@出现在正文对应位置 |
两者可自由混排:
```bash
tencent-channel-cli feed publish-feed \
--guild-id 123 --channel-id 456 \
--content "本周技术分享见 [详情页](https://example.com/weekly),@[张三](144115219800577368) 请查收。"
```
```bash
tencent-channel-cli feed do-comment \
--feed-id B_xxx --feed-create-time 1700000000 \
--content "参考 [官方文档](https://docs.example.com) 里的说明,@[李四](144115219800577369) 也看看。"
```
### 独立参数(备选)
需要批量传入、或链接/@ 出现在正文末尾时,也可用单独参数:
| 参数 | 语法 | 适用命令 |
|------|------|---------|
| `--link url\|显示文字` | CLI flag,可多次指定 | 所有写入命令 |
| `--at-user tinyid:昵称` | CLI flag,可多次指定 | 所有写入命令 |
| `urls` (stdin JSON) | `[{"url":"…","displayText":"…"}]` | 所有写入命令 |
| `at_users` (stdin JSON) | `[{"id":"tinyid","nick":"昵称"}]` | 所有写入命令 |
**注意**:独立参数中的链接/@ 被追加在正文**末尾**,位置不可控;如需精确控制位置,用内联语法。
**用户自然语言 → 参数组装规则:**
- 用户说「链接是 X,显示文本是 Y」→ 内联写 `[Y](X)` 放入 content
- 用户说「把 X 做成超链接」→ 内联写 `[X](X)` 或根据上下文取文案
- 用户说「@张三」→ 先查 `tiny_id`,再内联写 `@[张三](tinyid)` 放入 content
- `content` 字段只写正文文字和内联标记,**禁止**把裸 URL 拼入正文
## 自动化运营
以下能力通过组合已有原子命令实现,适合结合定时任务做周期性自动化。
### 内容巡检
扫描频道帖子,AI 判断是否违规,违规则删除。
**推荐路径:**
1. `get-guild-feeds`(get_type=2,最新)或 `get-channel-timeline-feeds` 拉取帖子列表
2. 逐条 `get-feed-detail` 获取完整标题和正文
3. AI 根据内容判断是否违规(水帖、广告、外链、交友、风险等)
4. 对违规帖子调 `del-feed --yes` 删除(高风险,需确认)
**参数建议:**
- 通过 `count` 控制每次扫描数量(建议 20~50)
- 可指定 `channel-id` 限定版块范围
- 定时调度间隔建议 30~60 分钟
### 问答自动回复
扫描频道帖子,识别求助帖,搜索相关内容后自动回复。
**推荐路径:**
1. `get-guild-feeds` 或 `get-channel-timeline-feeds` 拉取帖子列表
2. 逐条 `get-feed-detail` 获取完整内容
3. AI 判断是否为求助帖(含提问意图:问号、"怎么"、"如何"、"求助"等)
4. 对求助帖提取关键词,调 `search-guild-feeds` 搜索频道内相关帖子
5. 整理相关帖子内容生成回复,调 `do-comment` 发布评论
6. 无相关内容时发布礼貌提示
**参数建议:**
- `scan_count` 建议 20,每次处理适量帖子
- 每条回复引用的参考帖子建议 ≤3 条
- 支持 dry_run 模式:先分析不发布,确认后再正式运行
FILE:references/manage-guild.md
# 频道管理(manage-guild)
> **参数用 `tencent-channel-cli schema manage.<action>` 查,示例用 `--help` 看。本文档只写 schema 不体现的规则。**
## 意图→命令速查
| 意图 | 命令 | 注意 |
|------|------|------|
| 我的频道列表 / 列出频道 / 查看已加入的频道 | `get-my-join-guild-info` | 返回三类:created / managed / joined(列表级摘要) |
| 频道详情 / 频道详细信息 / 频道资料 / 成员数 / 公告 | `get-guild-info` | 返回成员数、公告、设置等详情;缺 guild_id → 先 `get-my-join-guild-info` 定位 |
| 子版块列表 | `get-guild-channel-list` | 只查版块,不查帖子 |
| 跨频道搜索频道/帖子/作者 | `search-guild-content` | scope: channel(默认) / feed / author / all;搜帖子用 scope=feed,频道内搜帖子用 feed 域 `search-guild-feeds` |
| 频道分享链接 | `get-guild-share-url` | 仅频道链接,帖子链接用 feed 域 |
| 解析分享链接 | `get-share-info` | 仅限 pd.qq.com 域名链接 |
| 加入频道 | `join-guild` | 内部自动预检,见下文 |
| 修改头像 | `upload-guild-avatar` | 需本地图片路径 |
| 修改名称/简介 | `update-guild-info` | 可只改其一 |
| 修改频道号 | `modify-guild-number` | 10~14 位英文/数字,需频道主权限;别和 `guild_id` 混淆 |
| 创建频道 | `create-theme-private-guild` | 未指定私密则默认公开 |
| 创建/删除/修改版块 | `create-channel` / `delete-channel` / `modify-channel` | delete 不可逆,高风险 |
| 查看加入设置 | `get-join-guild-setting` | 返回加入方式类型及验证问题 |
| 修改加入设置 | `update-join-guild-setting` | 6 种加入方式,高级类型需 stdin JSON |
| 发送频道私信 | `push-group-dm-msg` | 对方未回复前只能发 1 条 |
| 退出频道 | `leave-guild` | 不可逆,高风险,需 --yes |
| **帖子类任务** | **→ feed-reference.md** | |
| **成员类任务** | **→ manage-member.md** | |
### 分流误区
- "看频道有哪些帖子" → **feed-reference.md**,不是 manage
- "查成员/禁言/踢人" → **manage-member.md**
- "找频道"区分:搜未知频道 → `search-guild-content`,查已加入 → `get-my-join-guild-info`
- **帖子搜索**区分:跨频道全局搜索帖子 → `search-guild-content scope=feed`;已知频道内搜索帖子 → feed 域 `search-guild-feeds`
- **频道列表 vs 频道详情**:「列出我的频道」「我加入了哪些频道」→ `get-my-join-guild-info`(只返回名称、ID 等摘要);「查看某个频道的详细信息」「频道有多少成员」「频道公告是什么」→ 先定位 guild_id 再调 `get-guild-info`(返回成员数、公告、设置等完整详情)。**拿到列表后不要停下来**,如果用户意图是看详情,必须继续调 `get-guild-info`
## 频道创建规则
- `community_type`:`public`(默认)或 `private`,仅用户明确要求时传 `private`
- 频道名称 ≤15 字;公开频道仅中英数,私密无限制;简介 ≤300 字符
- 需 `theme` 或 `guild_name` 至少其一
- 补取分享链接失败不回滚频道,在返回中给出告警
## 加入频道规则
`join-guild` 内部自动预检加入设置,**无需**手动分两步。
| JoinGuildType | 含义 | AI 行为 |
|---------------|------|---------|
| 1 (DIRECT) | 直接加入 | 自动完成 |
| 2 (ADMIN_AUDIT) | 管理员验证 | 返回 `need_verification` → 向用户收集附言 `join_guild_comment` → 再次调用 |
| 3 (DISABLE) | 不允许 | 报错 |
| 4/5 (QUESTION*) | 回答问题 | 返回问题 → 收集答案填入 `join_guild_comment` → 再次调用 |
| 6 (MULTI_QUESTION) | 多题 | 返回问题列表 → 收集 `join_guild_answers`(⚡JSON) → 再次调用 |
| 7 (QUIZ) | 测试题 | 返回选择题 → 收集 `join_guild_answers`(⚡JSON) → 再次调用 |
> **收到 `need_verification` 必须先展示问题给用户、收集答案后才能再次调用。禁止自行编造答案。**
## 查询规则
- `get-my-join-guild-info` 返回 `created_guilds` / `managed_guilds` / `joined_guilds` 三类,用户说"我的频道"展示全部;前 10 个自动补取分享短链
- `search-guild-content` 的 `author` scope 搜的是「频道创作者」,**不是**帖子发布人也**不是**频道主;搜频道结果 ≤10 自动补取资料和短链
## 加入设置规则
### get-join-guild-setting
返回频道当前的加入方式(`joinType`)及对应验证问题/答题配置。可用于 `join-guild` 前判断需要提供哪些验证信息。
### update-join-guild-setting
修改频道加入方式,需频道主/管理员权限。
| join-type 枚举 | 含义 | 是否需要 stdin JSON |
|---------------|------|-------------------|
| `JOIN_GUILD_TYPE_DIRECT` | 无需审核,直接加入 | 否,CLI flag 即可 |
| `JOIN_GUILD_TYPE_ADMIN_AUDIT` | 发送验证消息,管理员审核 | 否 |
| `JOIN_GUILD_TYPE_DISABLE` | 不允许任何人加入 | 否 |
| `JOIN_GUILD_TYPE_QUESTION_WITH_ADMIN_AUDIT` | 回答问题 + 管理员审核 | 是,需 `setting.question.items` |
| `JOIN_GUILD_TYPE_MULTI_QUESTION` | 正确回答问题 | 是,需 `setting.question.items`(含 answer) |
| `JOIN_GUILD_TYPE_QUIZ` | 答题(选择题) | 是,需 `setting.quiz` 完整结构 |
> 高级类型(后三种)必须通过 stdin JSON 传入完整 `setting` 对象,CLI flag 仅支持前三种基础类型。
Windows / PowerShell 推荐写法:
```powershell
$body = @{
guild_id = "123456"
join_type = "JOIN_GUILD_TYPE_MULTI_QUESTION"
setting = @{
question = @{
items = @(
@{ title = "问题1"; answer = "A" },
@{ title = "问题2"; answer = "B" }
)
}
}
} | ConvertTo-Json -Depth 8 -Compress
$body | tencent-channel-cli manage update-join-guild-setting
```
## 频道私信规则
`push-group-dm-msg` 发送普通私信消息到指定用户。支持两种模式:
**⚠️ 模式选择决策规则(必须遵守):**
- 用户**引用了一条私信通知**并说"回复私信" → 使用**模式 2**(`--ref`)
- 用户说"给某人发私信"/"发私信给某人"(**没有引用私信通知**) → 使用**模式 1**(先查 `tiny_id`,再直接发送)
- **严禁**在主动发私信场景使用 `--ref`,`--ref` 仅用于回复已收到的私信通知
**模式 1:直接发送(主动发私信)**
1. 先通过 `guild-member-search` 或 `get-guild-member-list` 查到目标用户的 `tiny_id`
2. 确定来源频道的 `guild_id`(即你和目标用户共同所在的频道)
3. 调用:`tencent-channel-cli manage push-group-dm-msg --peer-tiny-id <tiny_id> --source-guild-id <guild_id> --text "内容" --json`
**模式 2:回复私信通知(通过 --ref 自动填充)**
- `ref`:通知编号(如 #1),CLI 自动从本地通知记录查找私信会话信息,并通过消息漫游获取对方 tinyId 和 sourceGuildId
- 调用:`tencent-channel-cli manage push-group-dm-msg --ref <编号> --text "内容" --json`
- **限制**:对方未回复之前只能发送 1 条消息(retCode `100707`)
- **严禁**在未获取用户同意的情况下批量发送私信
## 退出频道规则
`leave-guild` 退出指定频道,**不可逆**操作。退出后需要重新加入,且如果频道有验证设置,需重新通过验证。高风险操作,需 `--yes` 确认。
## 陷阱(schema 不体现)
- **频道号 ≠ guild_id**:用户可见的频道号(如 `pd20589127`)是展示层标识,不能当作 `guild_id` 使用。获取真实 guild_id 的方式:通过 `get-share-info` 解析分享链接,或从 `get-my-join-guild-info` 返回中提取。修改频道号用 `modify-guild-number --guild-id <ID> --guild-number <新频道号>`(10~14 位英文/数字)
- `get-share-info` 仅限 `pd.qq.com` 域名链接
- `join-guild` 的 `join_guild_answers` 和 `join_guild_comment` 仅 stdin JSON 可传
- `push-group-dm-msg` 的 `source-guild-id` 不是目标用户所在频道,而是发送者所在的来源频道
- `update-join-guild-setting` 高级类型未传 `setting` 对象会直接报错
- Windows / PowerShell 下不要直接照抄 bash 的 `echo '{...}' | ...`;复杂对象优先用 `ConvertTo-Json`
FILE:references/manage-member.md
# 成员管理(manage-member)
> **参数用 `tencent-channel-cli schema manage.<action>` 查,示例用 `--help` 看。本文档只写 schema 不体现的规则。**
## 意图→命令速查
| 意图 | 命令 | 注意 |
|------|------|------|
| 成员列表 / 找管理员 / 查机器人 | `get-guild-member-list` | 返回 owners / admins / robots / members 四类,自动去重 |
| 按昵称搜成员 / 查 tiny_id | `guild-member-search` | **优先用这个**,比翻页遍历快 |
| 个人资料 | `get-user-info` | 见参数组合 |
| 禁言/解禁 | `modify-member-shut-up` | time_stamp=0 解禁,见陷阱 |
| 踢成员 | `kick-guild-member` | 高风险,需 --yes |
| 设置/移除管理员 | `add-admin` / `remove-admin` | 支持批量 tiny_ids;remove 高风险 |
| **频道类任务** | **→ manage-guild.md** | |
| **帖子类任务** | **→ feed-reference.md** | |
### 分流误区
- 只是找某人的 tiny_id → **优先** `guild-member-search`,不要翻页遍历成员列表
- `get-user-info` 查个人资料,`get-guild-member-list` 查频道成员列表,别混淆
## get-user-info 参数组合
| 传参 | 效果 |
|------|------|
| `{}` | 查自己全局资料 |
| `{guild_id}` | 查自己在频道内资料 |
| `{guild_id, tiny_id}` | 查他人在频道内资料 |
> `isGuildAuthor` = 频道创作者,**不是**帖子发布人也**不是**频道主
## 时间戳展示
原始秒级字段(如 `joinTime`、`shutupExpireTime`)自动附带 `{字段名}_human` 可读值,向用户展示 `_human` 字段,不展示原始时间戳。禁言时间戳为 `0` 时显示"无禁言"。
## 陷阱(schema 不体现)
| 陷阱 | 说明 |
|------|------|
| 禁言 time_stamp | 必须传**绝对 Unix 时间戳**(当前时间 + 时长秒数),不是时长。`0` = 立即解禁 |
| 批量踢人 | `tiny_id`(单个)和 `member_tinyids`(⚡JSON 批量)二选一 |
| 成员搜索翻页 | `--next-pos` 原样传回上一页返回值 |
| 成员列表翻页 | `next_page_token` 不透明令牌,必须原样传回 |
FILE:references/notification-reference.md
# 频道消息通知参考
## 一、概述
频道消息通知覆盖所有频道级通知,包括帖子互动(点赞/评论/回复/@)、系统消息(加入申请等)、私信通知等。
> ⚠️ **当前仅支持 OpenClaw 平台**,非 OpenClaw 环境下无法自动推送通知。
## 二、命令路由表
| 用户意图 | CLI 命令 | 关键约束 |
|---------|----------|---------|
| 开启频道消息通知 / 帮我开启通知 | `notices-on` → `notices-on --confirm` | 两步:先测试推送,用户确认收到后加 `--confirm` 正式开启 |
| 关闭/取消频道消息通知 | `notices-off --session-key "<sessionKey>"` | 带 sessionKey 时仅移除该通道;不带时全量关闭 |
| 查看通知状态 | `notices-status` | 查看是否开启、推送模式、已注册通道列表(`registered_routes`)等 |
| 检查有没有新通知 | `check-notices` | 增量检查是否有新通知,需先开启通知 |
| 获取最近通知记录(本地) | `get-recent-notices` | 纯本地读取,用于按编号查找通知上下文 |
| 评论帖子(评论他) | `do-comment --ref <编号> --content "内容"` | 评论帖子本身,按通知编号自动填充 feed 信息 |
| 回复评论(回复他) | `do-reply --ref <编号> --content "内容"` | 回复该用户的评论,自动查找对应评论并回复 |
| 回复私信(处理通知) | `push-group-dm-msg --ref <编号> --text "内容"` | 仅用于回复已收到的私信通知,`--ref` 自动查找对方信息 |
| 主动发私信(非通知) | `push-group-dm-msg --peer-tiny-id <id> --source-guild-id <id> --text "内容"` | 先通过 `guild-member-search` 查到 tiny_id,详见 manage-guild.md |
## 三、自动引导(⚠️ 必须遵守!)
CLI 在**首次安装或版本升级后**第一次使用时引导用户开启频道消息通知(同一版本只提示一次)。
以下 hint 字段出现在 CLI 返回的 JSON 中时,Agent **必须立刻处理**,不得忽略:
**`setup_hint` 字段**(出现在 `token setup` 返回结果中):
- 表示用户刚完成登录但尚未开启频道消息通知
- Agent 收到此字段时**必须**:
1. 向用户展示 `setup_hint.message` 内容(如"登录成功!建议开启频道消息通知")
2. 主动询问用户是否要开启
3. 如果用户同意,执行 `setup_hint.command` 中的命令
- **示例**:收到 `"setup_hint": {"action":"subscribe_notices", "command":"tencent-channel-cli manage notices-on", "message":"登录成功!建议开启「频道消息通知」..."}`,应回复类似:"登录成功!建议开启频道消息通知,有新互动时自动推送给你。要开启吗?"
**`subscribe_hint` 字段**(出现在任意命令返回结果中):
- 表示用户尚未开启频道消息通知(仅在首次安装或版本升级后出现一次)
- Agent 收到此字段时**必须**:
1. 告知用户可以开启频道消息通知,有新互动时自动推送
2. 如果用户同意,执行 `subscribe_hint.command` 中的命令开启
3. 此 hint 每天最多出现一次,如果用户明确关闭了通知,则不会再出现
## 四、开启/关闭流程规则(重要!两步开启 + 根据返回字段区分状态)
**重要:通知是全局的,一次开启覆盖用户已加入的所有频道。不需要按频道单独开启,也不需要在加入新频道时重新开启。**
**⚠️ 多通道注意事项(必须遵守!):**
- 通知服务是全局的,但**推送路由是按通道(sessionKey)注册的**。即使通知已开启(`active=true`),当前通道也可能未注册推送路由
- **Agent 不能仅凭 `active=true` 就跳过 `notices-on` 调用**。用户说"开启频道消息通知"时,必须执行 `notices-on --session-key <当前通道sessionKey>` 注册当前通道
- 可通过 `notices-status` 查看 `registered_routes` 字段确认哪些通道已注册
1. **开启通知(两步流程)**:用户说"帮我开启频道消息通知"时,**必须带上当前会话的 sessionKey**:
**第一步:推送测试**
```bash
tencent-channel-cli manage notices-on --session-key "<sessionKey>" --json
```
- `--session-key`:当前会话的 sessionKey(格式 `agent:<agentid>:`,如 `agent:main:`、`agent:agent2:`),优先通过 session_status 工具获取,获取不到时从当前会话的 Agent 上下文 systemPrompt 中提取,严禁自行拼接或猜测
- ⚠️ **幻觉检查**:如果获取到的 sessionKey 形如 `agent:main:main...`(冒号后有多余重复内容),极可能是 Agent 产生了幻觉,请重新通过 session_status 工具获取,不要直接使用
- 此步**不会开启订阅**,仅发送一条测试消息验证推送通道是否正常
**根据返回的 `status` 字段判断**:
- `status = "test_sent"` → 测试消息已发出。返回中包含 `confirm_cmd` 字段(确认命令)。**Agent 必须告诉用户"已发送测试推送,请确认是否收到",然后等待用户明确回复(如"收到了"、"确认开启"等),严禁自动执行 confirm_cmd。** 只有用户主动确认后才执行 `confirm_cmd` 中的命令
- `status = "test_failed"` → 推送失败。**Agent 告知用户推送失败原因(`error` 和 `suggestions` 字段),不继续第二步**
- `status = "unsupported"` → 非 OpenClaw 环境,无法开启
**第二步:正式开启**(用户确认收到测试消息后)
```bash
tencent-channel-cli manage notices-on --session-key "<sessionKey>" --confirm --json
```
- 多 Agent 场景:用户可以从不同通道分别开启订阅,每个通道的路由独立注册,互不覆盖
2. **根据正式开启后返回结果的 `platform` 字段决定后续行为**:
**情况 A:`platform = "openclaw"`(OpenClaw 环境)**
- 通知已开启,有新互动时会自动推送到上下文
- 多 Agent 多通道:用户从 QQBot 开启订阅 → QQBot 收到通知;再从飞书开启 → 飞书也收到通知;多通道并行推送
- **向用户报告时,只需简洁告知"频道消息通知已开启,有新互动会自动推送"即可,不需要展示 PID、进程状态等内部信息**
**情况 B:`platform = "other"`(非 OpenClaw 环境)**
- 无法自动推送通知
- 告知用户:当前环境未检测到 OpenClaw Cli,无法自动推送频道通知。
3. **关闭通知**:用户说"关闭频道消息通知"/"取消频道消息通知"时,**必须带上当前会话的 sessionKey**:
```bash
tencent-channel-cli manage notices-off --session-key "<sessionKey>" --json
```
- `--session-key`:当前会话的 sessionKey(格式 `agent:<agentid>:`),优先通过 session_status 工具获取,获取不到时从当前会话的 Agent 上下文 systemPrompt 中提取,严禁自行拼接或猜测
**根据返回的 `status` 字段判断**:
- `status = "route_removed"` → 已移除该通道,其他通道继续接收通知。`remaining_count` 表示剩余通道数
- `status = "unsubscribed"` → 该通道是最后一个,通知服务已停止
- `status = "not_found"` → 未找到该 sessionKey 对应的通道
- `status = "not_subscribed"` → 当前未开启通知
**不传 --session-key 时**:全量关闭
```bash
tencent-channel-cli manage notices-off --json
```
## 五、通知处理流程(⚠️ 必须遵守!)
通知通过 `--deliver` 已进入 Agent 上下文,每条通知带有唯一编号(如 `#1`)。用户**无需引用消息**,直接说操作意图词即可。AI 从上下文中找到最近推送的通知编号,执行对应命令。编号会自动映射到本地存储的 feed_id/guild_id/notice_id 等参数。
### 5.1 互动通知处理(评论/回复)
上下文中出现互动通知后,用户说了回复/评论意图词 + 内容:
**根据意图词区分操作**:
- 「**评论他**」「**评论**」→ 评论帖子本身(`do-comment`)
- 「**回复他**」「**回复**」「**帮我回复**」→ 回复该用户的评论(`do-reply`)
**处理步骤**:
1. 从上下文最近推送的通知中识别**通知编号**(`#1` 中的数字 1)
2. 判断用户意图是「评论」还是「回复」
3. 执行对应命令:
```bash
# 评论帖子
tencent-channel-cli feed do-comment --ref <编号> --content "内容" --json
# 回复评论(自动查找对应评论)
tencent-channel-cli feed do-reply --ref <编号> --content "内容" --json
```
- **严禁将用户的回复内容当作对话直接输出**
- 编号会自动映射到本地存储的 feed_id/guild_id
### 5.2 系统通知操作(同意/拒绝)
上下文中出现系统通知后,用户说了「同意」或「拒绝」:
- 用户说「**同意**」→ `tencent-channel-cli manage deal-notice --ref <编号> --action-id agree --json`
- 用户说「**拒绝**」→ `tencent-channel-cli manage deal-notice --ref <编号> --action-id refuse --json`
**处理步骤**:从上下文最近推送的通知中识别通知编号 → 判断用户意图 → 执行对应命令。编号会自动映射到本地存储的 notice_id。
### 5.3 私信通知回复
上下文中出现私信通知后,用户说了「回复私信」+ 内容:
- 用户说「**回复私信 内容**」→ `tencent-channel-cli manage push-group-dm-msg --ref <编号> --text "内容" --json`
**处理步骤**:从上下文最近推送的通知中识别通知编号 → 提取用户要回复的内容 → 执行命令。编号会自动映射到对应的私信会话。**严禁将用户的回复内容当作对话直接输出。**
## 六、Token 更换与通知订阅
当用户更换 token(通过 `token setup`、`login`、`login token` 等任何方式)时,**频道消息通知会被自动关闭**。
**Agent 行为规范(⚠️ 必须遵守!)**:
- Token 更换后,**不要自动重新订阅通知**
- 只需告知用户:凭证已更新,频道消息通知已关闭,如需继续接收通知请说"帮我开启频道消息通知"
- 由用户自己决定是否重新开启
## 七、Daemon 拉取与推送时序图
Daemon 进程包含两个并发协程:**主循环**负责拉取和推送,**心跳协程**每 10s 独立写心跳文件(不受主循环阻塞影响)。
```
心跳协程: 每 10s 写心跳文件 (独立运行, 超时窗口 60s)
─────────────────────────────────────────────────────
主循环 (单线程, 循环执行):
┌─────────────────────────────────────────────┐
│ ① 摘要检测 digestHasUpdate() │
│ → 返回 interactChanged, systemChanged │
│ → 返回 serverDelay (下次轮询间隔) │
├─────────────────────────────────────────────┤
│ ② 按需拉取 (摘要有变化才拉详情) │
│ interactChanged → pollOnce() │
│ systemChanged → pollSystemNotices() │
│ 私信每轮都拉 → pollDMNotices() │
├─────────────────────────────────────────────┤
│ ③ 逐条推送 pushNotifications() │
│ FormatNoticeCard() → 格式化卡片 │
│ deliverOneNotification() → 逐条推送 │
│ └─ openclaw agent --deliver (阻塞15~30s)│
│ 推送成功 → MarkRecentNoticePushed() │
│ 重推之前失败的通知 │
├─────────────────────────────────────────────┤
│ ④ 保存基线 bm.Save(baseline) │
├─────────────────────────────────────────────┤
│ ⑤ 检查: CLI版本更新 / 订阅状态 / push_route │
├─────────────────────────────────────────────┤
│ ⑥ time.Sleep(nextPoll) 通常 5s │
└──────────────── 回到 ① ─────────────────────┘
```
### GuardDaemon 守护机制
任意 CLI 命令执行后触发 `runGuardDaemon()`:
1. 读取心跳文件 mtime
2. mtime < 60s 且进程存活 → 不做操作
3. mtime >= 60s 或进程不存在 → `ForkDaemon()` 启动新实例
### 关键设计点
| 设计 | 说明 |
|------|------|
| 摘要门控 | 互动/系统通知由摘要前置检测,有变化才拉详情;私信无摘要,每轮都拉 |
| 推送阻塞 | `openclaw agent --deliver` 同步阻塞 15-30s(LLM 处理) |
| 心跳独立 | 心跳协程每 10s 写一次,不受推送阻塞影响 |
| 逐条推送 | 每条通知独立推送,成功标记 pushed,失败下轮重试 |
| 基线最后保存 | 三类通知全部完成后才保存,确保一致性 |
## 八、问题定位
| 提示 / 错误 | 处理 |
|-------------|------|
| MCP 鉴权失败(retCode `8011`) | 执行 `tencent-channel-cli token setup` 重新配置凭证 → `tencent-channel-cli doctor` 确认 |