模拟指定位置,支持地图选点、手动输入经纬度和快捷键隐藏/显示(Ctrl+;)
// ==UserScript==
// @name 模拟地理位置
// @namespace http://tampermonkey.net/
// @version 2.1
// @description 模拟指定位置,支持地图选点、手动输入经纬度和快捷键隐藏/显示(Ctrl+;)
// @author mcbaoge
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const KEY = 'geo_ui_visible'; // localStorage key
// 初始化经纬度
let fakeLat = 39.9042;
let fakeLon = 116.4074;
// 重写 geolocation API
function overrideGeolocation(lat, lon) {
navigator.geolocation.getCurrentPosition = function (successCallback) {
const position = {
coords: {
latitude: lat,
longitude: lon,
accuracy: 50,
},
timestamp: Date.now()
};
successCallback(position);
};
navigator.geolocation.watchPosition = function (successCallback) {
const position = {
coords: {
latitude: lat,
longitude: lon,
accuracy: 50,
},
timestamp: Date.now()
};
successCallback(position);
return 1;
};
console.log(`[🌍模拟定位] 已更新至: ${lat}, ${lon}`);
}
overrideGeolocation(fakeLat, fakeLon);
// 插入 UI 样式
const style = document.createElement("style");
style.innerHTML = `
#geo-panel {
position: fixed;
top: 80px;
right: 20px;
z-index: 99999;
width: 320px;
background: #fff;
border: 1px solid #ccc;
box-shadow: 0 0 10px rgba(0,0,0,0.3);
border-radius: 8px;
font-family: sans-serif;
font-size: 14px;
overflow: hidden;
}
#geo-header {
background: #f0f0f0;
padding: 5px 10px;
font-weight: bold;
border-bottom: 1px solid #ddd;
}
#geo-map {
height: 300px;
}
#geo-form {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
gap: 4px;
background: #fafbfc;
border-top: 1px solid #eee;
}
#geo-form input {
width: 80px;
padding: 2px 4px;
border: 1px solid #ccc;
border-radius: 4px;
}
#geo-set-btn {
padding: 2px 8px;
background: #2d8cf0;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
#geo-set-btn:hover {
background: #1c6fc7;
}
`;
document.head.appendChild(style);
// 创建面板 UI,增加表单部分
const panel = document.createElement("div");
panel.id = "geo-panel";
panel.innerHTML = `
<div id="geo-header">🌍 模拟定位(地图选点/手动输入/快捷键隐藏/显示(Ctrl+;))</div>
<div id="geo-map"></div>
<form id="geo-form" autocomplete="off">
<label>纬度:<input type="number" id="geo-lat" name="lat" step="0.000001" required></label>
<label>经度:<input type="number" id="geo-lon" name="lon" step="0.000001" required></label>
<button type="submit" id="geo-set-btn">设置</button>
</form>
`;
document.body.appendChild(panel);
// 插入 Leaflet 脚本和样式
const leafletCSS = document.createElement("link");
leafletCSS.rel = "stylesheet";
leafletCSS.href = "https://unpkg.com/[email protected]/dist/leaflet.css";
document.head.appendChild(leafletCSS);
const leafletScript = document.createElement("script");
leafletScript.src = "https://unpkg.com/[email protected]/dist/leaflet.js";
leafletScript.onload = () => {
const map = L.map("geo-map").setView([fakeLat, fakeLon], 10);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
}).addTo(map);
const marker = L.marker([fakeLat, fakeLon], { draggable: true }).addTo(map);
function updatePosition(lat, lon, moveMarker = true) {
fakeLat = lat;
fakeLon = lon;
overrideGeolocation(lat, lon);
// 更新输入框
latInput.value = lat.toFixed(6);
lonInput.value = lon.toFixed(6);
// 移动marker和地图
if (moveMarker) marker.setLatLng([lat, lon]);
map.setView([lat, lon]);
}
map.on('click', function (e) {
updatePosition(e.latlng.lat, e.latlng.lng);
});
marker.on('dragend', function (e) {
const latlng = e.target.getLatLng();
updatePosition(latlng.lat, latlng.lng, false);
});
// 初始化输入框
latInput.value = fakeLat.toFixed(6);
lonInput.value = fakeLon.toFixed(6);
};
document.body.appendChild(leafletScript);
// 表单逻辑
const latInput = panel.querySelector("#geo-lat");
const lonInput = panel.querySelector("#geo-lon");
const form = panel.querySelector("#geo-form");
form.addEventListener("submit", function (e) {
e.preventDefault();
let lat = parseFloat(latInput.value);
let lon = parseFloat(lonInput.value);
if (!isNaN(lat) && !isNaN(lon)) {
fakeLat = lat;
fakeLon = lon;
overrideGeolocation(lat, lon);
// 地图和marker同步
if (window.L && window.L.map) {
// Leaflet 脚本已加载
const map = window.L.DomUtil.get('geo-map')? window.L.map('geo-map') : null;
if (map) map.setView([lat, lon]);
}
}
});
// 处理隐藏/显示快捷键
document.addEventListener("keydown", function (e) {
if (e.ctrlKey && e.code === "Semicolon") {
const current = localStorage.getItem(KEY) === 'true';
const newState = (!current).toString();
localStorage.setItem(KEY, newState);
updatePanelVisibility();
}
});
// 统一隐藏/显示状态
function updatePanelVisibility() {
const isVisible = localStorage.getItem(KEY) === 'true';
panel.style.display = isVisible ? 'block' : 'none';
}
// 初始化显示状态
if (localStorage.getItem(KEY) === null) {
localStorage.setItem(KEY, 'true');
}
updatePanelVisibility();
})();