您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
This script adds a "Download KML" button to any trail on AllTrails.com. KML can be imported into Google Maps and other mapping programs.
当前为
// ==UserScript== // @name AllTrails KML Downloader // @namespace http://tampermonkey.net/ // @version 2025-05-02 // @description This script adds a "Download KML" button to any trail on AllTrails.com. KML can be imported into Google Maps and other mapping programs. // @match https://www.alltrails.com/trail/* // @grant GM_xmlhttpRequest // @grant GM_download // @require https://cdnjs.cloudflare.com/ajax/libs/mapbox-polyline/1.1.1/polyline.min.js // @license GNU GPLv3 // ==/UserScript== (function() { 'use strict'; function createDownloadButton() { const button = document.createElement('button'); button.textContent = 'Download KML'; button.style.position = 'fixed'; button.style.top = '10px'; button.style.right = '10px'; button.style.zIndex = '9999'; button.style.padding = '10px 15px'; button.style.background = '#4CAF50'; button.style.color = 'white'; button.style.border = 'none'; button.style.borderRadius = '5px'; button.style.fontSize = '16px'; button.style.cursor = 'pointer'; button.onclick = function() { fetchMapPageAndGenerateKML(); }; document.body.appendChild(button); } function extractTrailDetails() { const details = { length: '', elevation: '', time: '', type: '' }; const statBlocks = document.querySelectorAll('.TrailStats_stat__O2GvM'); statBlocks.forEach(stat => { const labelEl = stat.querySelector('.TrailStats_statLabel__vKMLy'); const valueEl = stat.querySelector('.TrailStats_statValueSm__HlKIU'); if (!labelEl) return; const label = labelEl.textContent.trim().toLowerCase(); if (label.includes('length')) { details.length = valueEl?.textContent.trim() || ''; } else if (label.includes('elevation')) { details.elevation = valueEl?.textContent.trim() || ''; } else if (label.includes('estimated time')) { details.time = valueEl?.textContent.trim() || ''; } else if (label.match(/loop|out.*back|point.*point/i)) { details.type = labelEl.textContent.trim(); } }); // If type still empty, try to grab from last stat label if it's a standalone type if (!details.type) { statBlocks.forEach(stat => { const labelEl = stat.querySelector('.TrailStats_statLabel__vKMLy'); if (labelEl && ['loop', 'out & back', 'point to point'].includes(labelEl.textContent.trim().toLowerCase())) { details.type = labelEl.textContent.trim(); } }); } // Extract difficulty const difficultyEl = document.querySelector('[data-testid="trail-difficulty"]'); if (difficultyEl) { details.difficulty = difficultyEl.textContent.trim(); } return details; } function fetchMapPageAndGenerateKML() { const trailUrl = window.location.href; const mapUrl = trailUrl.replace('/trail/', '/explore/trail/') + '?mobileMap=false&initFlyover=true&flyoverReturnToTrail'; console.log(`Fetching map data from: ${mapUrl}`); GM_xmlhttpRequest({ method: 'GET', url: mapUrl, onload: function(response) { const mapPageHtml = response.responseText; const mapDataMatch = mapPageHtml.match(/<div data-react-class="SearchApp" data-react-props="({.+?})"/); if (mapDataMatch && mapDataMatch[1]) { const mapDataJson = JSON.parse(mapDataMatch[1].replace(/"/g, '"')); const details = extractTrailDetails(); generateKML(mapDataJson, details); } else { console.error('Failed to extract map data'); } }, onerror: function() { console.error('Error fetching map page.'); } }); } function generateKML(mapData, trailDetails) { const trailName = mapData.initialExploreMap.name; const waypoints = mapData.initialExploreMap.waypoints; const routes = mapData.initialExploreMap.routes; const description = ` Difficulty: ${trailDetails.difficulty}<br/> Length: ${trailDetails.length}<br/> Elevation Gain: ${trailDetails.elevation}<br/> Estimated Time: ${trailDetails.time}<br/> Trail Type: ${trailDetails.type}<br/><br/> ${window.location.href} `.trim(); let kmlContent = `<?xml version="1.0" encoding="UTF-8"?>\n<kml xmlns="http://www.opengis.net/kml/2.2">\n<Document>\n`; routes.forEach(route => { route.lineSegments.forEach(segment => { const pointsData = segment.polyline.pointsData; if (typeof pointsData === 'string') { const decodedPoints = polyline.decode(pointsData); let coordinates = decodedPoints.map(point => `${point[1]},${point[0]},0`).join(' '); kmlContent += ` <Placemark> <name>${trailName}</name> <description><![CDATA[${description}]]></description> <LineString> <coordinates>${coordinates}</coordinates> </LineString> </Placemark> `; } else { console.error('pointsData is not a polyline string:', pointsData); } }); }); waypoints.forEach(waypoint => { kmlContent += ` <Placemark> <name>${waypoint.name}</name> <Style> <IconStyle> <scale>1</scale> <Icon> <href>https://maps.google.com/mapfiles/kml/paddle/grn-blank.png</href> </Icon> </IconStyle> </Style> <Point> <coordinates>${waypoint.location.longitude},${waypoint.location.latitude},0</coordinates> </Point> <description><![CDATA[${waypoint.description || ''}]]></description> </Placemark> `; }); kmlContent += `</Document>\n</kml>`; const blob = new Blob([kmlContent], { type: 'application/vnd.google-earth.kml+xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${trailName}.kml`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } createDownloadButton(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址