- // ==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://greatest.deepsurf.us/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);
- })();