// ==UserScript==
// @name AmapTools
// @description 一款高德地图扩展工具。拦截高德地图(驾车、公交、步行)路线规划接口数据,将其转换成GeoJSON格式数据,并提供复制和下载。
// @version 1.0.2
// @author DD1024z
// @namespace https://github.com/10D24D/AmapTools/
// @supportURL https://github.com/10D24D/AmapTools/
// @match https://www.amap.com/*
// @match https://ditu.amap.com/*
// @match https://www.gaode.com/*
// @icon https://a.amap.com/pc/static/favicon.ico
// @license MIT
// @grant none
// ==/UserScript==
(function () {
'use strict';
let responseData = null; // 拦截到的接口数据
let routeType = ''; // 当前路线类型(驾车、公交或步行)
let listGeoJSON = []
let currentGeoJSON = {}
let selectedPathIndex = -1;
let isDragging = false;
let dragOffsetX = 0;
let dragOffsetY = 0;
let panelPosition = { left: null, top: null }; // 保存面板位置
const directionMap = {
"driving": "驾车",
"transit": "公交",
"walking": "步行",
}
const uriMap = {
"driving": "/service/autoNavigat",
"transit": "/service/nav/bus",
"walking": "/v3/direction/walking",
}
// 样式封装
const style = document.createElement('style');
style.innerHTML = `
#routeOptions {
position: fixed;
z-index: 9999;
background-color: #f9f9f9;
border: 1px solid #ccc;
padding: 10px;
box-shadow: 0 2px 2px rgba(0, 0, 0, .15);
background: #fff;
width: 300px;
border-radius: 3px;
font-family: Arial, sans-serif;
cursor: move;
}
#routeOptions #closeBtn {
position: absolute;
top: -12px;
right: 0px;
background-color: transparent;
color: #b3b3b3;
border: none;
font-size: 24px;
cursor: pointer;
}
#routeOptions h3 {
color: #333;
font-size: 14px;
}
#routeOptions label {
display: block;
margin-bottom: 8px;
}
#routeOptions button {
margin-top: 5px;
padding: 5px 10px;
cursor: pointer;
}
`;
document.head.appendChild(style);
// 拦截 XMLHttpRequest 请求
(function (open) {
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
if (url.includes(uriMap.driving) || url.includes(uriMap.transit)) {
this.addEventListener('load', function () {
if (this.readyState === 4 && this.status === 200) {
try {
routeType = url.includes(uriMap.driving) ? directionMap.driving : directionMap.transit;
responseData = JSON.parse(this.responseText);
parseDataToGeoJSON();
} catch (e) {
responseData = null;
console.error('解析路线数据时出错', e);
}
}
});
}
open.apply(this, arguments);
};
})(XMLHttpRequest.prototype.open);
// 拦截 script 请求
const observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
mutation.addedNodes.forEach(function (node) {
// 动态拦截步行路线的 JSONP 请求
if (node.tagName === 'SCRIPT' && node.src.includes(uriMap.walking)) {
const callbackName = /callback=([^&]+)/.exec(node.src)[1];
if (callbackName && window[callbackName]) {
const originalCallback = window[callbackName];
window[callbackName] = function (data) {
routeType = directionMap.walking;
responseData = data;
parseDataToGeoJSON();
if (originalCallback) {
originalCallback(data);
}
};
}
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
const lineGeoJSONTemplate = {
type: "Feature",
geometry: {
type: "LineString",
coordinates: []
},
properties: {}
};
// 初始化一个路线的geojson
function initLineGeoJSON() {
return JSON.parse(JSON.stringify(lineGeoJSONTemplate)); // 深拷贝模板对象
}
// 将原始数据转换成geojson
function parseDataToGeoJSON() {
listGeoJSON = [];
let pathList = [];
if (routeType === directionMap.driving) {
// 解析驾车规划的数据
pathList = responseData.data.path_list;
pathList.forEach((data, index) => {
let geoJSON = initLineGeoJSON();
geoJSON.properties.duration = Math.ceil(responseData.data.drivetime.split(',')[index] / 60)
geoJSON.properties.distance = parseInt(responseData.data.distance.split(',')[index], 10)
geoJSON.properties.traffic_lights = parseInt(data.traffic_lights || 0, 10)
data.path.forEach((path, index) => {
path.segments.forEach((segment, index) => {
if (segment.coor) {
// 去掉 `[]` 符号
const cleanedCoor = segment.coor.replace(/[\[\]]/g, '');
const coorArray = cleanedCoor.split(',').map(Number);
for (let k = 0; k < coorArray.length; k += 2) {
const lng = coorArray[k];
const lat = coorArray[k + 1];
if (!isNaN(lng) && !isNaN(lat)) {
geoJSON.geometry.coordinates.push([lng, lat]);
}
}
}
});
});
listGeoJSON.push(geoJSON)
});
} else if (routeType === directionMap.transit) {
// 解析公交规划的数据
if (responseData.data.routelist && responseData.data.routelist.length > 0) {
// 如果存在 routelist 则优先处理 routelist
pathList = responseData.data.routelist;
// 处理 routelist 数据结构
pathList.forEach((segment, index) => {
let geoJSON = initLineGeoJSON();
segment.segments.forEach((subSegment, i) => {
subSegment.forEach((element, j) => {
// 铁路。拼接起点、途经点和终点坐标
if (element[0] === "railway") {
// 添加起点坐标
const startCoord = element[1].scord.split(' ').map(Number);
geoJSON.geometry.coordinates.push(startCoord);
// 添加途经点坐标
const viaCoords = element[1].viastcord.split(' ').map(Number);
for (let k = 0; k < viaCoords.length; k += 2) {
geoJSON.geometry.coordinates.push([viaCoords[k], viaCoords[k + 1]]);
}
// 添加终点坐标
const endCoord = element[1].tcord.split(' ').map(Number);
geoJSON.geometry.coordinates.push(endCoord);
}
});
});
geoJSON.properties.duration = parseInt(segment.time, 10); // 路程时间(单位:分钟)
geoJSON.properties.distance = parseInt(segment.distance, 10); // 路程距离(单位:米)
geoJSON.properties.cost = parseFloat(segment.cost); // 花费金额
listGeoJSON.push(geoJSON);
});
} else {
// 过滤掉没有 busindex 的公交路线
pathList = responseData.data.buslist.filter(route => route.busindex !== undefined);
pathList.forEach(data => {
let geoJSON = initLineGeoJSON();
geoJSON.properties.distance = parseInt(data.allLength, 10)
geoJSON.properties.duration = Math.ceil(data.expensetime / 60)
geoJSON.properties.walk_distance = parseInt(data.allfootlength, 10)
geoJSON.properties.expense = Math.ceil(data.expense)
geoJSON.properties.expense_currency = data.expense_currency
const segmentList = data.segmentlist;
let segmentProperties = []
segmentList.forEach(segment => {
if (!geoJSON.properties.startStation) {
geoJSON.properties.startStation = segment.startname + (geoJSON.properties.inport ? '(' + geoJSON.properties.inport + ')' : '');
}
let importantInfo = {
startname: segment.startname ? segment.startname : '',
endname: segment.endname ? segment.endname : '',
bus_key_name: segment.bus_key_name ? segment.bus_key_name : '',
inport_name: segment.inport.name ? segment.inport.name : '',
outport_name: segment.outport.name ? segment.outport.name : '',
}
segmentProperties.push(importantInfo);
// 起点到公交的步行路径
if (segment.walk && segment.walk.infolist) {
segment.walk.infolist.forEach(info => {
const walkCoords = info.coord.split(',').map(Number);
for (let i = 0; i < walkCoords.length; i += 2) {
geoJSON.geometry.coordinates.push([walkCoords[i], walkCoords[i + 1]]);
}
});
}
// 公交驾驶路线
const driverCoords = segment.drivercoord.split(',').map(Number);
for (let i = 0; i < driverCoords.length; i += 2) {
geoJSON.geometry.coordinates.push([driverCoords[i], driverCoords[i + 1]]);
}
// 公交换乘路线
// if (segment.alterlist && segment.alterlist.length > 0){
// for (let i = 0; i < segment.alterlist.length; i++) {
// const after = array[i];
// }
// }
});
// 到达公交后离终点的步行路径
if (data.endwalk && data.endwalk.infolist) {
data.endwalk.infolist.forEach(info => {
const endwalkCoords = info.coord.split(',').map(Number);
for (let i = 0; i < endwalkCoords.length; i += 2) {
geoJSON.geometry.coordinates.push([endwalkCoords[i], endwalkCoords[i + 1]]);
}
});
}
listGeoJSON.push(geoJSON);
});
}
} else if (routeType === directionMap.walking) {
// 解析步行规划的数据
pathList = responseData.route.paths;
pathList.forEach(path => {
let geoJSON = initLineGeoJSON()
geoJSON.properties.distance = parseInt(path.distance, 10)
geoJSON.properties.duration = Math.ceil(parseInt(path.duration, 10) / 60)
path.steps.forEach(step => {
const coorArray = step.polyline.split(';').map(item => item.split(',').map(Number));
coorArray.forEach(coordinate => {
if (coordinate.length === 2 && !isNaN(coordinate[0]) && !isNaN(coordinate[1])) {
geoJSON.geometry.coordinates.push(coordinate);
}
});
});
listGeoJSON.push(geoJSON);
});
} else {
console.error('未知的数据')
return;
}
displayRouteOptions()
}
// 创建路线选择界面
function displayRouteOptions() {
const existingDiv = document.getElementById('routeOptions');
if (existingDiv) {
existingDiv.remove();
}
const routeDiv = document.createElement('div');
routeDiv.id = 'routeOptions';
// 检查是否有保存的位置数据
if (panelPosition.left && panelPosition.top) {
routeDiv.style.left = `${panelPosition.left}px`;
routeDiv.style.top = `${panelPosition.top}px`;
} else {
// 如果没有保存的位置数据,使用默认位置
routeDiv.style.right = '20px';
routeDiv.style.top = '100px';
}
// 创建关闭按钮
const closeBtn = document.createElement('button');
closeBtn.id = 'closeBtn';
closeBtn.innerText = '×';
closeBtn.onclick = function () {
routeDiv.remove();
};
routeDiv.appendChild(closeBtn);
// 出行方式
const modeTitle = document.createElement('h3');
modeTitle.innerText = '出行方式:';
routeDiv.appendChild(modeTitle);
const modeSelectionDiv = document.createElement('div');
modeSelectionDiv.style.display = 'flex';
modeSelectionDiv.style.flexDirection = 'row';
modeSelectionDiv.style.flexWrap = 'wrap';
const modes = [directionMap.driving, directionMap.transit, directionMap.walking];
const modeIds = ['carTab', 'busTab', 'walkTab'];
modes.forEach((mode, modeIndex) => {
const modeLabel = document.createElement('label');
const modeRadio = document.createElement('input');
modeLabel.style.marginRight = '5px';
modeRadio.type = 'radio';
modeRadio.name = 'modeSelection';
modeRadio.value = mode;
modeRadio.onchange = function () {
const modeTab = document.getElementById(modeIds[modeIndex]);
if (modeTab) {
modeTab.click(); // 触发高德地图相应Tab的点击事件
}
};
if (mode === routeType) {
modeRadio.checked = true;
}
modeLabel.appendChild(modeRadio);
modeLabel.appendChild(document.createTextNode(mode));
modeSelectionDiv.appendChild(modeLabel);
});
// 将 modeSelectionDiv 添加到路线选择界面
routeDiv.appendChild(modeSelectionDiv);
// 修改原来的标题
const title = document.createElement('h3');
title.innerText = `路线列表:`;
routeDiv.appendChild(title);
const routeFragment = document.createDocumentFragment();
// 遍历所有的路线
listGeoJSON.forEach((geoJSON, index) => {
const label = document.createElement('label');
const radio = document.createElement('input');
radio.type = 'radio';
radio.name = 'routeSelection';
radio.value = index;
radio.onclick = function () {
selectedPathIndex = index;
currentGeoJSON = listGeoJSON[selectedPathIndex]
copyToClipboard(JSON.stringify(currentGeoJSON));
// console.log("选中的路线:", currentGeoJSON);
// 同步点击高德地图的路线选项
// 去除所有元素的 open 样式
document.querySelectorAll('.planTitle.open').forEach(function (el) {
el.classList.remove('open');
});
// 为当前选中的路线添加 open 样式
const currentPlanTitle = document.getElementById(`plantitle_${index}`);
if (currentPlanTitle) {
currentPlanTitle.classList.add('open');
currentPlanTitle.click();
}
};
if (index === 0) {
radio.checked = true;
selectedPathIndex = 0;
currentGeoJSON = listGeoJSON[selectedPathIndex]
copyToClipboard(JSON.stringify(currentGeoJSON));
// console.log("选中的路线:", currentGeoJSON);
}
const totalDistance = formatDistance(geoJSON.properties.distance);
const totalTime = formatTime(geoJSON.properties.duration);
const trafficLights = geoJSON.properties.traffic_lights ? ` | 红绿灯${geoJSON.properties.traffic_lights}个` : '';
const walkDistance = geoJSON.properties.walk_distance ? ` | 步行${formatDistance(geoJSON.properties.walk_distance)}` : '';
const expense = geoJSON.properties.expense ? ` | ${Math.ceil(geoJSON.properties.expense)}${geoJSON.properties.expense_currency}` : '';
label.appendChild(radio);
label.appendChild(document.createTextNode(`路线${index + 1}:约${totalTime} | ${totalDistance}${trafficLights}${walkDistance}${expense}`));
routeFragment.appendChild(label);
});
routeDiv.appendChild(routeFragment);
const downloadBtn = document.createElement('button');
downloadBtn.innerText = '下载GeoJSON';
downloadBtn.onclick = function () {
if (selectedPathIndex === -1) {
alert("请先选择一条路线");
return;
}
currentGeoJSON = listGeoJSON[selectedPathIndex]
downloadGeoJSON(currentGeoJSON, `${routeType}_路线${selectedPathIndex + 1}.geojson`);
};
routeDiv.appendChild(downloadBtn);
document.body.appendChild(routeDiv);
// 添加拖拽功能
routeDiv.addEventListener('mousedown', function (e) {
isDragging = true;
dragOffsetX = e.clientX - routeDiv.offsetLeft;
dragOffsetY = e.clientY - routeDiv.offsetTop;
routeDiv.style.cursor = 'grabbing';
});
document.addEventListener('mousemove', function (e) {
if (isDragging) {
const newLeft = Math.max(0, Math.min(window.innerWidth - routeDiv.offsetWidth, e.clientX - dragOffsetX));
const newTop = Math.max(0, Math.min(window.innerHeight - routeDiv.offsetHeight, e.clientY - dragOffsetY));
routeDiv.style.left = `${newLeft}px`;
routeDiv.style.top = `${newTop}px`;
// 保存新的位置到 panelPosition
panelPosition.top = newTop;
panelPosition.left = newLeft;
}
});
document.addEventListener('mouseup', function () {
isDragging = false;
routeDiv.style.cursor = 'move';
});
}
// 时间格式化:大于60分钟显示小时,大于24小时显示天
function formatTime(minutes) {
if (minutes >= 1440) { // 超过24小时
const days = Math.floor(minutes / 1440);
const hours = Math.floor((minutes % 1440) / 60);
return `${days}天${hours ? hours + '小时' : ''}`;
} else if (minutes >= 60) { // 超过1小时
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return `${hours}小时${mins ? mins + '分钟' : ''}`;
}
return `${minutes}分钟`;
}
// 格式化距离函数:如果小于1000米,保留米;如果大于等于1000米,转换为公里
function formatDistance(distanceInMeters) {
if (distanceInMeters < 1000) {
return `${distanceInMeters}米`;
} else {
return `${(distanceInMeters / 1000).toFixed(1)}公里`;
}
}
// 复制内容到剪贴板
function copyToClipboard(text) {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
console.log("GeoJSON已复制到剪贴板");
}).catch(() => fallbackCopyToClipboard(text));
} else {
fallbackCopyToClipboard(text);
}
}
// 备用复制方案
function fallbackCopyToClipboard(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
try {
document.execCommand('copy');
console.log("GeoJSON已复制到剪贴板");
} catch (err) {
console.error("备用复制方案失败: ", err);
}
document.body.removeChild(textarea);
}
// 下载GeoJSON文件
function downloadGeoJSON(geoJSON, filename) {
const blob = new Blob([JSON.stringify(geoJSON)], { type: 'application/json' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// AmapLoginAssist - 高德地图支持密码登录、三方登录
// [clone from MIT code](https://greasyfork.org/zh-CN/scripts/477376-amaploginassist-%E9%AB%98%E5%BE%B7%E5%9C%B0%E5%9B%BE%E6%94%AF%E6%8C%81%E5%AF%86%E7%A0%81%E7%99%BB%E5%BD%95-%E4%B8%89%E6%96%B9%E7%99%BB%E5%BD%95)
let pollCount = 0;
let intervalID = setInterval(() => {
try {
pollCount++;
if (pollCount > 50) {
clearInterval(intervalID);
return;
}
//
if (window.passport && window.passport.config) {
clearInterval(intervalID);
window.passport.config({
loginMode: ["password", "message", "qq", "sina", "taobao", "alipay", "subAccount", "qrcode"],
loginParams: {
dip: 20303
}
});
window.passport.config = () => { };
}
} catch (e) {
console.error(e)
clearInterval(intervalID);
}
}, 100);
})();