- // ==UserScript==
- // @name 图寻复盘工具 PRO
- // @namespace https://greasyfork.org/users/1179204
- // @version 1.6.6
- // @description 增加复盘小地图,全面提升复盘效果
- // @match *://tuxun.fun/replay-pano?gameId=*&round=*
- // @icon data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDggNDgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgZmlsbD0iIzAwMDAwMCI+PGcgaWQ9IlNWR1JlcG9fYmdDYXJyaWVyIiBzdHJva2Utd2lkdGg9IjAiPjwvZz48ZyBpZD0iU1ZHUmVwb190cmFjZXJDYXJyaWVyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjwvZz48ZyBpZD0iU1ZHUmVwb19pY29uQ2FycmllciI+PHRpdGxlPjcwIEJhc2ljIGljb25zIGJ5IFhpY29ucy5jbzwvdGl0bGU+PHBhdGggZD0iTTI0LDEuMzJjLTkuOTIsMC0xOCw3LjgtMTgsMTcuMzhBMTYuODMsMTYuODMsMCwwLDAsOS41NywyOS4wOWwxMi44NCwxNi44YTIsMiwwLDAsMCwzLjE4LDBsMTIuODQtMTYuOEExNi44NCwxNi44NCwwLDAsMCw0MiwxOC43QzQyLDkuMTIsMzMuOTIsMS4zMiwyNCwxLjMyWiIgZmlsbD0iI2ZmOTQyNyI+PC9wYXRoPjxwYXRoIGQ9Ik0yNS4zNywxMi4xM2E3LDcsMCwxLDAsNS41LDUuNUE3LDcsMCwwLDAsMjUuMzcsMTIuMTNaIiBmaWxsPSIjZmZmZmZmIj48L3BhdGg+PC9nPjwvc3ZnPg==
- // @author KaKa
- // @license BSD
- // @grant GM_setClipboard
- // @grant GM_addStyle
- // @grant GM_xmlhttpRequest
- // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
- // @require https://unpkg.com/leaflet@1.9.2/dist/leaflet.js
- // @require https://unpkg.com/gcoord/dist/gcoord.global.prod.js
- // @require https://cdn.jsdelivr.net/npm/suncalc@1.9.0/suncalc.min.js
- // ==/UserScript==
- (function() {
- 'use strict';
- GM_addStyle(`
-
- @import url('https://unpkg.com/leaflet@1.9.2/dist/leaflet.css');
-
- #panels {
- position: fixed;
- top: 100px;
- left: 10px;
- padding: 10px;
- border-radius: 20px !important;
- z-index: 1000;
- display: flex;
- flex-direction: column;
- width: 180px;
- }
-
- #panels button {
- cursor: pointer;
- width: 100% !important;
- font-weight: bold !important;
- border: 8px solid #000000 !important;
- text-align: left !important;
- padding-left: 8px !important;
- padding-right: 8px !important;
- backdrop-filter: blur(10px);
- margin-bottom: 5px;
- border-radius: 4px;
- background-color: #000000 !important;
- color: #A0A0A0 !important;
- }
-
- #timeline {
- cursor: pointer;
- width: 100%;
- font-weight: bold;
- font-size:14px;
- border: 8px solid #000000;
- text-align: left;
- padding-left: 4px;
- padding-right: 2px;
- backdrop-filter: blur(10px);
- margin-bottom: 5px;
- border-radius: 4px;
- background-color: #000000;
- color: #A0A0A0;
- }
-
- #replay {
- cursor: pointer;
- width: 100%;
- font-weight: bold;
- font-size:16px;
- border: 8px solid #000000;
- text-align: left;
- padding-left: 4px;
- padding-right: 2px;
- backdrop-filter: blur(10px);
- margin-bottom: 5px;
- border-radius: 4px;
- background-color: #000000;
- color: #A0A0A0;
- }
-
- .custom-marker {
- background-color: red;
- color: white;
- border-radius: 50%;
- width: 20px;
- height: 20px;
- text-align: center;
- line-height: 20px;
- }
-
- .leaflet-tooltip {
- background: rgba(255, 255, 255, 0.8);
- border: 0.5px solid #ccc;
- border-radius: 4px;
- font-size: 13px;
- color: black;
- font-weight: bold;
- }
-
- .ripple {
- position: absolute;
- border-radius: 50%;
- background: rgba(0, 0, 0, 0.3);
- pointer-events: none;
- transform: scale(0);
- animation: ripple-animation 1s linear;
- }
-
- @keyframes ripple-animation {
- to {
- transform: scale(4);
- opacity: 0;}
- }
-
- `);
-
- L.Projection.BaiduMercator = L.Util.extend({}, L.Projection.Mercator, {
- R: 6378206,
- R_MINOR: 6356584.314245179,
- bounds: new L.Bounds([-20037725.11268234, -19994619.55417086], [20037725.11268234, 19994619.55417086])
- });
-
- L.CRS.Baidu = L.Util.extend({}, L.CRS.Earth, {
- code: 'EPSG:Baidu',
- projection: L.Projection.BaiduMercator,
- transformation: new L.Transformation(1, 0.5, -1, 0.5),
- scale: function (zoom) { return 1 / Math.pow(2, (18 - zoom)); },
- zoom: function (scale) { return 18 - Math.log(1 / scale) / Math.LN2; },
- });
-
- L.TileLayer.BaiDuTileLayer = L.TileLayer.extend({
- initialize: function (param, options) {
- var templateImgUrl = "//maponline{s}.bdimg.com/starpic/u=x={x};y={y};z={z};v=009;type=sate&qt=satepc&fm=46&app=webearth2&v=009";
- var templateUrl = "//maponline{s}.bdimg.com/tile/?x={x}&y={y}&z={z}&{p}";
- var streetViewUrl = "//mapsv1.bdimg.com/?qt=tile&styles=pl&x={x}&y={y}&z={z}";
- var myUrl;
- if (param === "img") {
- myUrl = templateImgUrl;
- } else if (param === "streetview") {
- myUrl = streetViewUrl;
- } else {
- myUrl = templateUrl;
- }
- options = L.extend({
- getUrlArgs: function (o) { return { x: o.x, y: (-1 - o.y), z: o.z }; },
- p: param, subdomains: "0123", minZoom: 3, maxZoom: 19, minNativeZoom: 3, maxNativeZoom:19
- }, options);
- L.TileLayer.prototype.initialize.call(this, myUrl, options);
- },
-
- getTileUrl: function (coords) {
- if (this.options.getUrlArgs) {
- return L.Util.template(this._url, L.extend({ s: this._getSubdomain(coords), r: L.Browser.retina ? '@2x' : '' }, this.options.getUrlArgs(coords), this.options));
- } else {
- return L.TileLayer.prototype.getTileUrl.call(this, coords);
- }
- },
- _setZoomTransform: function (level, center, zoom) {
- center =L.latLng(gcoord.transform([center.lng, center.lat], gcoord.WGS84, gcoord.BD09).reverse())
- L.TileLayer.prototype._setZoomTransform.call(this, level, center, zoom);
- },
- _getTiledPixelBounds: function (center) {
- center = L.latLng(gcoord.transform([center.lng, center.lat], gcoord.WGS84, gcoord.BD09).reverse())
- return L.TileLayer.prototype._getTiledPixelBounds.call(this, center);
- }
- });
-
- L.tileLayer.baiDuTileLayer = function (param, options) { return new L.TileLayer.BaiDuTileLayer(param, options); };
-
- L.Control.OpacityControl = L.Control.extend({
- options: {
- position: 'topright'
- },
-
- initialize: function (layer, options) {
- this.layer = layer;
- L.setOptions(this, options);
- },
-
- onAdd: function (map) {
- var container = L.DomUtil.create('div', 'leaflet-control-opacity');
- this.container=container
- container.style.backgroundColor='#fff'
- container.style.width='100px'
- container.style.height='28px'
- container.style.boxShadow='rgba(0, 0, 0, 0.3) 0px 1px 4px -1px'
- container.style.borderRadius='5px'
- container.innerHTML = `
- <input type="range" id="opacity-slider" min="0" max="100" value="0" step="10" style="margin:5px; width:90px">
- `;
- L.DomEvent.disableClickPropagation(container);
- L.DomEvent.disableScrollPropagation(container);
- L.DomEvent.on(container.querySelector('#opacity-slider'), 'input', function (e) {
- var opacity = e.target.value / 100;
- this._currentOpacity = opacity;
- this.layer.setOpacity(opacity)
- }.bind(this));
-
- return container;
- },
- setOpacity: function(value){
- if(this.container) this.container.style.opacity=`${value}`
- }
- });
-
- //if (window.location.href.includes('/solo/') || window.location.href.includes('/challenge/')) return
-
- L.control.opacityControl = function(opts) {
- return new L.Control.OpacityControl(opts);
- };
-
- function getCustomIcon(color, url) {
- if (!url) url="https://i.chao-fan.com/f58b7f52d7c801ba0806e2125a776a44.png"
- return L.divIcon({
- className: 'custom-icon',
- html: `
- <div class="marker-background" style="height:100%;width:100%; background-image: url("https://s.chao-fan.com/tuxun/images/marker_background_${color}.png"); background-size: 100%; background-repeat: no-repeat; overflow:hidden;">
- <img src="https://i.chao-fan.com/${url}?x-oss-process=image/resize,h_80/quality,q_100" style="position: absolute; top: 38%; left: 50%; width:28px; height:28px; transform: translate(-50%, -50%); border-radius: 100%" />
- </div>
- `,
- iconSize: [30, 42],
- iconAnchor: [15, 42],
- popupAnchor: [1, -34],
- shadowSize: [42, 42]
- });
- }
-
- const flagIcon = new L.divIcon({
- className: 'custom-icon',
- html: `
- <div class="marker-background" style="height:100%;width:100%; background-image: url("https://s.chao-fan.com/tuxun/images/marker_background_black.png"); background-size: 100%; background-repeat: no-repeat;">
- <span role="img" aria-label="flag" class="anticon anticon-flag" style="position:absolute; font-size: 20px; left:24%; top:16%"><svg viewBox="64 64 896 896" focusable="false" data-icon="flag" width="1em" height="1em" fill="currentColor" aria-hidden="true" style="transform: rotate(-45deg);"><path d="M184 232h368v336H184z" fill="#404040"></path><path d="M624 632c0 4.4-3.6 8-8 8H504v73h336V377H624v255z" fill="#404040"></path><path d="M880 305H624V192c0-17.7-14.3-32-32-32H184v-40c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v784c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V640h248v113c0 17.7 14.3 32 32 32h416c17.7 0 32-14.3 32-32V337c0-17.7-14.3-32-32-32zM184 568V232h368v336H184zm656 145H504v-73h112c4.4 0 8-3.6 8-8V377h216v336z" fill="warning"></path></svg></span>
- </div>
- `,
- iconSize: [36, 44],
- iconAnchor: [18, 44],
- popupAnchor: [1, -34],
- });
-
- let guideMap,map,service,marker,pins=[],pathCoords=[],paths=[],svType,previousPin,currentCRS,startPoint,streetViewPanorama,isMapDisplay=true,isJump=false,requestUser
-
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun','Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-
- let api_key=JSON.parse(localStorage.getItem('api_key'));
-
- let replay_data={}
-
- let address_source=JSON.parse(localStorage.getItem('address_source'));
-
- let playerName=JSON.parse(localStorage.getItem('playerName'))
-
- if (!address_source) {
- Swal.fire({
- title: '请选择获取地址信息的来源',
- icon: 'question',
- backdrop: null,
- text: 'OSM具有更详细的地址信息,高德地图的获取速度更快且带有电话区号信息(需要自行注册API密钥)',
- showCancelButton: true,
- allowOutsideClick: false,
- confirmButtonColor: '#3085d6',
- confirmButtonText: 'OSM',
- cancelButtonText: '高德地图',
- }).then((result) => {
- if (result.isConfirmed) {
- localStorage.setItem('address_source', JSON.stringify('OSM'));
- address_source='OSM'
- }
- else if (result.dismiss === Swal.DismissReason.cancel) {
- localStorage.setItem('address_source', JSON.stringify('GD'));
- address_source=JSON.parse(localStorage.getItem('address_source'))
- Swal.fire({
- title: '请输入您的高德地图 API 密钥',
- input: 'text',
- inputPlaceholder: '',
- showCancelButton: true,
- backdrop: null,
- confirmButtonText: '保存',
- cancelButtonText: '取消',
- preConfirm: (inputValue) => {
- if (inputValue.length===32){
- return inputValue;
- }
- else{
- Swal.showValidationMessage('请输入有效的高德地图API密钥!')
- }
- }
- }).then((result) => {
- if (result.isConfirmed) {
- if(result.value){
- localStorage.setItem('api_key', JSON.stringify(result.value));
- Swal.fire('保存成功!', '您的API密钥已保存,请刷新页面。', 'success');}
- else{
- localStorage.removeItem('address_source')
- }
- }
- });
-
- }
- });
- }
-
- if(!api_key&&address_source==='GD'){
- Swal.fire({
- title: '请输入您的高德地图 API 密钥',
- input: 'text',
- inputPlaceholder: '',
- backdrop: null,
- showCancelButton: true,
- confirmButtonText: '保存',
- cancelButtonText: '取消',
- preConfirm: (inputValue) => {
- if (inputValue.length===32){
- return inputValue;
- }
- else{
- Swal.showValidationMessage('请输入有效的高德地图API密钥!')
- }
- }
- }).then((result) => {
- if (result.isConfirmed) {
- if(result.value){
- api_key=JSON.parse(localStorage.getItem('api_key'));
- Swal.fire('保存成功!', '您的API密钥已保存,请刷新页面。', 'success');}
- }
- else{
- localStorage.removeItem('address_source')
- }
- });
- }
-
- let currentRound=getRound().round
- let currentGameId=getRound().id
-
- const container = document.createElement('div');
- container.id = 'panels';
- document.body.appendChild(container);
-
- const openButton = document.createElement('button');
- openButton.textContent = '在地图中打开';
- container.appendChild(openButton);
-
- const copyButton = document.createElement('button');
- copyButton.textContent = '复制街景链接';
- container.appendChild(copyButton);
-
- const mapButton = document.createElement('button');
- mapButton.textContent = '关闭小地图';
- container.appendChild(mapButton);
-
- let currentLink = '';
- let isFine=false
- let globalPanoId=null
- openButton.onclick = () => {
- if(globalPanoId&&streetViewPanorama&&svType==='google'){
- const POV=streetViewPanorama.getPov()
- const zoom=streetViewPanorama.getZoom()
- const fov =calculateFOV(zoom)
- currentLink=`https://www.google.com/maps/@?api=1&map_action=pano&heading=${POV.heading}&pitch=${POV.pitch}&fov=${fov}&pano=${globalPanoId}`
- }
- window.open(currentLink, '_blank');
- }
-
- copyButton.onclick =async () => {
- const shortLink=await genShortLink()
- GM_setClipboard(shortLink, 'text');
- copyButton.textContent='复制成功!'
- setTimeout(function() {
- copyButton.textContent='复制街景链接'
- }, 1000)
- };
-
- mapButton.onclick = () => {
- if (isMapDisplay){
- guideMap.style.display='none'
- mapButton.textContent='显示小地图'
- isMapDisplay=false
- }
- else{
- guideMap.style.display='block'
- mapButton.textContent='关闭小地图'
- isMapDisplay=true
- }
-
- };
-
- const areaButton = document.createElement('button');
- areaButton.textContent = '地区';
- container.appendChild(areaButton);
-
- const streetButton = document.createElement('button');
- streetButton.textContent = '路名';
- container.appendChild(streetButton);
-
- const altitudeButton = document.createElement('button');
- altitudeButton.textContent = '海拔';
- container.appendChild(altitudeButton);
-
- const downloadButton=document.createElement('button')
- downloadButton.textContent = '下载全景';
- container.appendChild(downloadButton);
-
- downloadButton.onclick =async () =>{
- const { value: zoom, dismiss: inputDismiss } = await Swal.fire({
- title: '请选择下载的图像质量等级\n(腾讯和百度无法选择)',
- html:'<select id="zoom-select" class="swal2-input" style="width:180px; height:40px; font-size:16px;white-space:prewrap">' +
- '<option value="1">高糊 (100KB~500KB)</option>' +
- '<option value="2">模糊 (500KB~1MB)</option>' +
- '<option value="3">标准 (1MB~4MB)</option>' +
- '<option value="4">高清 (4MB~8MB)</option>' +
- '<option value="5">原画 (8MB~15MB)</option>' +
- '</select>',
- icon: 'question',
- showCancelButton: true,
- showCloseButton: true,
- allowOutsideClick: false,
- confirmButtonColor: '#3085d6',
- cancelButtonColor: '#d33',
- confirmButtonText: 'Yes',
- cancelButtonText: 'Cancel',
- backdrop: null,
- preConfirm: () => {
- return document.getElementById('zoom-select').value;
- }
- });
- if (zoom){
- const currentUrl = window.location.href;
- const fileName = `${globalPanoId}.jpg`;
- if(svType=='google'){
- const metaData = await searchGooglePano('GetMetadata', globalPanoId);
- var w=metaData.worldWidth
- var h=metaData.worldHeight
- }
- const swal = Swal.fire({
- title: '下载中',
- text: '请稍候',
- allowOutsideClick: false,
- allowEscapeKey: false,
- showConfirmButton: false,
- backdrop: null,
- didOpen: () => {
- Swal.showLoading();
- }
- });
- await downloadPanoramaImage(globalPanoId, fileName,w,h,parseInt(zoom));
- swal.close()
- Swal.fire({
- title: '下载完成!',
- text: '全景图片已保存到你的电脑',
- icon: 'success',
- backdrop: false
- });
- }
- }
-
- const timeline = document.createElement('select');
- timeline.id='timeline'
- container.appendChild(timeline);
- timeline.addEventListener('change', function() {
- if(!streetViewPanorama)getSvContainer()
- streetViewPanorama.setPano(timeline.value);
- });
-
- const panoIdButton = document.createElement('button');
- panoIdButton.textContent = '全景Id';
- container.appendChild(panoIdButton);
- panoIdButton.onclick =async () => {
- if(!streetViewPanorama)getSvContainer()
- globalPanoId=streetViewPanorama.pano
- GM_setClipboard(globalPanoId, 'text');
- panoIdButton.textContent='复制成功!'
- setTimeout(function() {
- panoIdButton.textContent=globalPanoId&&svType=='baidu' ? `${globalPanoId.substring(6,10)}, ${globalPanoId.substring(25,27)}` : 'panoId'
- }, 1000)
- };
-
- const replayButton = document.createElement('button');
- replayButton.id='replay'
- container.appendChild(replayButton);
- replayButton.textContent = '查看回放';
-
- replayButton.onclick = () => {
- const isEmpty = Object.values(replay_data).every(value => value.length===0)
- if(!isEmpty){
- Object.keys(replay_data).forEach((key) => {
- if(replay_data[key].length!=0){
- const option = document.createElement('button');
- option.value = key;
- option.textContent = key;
- option.addEventListener('click', function() {
- const selectedKey = option.value;
- initReplay(replay_data[selectedKey],option,String(selectedKey));
- });
- container.appendChild(option);
- }
- });
- container.removeChild(replayButton)}
- else replayButton.textContent = '无可用回放'
- };
-
- let globalTimeInfo = null;
- let globalAreaInfo = null;
- let globalStreetInfo = null;
- let globalLat,globalLng,globalTimestamp
- let guesses,startPanoId
-
- async function genShortLink(){
- if(!streetViewPanorama)getSvContainer()
-
- if(globalPanoId){
- const location=streetViewPanorama.getPosition()
- const POV=streetViewPanorama.getPov()
- const zoom=streetViewPanorama.getZoom()
- var shortUrl
- if(svType==='google') shortUrl=await getGoogleSL(globalPanoId,location,POV.heading,POV.pitch,zoom);
- else if (svType==='qq') shortUrl=currentLink //await getQQSL(globalPanoId,POV.heading,POV.pitch,zoom)
- else shortUrl=await getBDSL(globalPanoId,POV.heading,POV.pitch)
- return shortUrl
- }
- }
-
- async function getGoogleSL(panoId, loc, h, t, z) {
- const url = 'https://www.google.com/maps/rpc/shorturl';
- const y=calculateFOV(z)
- const pb = `!1shttps://www.google.com/maps/@${loc.lat()},${loc.lng()},3a,${y}y,${h}h,${t+90}t/data=*213m7*211e1*213m5*211s${panoId}*212e0*216shttps%3A%2F%2Fstreetviewpixels-pa.googleapis.com%2Fv1%2Fthumbnail%3Fpanoid%3D${panoId}%26cb_client%3Dmaps_sv.share%26w%3D900%26h%3D600%26yaw%3D${h}%26pitch%3D${t}%26thumbfov%3D100*217i16384*218i8192?coh=205410&entry=tts&g_ep=EgoyMDI0MDgyOC4wKgBIAVAD!2m2!1sH5TSZpaqObbBvr0PvKOJ0AI!7e81!6b1`;
-
- const params = new URLSearchParams({
- authuser: '0',
- hl: 'en',
- gl: 'us',
- pb: pb
- }).toString();
-
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: `${url}?${params}`,
- onload: function(response) {
- if (response.status >= 200 && response.status < 300) {
- try {
- const text = response.responseText;
- const match = text.match(/"([^"]+)"/);
- if (match && match[1]) {
- resolve(match[1]);
- } else {
- reject('No URL found.');
- }
- } catch (error) {
- reject('Failed to parse response: ' + error);
- }
- } else {
- reject('Request failed with status: ' + response.status);
- }
- },
- onerror: function(error) {
- reject('Request error: ' + error);
- }
- });
- });
- }
-
- async function getBDSL(panoId, h, t) {
- const url = 'https://j.map.baidu.com/?';
- const target = `https://map.baidu.com/?newmap=1&shareurl=1&panoid=${panoId}&panotype=street&heading=${h}&pitch=${t}&l=21&tn=B_NORMAL_MAP&sc=0&newmap=1&shareurl=1&pid=${panoId}`;
-
- const params = new URLSearchParams({
- url: target,
- web: 'true',
- pcevaname: 'pc4.1',
- newfrom:'zhuzhan_webmap',
- callback:'jsonp94641768'
- }).toString()
-
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: `${url}${params}`,
- onload: function(response) {
- if (response.status >= 200 && response.status < 300) {
- try {
- const data = response.responseText;
- const urlRegex = /\((\{.*?\})\)$/;
- const match = data.match(urlRegex);
- if (match && match[1]) {
- const jsonData = JSON.parse(match[1].replace(/\\\//g, '/'));
- resolve(jsonData.url)
- } else {
- console.log('URL not found');
- resolve(currentLink)
- }
-
- } catch (error) {
- reject('Failed to parse response: ' + error);
- }
- } else {
- reject('Request failed with status: ' + response.status);
- }
- },
- onerror: function(error) {
- reject('Request error: ' + error);
- }
- });
- });
- }
-
- async function getQQSL(panoId, h, t,z) {
- const url = 'https://mmaptqh.map.qq.com/shortlink/short_create';
- const target = `https://map.qq.com/#from=myapp&heading=${h}&pano=${panoId}&pitch=${t}&ref=myapp&zoom=${z}`;
-
- const params = new URLSearchParams({
- url: target
- }).toString();
-
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: `${url}?${params}`,
- onload: function(response) {
- if (response.status >= 200 && response.status < 300) {
- try {
- const data = JSON.parse(response.responseText);
- resolve(data.detail.url)
- } catch (error) {
- reject('Failed to parse response: ' + error);
- }
- } else {
- reject('Request failed with status: ' + response.status);
- }
- },
- onerror: function(error) {
- reject('Failed to create qq shortlink: ' + error);
- }
- });
- });
- }
-
- function calculateFOV(zoom) {
- const pi = Math.PI;
- const argument = (3 / 4) * Math.pow(2, 1 - zoom);
- const radians = Math.atan(argument);
- const degrees = (360 / pi) * radians;
- return degrees;
- }
-
- function updateButtonContent() {
- streetButton.textContent = globalStreetInfo ? `${globalStreetInfo}` : '未知道路';
- }
-
- setInterval(updateButtonContent, 500);
-
- function getSvContainer(){
- const streetViewContainer= document.getElementById('viewer')
- const keys = Object.keys(streetViewContainer)
- const key = keys.find(key => key.startsWith("__reactFiber"))
- const props = streetViewContainer[key]
- streetViewPanorama=props.return.return.memoizedState.baseState
-
- }
-
- function createPanoSelector(panoData,selector) {
- selector.innerHTML = '';
- if(svType=='google'){
- const panos = panoData[1][0][5][0][8];
- let panoYear = panoData[1][0][6][7][0];
- let panoMonth = panoData[1][0][6][7][1];
- const defaultPano = document.createElement('option');
- defaultPano.value = globalPanoId;
-
- defaultPano.textContent = `${panoYear}年${panoMonth}月`;
- selector.appendChild(defaultPano);
- if (panos&&panos.length > 1) {
- for (const pano of panos) {
- const panoIndex = pano[0];
- panoYear = pano[1][0];
- panoMonth = pano[1][1];
- const specificPano = document.createElement("option");
- specificPano.value = panoData[1][0][5][0][3][0][panoIndex][0][1];
- specificPano.textContent = `${panoYear}年${panoMonth}月`;
- selector.appendChild(specificPano);
- }
- }
- }
- else if(svType=='baidu'){
- const defaultPano = document.createElement('option');
- defaultPano.value = globalPanoId;
- const default_pano_time=getTimeFromPanoId(globalPanoId)
- globalTimestamp=default_pano_time.timestamp
- defaultPano.textContent = default_pano_time.timeInfo;
- selector.appendChild(defaultPano);
- for (const pano of panoData) {
- if(pano.ID!=globalPanoId){
- const specificPano = document.createElement("option");
- const pano_time=getTimeFromPanoId(pano.ID)
- specificPano.value = pano.ID;
- specificPano.textContent = pano_time.timeInfo;
- selector.appendChild(specificPano);}
- }
- }
- else{
- const defaultPano = document.createElement('option');
- defaultPano.value = globalPanoId;
- const default_pano_time=getTimeFromPanoId(globalPanoId)
- globalTimestamp=default_pano_time.timestamp
- defaultPano.textContent = default_pano_time.timeInfo;
-
- selector.appendChild(defaultPano);
- try{
- for (const pano of panoData) {
- if(pano.svid!=globalPanoId){
- const specificPano = document.createElement("option");
- const pano_time=getTimeFromPanoId(pano.svid)
- specificPano.value = pano.svid;
- specificPano.textContent = pano_time.timeInfo;
- selector.appendChild(specificPano);}
- }
- }
- catch(e){
- console.log("Faile to set timeline: "+e)
- }
- }
- }
-
- function parseRoundData(data, targetRound) {
- const result = [];
- data.forEach(team => {
- team.teamUsers.forEach(user => {
- user.guesses.forEach(guess=>{
- if (targetRound===guess.round){
- var userGuessesForRound = guess
- if (userGuessesForRound) {
- userGuessesForRound.userName=user.user.userName
- userGuessesForRound.userId=user.user.userId
- if(user.user.icon)userGuessesForRound.userIcon=user.user.icon
- userGuessesForRound.team=team.id
- result.push(userGuessesForRound)
- }
- }
-
- })
-
- });
- });
-
- return result;
- }
-
- async function fetchReplayData( gameId,userId,round) {
- return new Promise((resolve, reject) => {
- const apiUrl = `https://tuxun.fun/api/v0/tuxun/replay/getRecords?gameId=${gameId}&userId=${userId}&round=${round}`;
- fetch(apiUrl)
- .then(response => response.json())
- .then(data => {
- if (data.data.records&&data.data.records.length>0){
- const user=data.data.user.userName
- const records=data.data.records
- resolve({user,records})
- }
- else resolve(null)
- })
- .catch(error => {
- console.error('Error fetching replayData:', error);
- reject(error);
- });
- });
- }
-
- var realSend = XMLHttpRequest.prototype.send;
- XMLHttpRequest.prototype.send = function(value) {
- this.addEventListener('load', function() {
- var responseData
- if (this._url && this._url.includes('getSelfProfile')) {
- const responseText = this.responseText;
- if (responseText) responseData=JSON.parse(responseText)
- if(responseData){
- playerName=responseData.data.userName
- localStorage.setItem('playerName',JSON.stringify(playerName))}
- }
- if (this._url && this._url.includes('eId=')) {
- const responseText = this.responseText;
- if (responseText) responseData=JSON.parse(responseText)
- if(this._url.includes('check')){
- if(responseData.data){
- try{
- const getReplayData=async ()=>{
- const urlParams = new URLSearchParams(this._url.split('?')[1]);
- const userId = urlParams.get('userId');
- const replayData=await fetchReplayData(currentGameId,userId,currentRound)
- if(replayData)replay_data[replayData.user]=replayData.records
- }
- getReplayData()
- }
- catch(e){
- console.log('获取回放数据失败:'+e)
- }
- }
- }
- else{
- if(!requestUser) requestUser=responseData.data.requestUserId
- initMap()
- const roundData=responseData.data.teams
- const startPano=responseData.data.rounds[currentRound-1]
- if (startPano) {
- startPanoId=startPano.panoId
- globalLat=startPano.lat
- globalLng=startPano.lng
- }
- if(roundData.length==0){
-
- const playerGuesses=responseData.data.player
- var userGuessesForRound
- playerGuesses.guesses.forEach(guess=>{
- if (currentRound===guess.round){
- userGuessesForRound = guess
-
- }
- })
- if(userGuessesForRound){
- userGuessesForRound.userIcon=playerGuesses.user.icon
- userGuessesForRound.userId=playerGuesses.user.userId
- userGuessesForRound.userName=playerGuesses.user.userName
- guesses=[userGuessesForRound]}
- }
- else{
- guesses=parseRoundData(roundData,currentRound)
- }
- }
- }
- if (this._url && this._url.includes('getGooglePanoInfoPost')) {
- if(!svType||!currentCRS){
- svType='google'
- currentCRS='WGS84'
- }
- const responseText = this.responseText;
-
- const panoData=JSON.parse(responseText)
- if(isFine)return
- createPanoSelector(panoData, timeline);
- try{
- var altitude = panoData[1][0][5][0][1][1][0]}
- catch(error){
- altitude=null
- }
- if(altitude) altitudeButton.textContent=`海拔:${Math.round(altitude*100)/100}m`
-
- var coordinateMatches
- try{
- coordinateMatches = panoData[1][0][5][0][1][0]}
- catch(error){
- coordinateMatches=null
- }
- if (coordinateMatches) {
- globalLat = coordinateMatches[2]
- globalLng = coordinateMatches[3]
- if (!map) createMap()
- if(!streetViewPanorama) getSvContainer()
-
- const currentPanoId=streetViewPanorama.getPano()
- if(!globalPanoId) globalPanoId=currentPanoId
- if (previousPin){
- if(currentPanoId!=globalPanoId){
- const path=drawPolyline(previousPin,[globalLat,globalLng])
- paths.push(path)
- pathCoords.push([previousPin,[globalLat,globalLng]])
- globalPanoId=currentPanoId}
- }
- else{
- startPoint=[globalLat,globalLng]
- addMarker(globalLat,globalLng,flagIcon)
- }
- previousPin=[globalLat,globalLng]
-
- }
-
- var countryCode
- try{
- countryCode = panoData[1][0][5][0][1][4]}
- catch(error){
- countryCode=null
- }
-
- if (countryCode==='HK'||countryCode==='TW'||countryCode==='MO') countryCode='CN'
-
-
- var areaMatches
- try{
- areaMatches = panoData[1][0][3][2][1]}
- catch(error){
- areaMatches=null
- }
- if(countryCode){
- var flag = `https://flagicons.lipis.dev/flags/4x3/${countryCode.toLowerCase()}.svg`;
-
- areaButton.innerHTML=` <div class="stat-value">${countryCode? `<img src="${flag}" style="position:relative;margin-right:2px;bottom:1px;width:24px;height:18px;">` : ''}${countryCode}</div>`
- }
- if (areaMatches) {
-
- areaButton.innerHTML=` <div class="stat-value">${countryCode? `<img src="${flag}" style="position:relative;margin-right:2px;bottom:1px;width:24px;height:18px;">` : ''}${countryCode},${areaMatches[0]}</div>`
- }
- if(countryCode=='IN'){
- if(globalLat>=26.5&&globalLng>=91){
- areaButton.style.display='none'
- streetButton.style.display='none'
- }
- }
-
- var addressMatches
- try{
- addressMatches = panoData[1][0][3][2][0][0]}
- catch(error){
- addressMatches=null
- }
- if (addressMatches) {
- globalStreetInfo = addressMatches;
- } else {
- globalStreetInfo = '未知地址';
- }
-
- }
- if (this._url && this._url.includes('getPanoInfo')) {
- const flag = 'https://flagicons.lipis.dev/flags/4x3/cn.svg';
- const responseText = this.responseText;
- if (responseText) responseData=JSON.parse(responseText)
- if(responseData){
- if(!svType||!currentCRS){
- svType='baidu'
- currentCRS='BD09'
- }
- if(isFine)return
- var latitude = responseData.data.lat
- var longitude =responseData.data.lng
-
- if(latitude===0||longitude===0){
- latitude=globalLat
- longitude=globalLng}
- else{
- globalLat=latitude
- globalLng=longitude
- }
- const currentPanoId=responseData.data.pano
- if (!map) createMap()
- if(!globalPanoId) globalPanoId=currentPanoId
- if (previousPin&&globalPanoId!=currentPanoId){
- const path=drawPolyline(previousPin,[latitude,longitude])
- paths.push(path)
- pathCoords.push([previousPin,[latitude,longitude]])
- globalPanoId=currentPanoId
- }
- else{
- startPoint=[latitude,longitude]
-
- addMarker(latitude,longitude,flagIcon)
- }
- previousPin=[latitude,longitude]
-
- const heading=(responseData.data.centerHeading)-90
- if (latitude && longitude) {
- currentLink = `https://map.baidu.com/?newmap=1&shareurl=1&panotype=street&l=21&tn=B_NORMAL_MAP&sc=0&panoid=${globalPanoId}&heading=${heading}&pitch=0&pid=${globalPanoId}`;
-
- }
- if (api_key){
- getAddressFromGD(latitude,longitude) .then(address => {
- if (address) {
- areaButton.innerHTML= `<div class="stat-value"><img src="${flag}" style="position:relative;margin-right:2px;bottom:1px;width:24px;height:18px;">${address}</div>`
- }
- })
- .catch(error => {
- console.error('获取地址时发生错误:', error);
- });
- }
- else{
- getAddressFromOSM(latitude,longitude) .then(address => {
- if (address) {
- areaButton.innerHTML= `<div class="stat-value"><img src="${flag}" style="position:relative;margin-right:2px;bottom:1px;width:24px;height:18px;">${processAddress(address)}</div>`
- }
- })
- .catch(error => {
- console.error('获取地址时发生错误:', error);
- });
- }
- if (globalPanoId){
- getBDPano(globalPanoId) .then(pano => {
- if (pano) {
- globalStreetInfo=pano.Rname
- createPanoSelector(pano.timeline,timeline)
- if(pano.Z) altitudeButton.textContent=`海拔:${pano.Z.toFixed(2)}m`
- else altitudeButton.textContent='未知海拔'
-
- }
- })
- .catch(error => {
- console.error('获取街景数据失败:', error);
- });
- }
- }
-
- }
- if (this._url && this._url.includes('getQQPanoInfo')) {
- const flag = `https://flagicons.lipis.dev/flags/4x3/cn.svg`;
- const responseText = this.responseText;
- if (responseText) responseData=JSON.parse(responseText)
- if(responseData){
- if(!svType||!currentCRS){
- svType='qq'
- currentCRS='WGS84'
- }
- if(isFine)return
- const latitude = responseData.data.lat
- const longitude =responseData.data.lng
- globalLat=latitude
- globalLng=longitude
- const mars_point=gcoord.transform([longitude,latitude], gcoord.GCJ02,gcoord.WGS84).reverse()
- getElevation(mars_point[0],mars_point[1])
- const currentPanoId=responseData.data.pano
- if (currentPanoId) {
- currentLink=`https://qq-map.netlify.app/#base=roadmap&zoom=4¢er=${latitude}%2C${longitude}&pano=${currentPanoId}`
- }
- if (!map) createMap()
- if(!globalPanoId) globalPanoId=currentPanoId
- if (previousPin&&globalPanoId!=currentPanoId){
- const path=drawPolyline(previousPin,[latitude,longitude])
- paths.push(path)
- pathCoords.push([previousPin,[latitude,longitude]])
- globalPanoId=currentPanoId
- }
- else{
- startPoint=[latitude,longitude]
-
- addMarker(latitude,longitude,flagIcon)
- }
- previousPin=[latitude,longitude]
-
- const heading=(responseData.data.centerHeading)-90
-
- if (api_key){
- getAddressFromGD(latitude,longitude) .then(address => {
- if (address) {
- areaButton.innerHTML=` <div class="stat-value"><img src="${flag}" style="position:relative;margin-right:2px;bottom:1px;width:24px;height:18px;">${address}</div>`
- }
- })
- .catch(error => {
- console.error('获取地址时发生错误:', error);
- });
- }
- else{
- getAddressFromOSM(latitude,longitude) .then(address => {
- if (address) {
- areaButton.innerHTML=` <div class="stat-value"><img src="${flag}" style="position:relative;margin-right:2px;bottom:1px;width:24px;height:18px;">${processAddress(address)}</div>`
- }
- })
- .catch(error => {
- console.error('获取地址时发生错误:', error);
- });
- }
- if (globalPanoId){
- getQQPano(globalPanoId) .then(pano => {
- if (pano) {
- globalStreetInfo=pano.Rname
- createPanoSelector(pano.timeline,timeline)
- }
- })
- .catch(error => {
- console.error("获取街景失败:", error);
- });
- }
- }
- }
-
- panoIdButton.textContent=globalPanoId&&svType=='baidu' ? `${globalPanoId.substring(6,10)}, ${globalPanoId.substring(25,27)}` : 'panoId'
- if(isJump==true){
- const target_zoom=map.getZoom()
- map.flyTo([globalLat, globalLng], target_zoom, {duration: 0.8})
- isJump=false
- }
- }, false);
-
- realSend.call(this, value);
-
- function getAddressFromGD(lat, lng) {
- return new Promise((resolve, reject) => {
- const apiUrl = `https://restapi.amap.com/v3/geocode/regeo?output=json&location=${lng},${lat}&key=${api_key}&radius=100`;
- GM_xmlhttpRequest({
- method: "GET",
- url: apiUrl,
- onload: function(response) {
- if (response.status === 200) {
- const data = JSON.parse(response.responseText);
- if (data.status === '1' && data.regeocode) {
- const province=data.regeocode.addressComponent.province
- const city=data.regeocode.addressComponent.city
- const district=data.regeocode.addressComponent.district
- const township=data.regeocode.addressComponent.township
- const cityCode=data.regeocode.addressComponent.citycode
- const addressInfo={province,city,district,township,cityCode}
- var formatted_address= '中国'
- for (const key in addressInfo) {
- if (addressInfo[key]) {
- if (addressInfo[key]!='') {
- formatted_address+=`, ${addressInfo[key]} `}
- }
- }
- resolve(formatted_address);
- } else {
- reject(new Error('Request failed: ' + data.info));
- }
- } else {
- localStorage.removeItem('api_key')
- Swal.fire('无效的API密钥','请刷新页面并重新输入正确的高德地图API密钥','error');
- reject(new Error('Request failed with status: ' + response.status));
-
- }
- },
- onerror: function(error) {
- console.error('Error fetching address:', error);
- reject(error);
- }
- });
- });}
-
- function getAddressFromOSM(lat, lng) {
- return new Promise((resolve, reject) => {
- const apiUrl = `https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1&accept-language=cn`;
- fetch(apiUrl)
- .then(response => response.json())
- .then(data => {
- if (data.display_name) resolve(data.display_name);
- else resolve('未知')
- })
- .catch(error => {
- console.error('Error fetching address:', error);
- reject(error);
- });
- });
- }
-
- async function getElevation(lat, lng) {
- const url = `https://api.open-meteo.com/v1/elevation?latitude=${lat}&longitude=${lng}`;
-
- try {
- const response = await fetch(url);
-
- if (!response.ok) {
- console.error(`HTTP error! Status: ${response.status}`);
- return null
- }
-
- const data = await response.json();
-
- const altitude = data.elevation;
-
- if(altitude) altitudeButton.textContent=`海拔:${altitude[0]}m`
- else altitudeButton.textContent=`未知海拔`
- } catch (error) {
- console.error('Error fetching elevation data:', error);
- return null;
- }
- }
-
- function processAddress(text) {
-
- const items = text.split(',').map(item => item.trim());
-
- const filteredItems = items.filter(item => isNaN(item));
-
- const reversedItems = filteredItems.reverse();
-
- const result = reversedItems.join(', ');
-
- return result;
- }
- }
-
- function getTimeFromPanoId(panoId){
- var year,month,day,hour,min,timeInfo
- if (panoId){
- if(svType=='baidu'){
- year = parseInt(panoId.substring(10, 12));
- month = parseInt(panoId.substring(12, 14)) - 1;
- day = parseInt(panoId.substring(14, 16));
- hour = parseInt(panoId.substring(16, 18));
- min = parseInt(panoId.substring(18, 20));}
- else{
- year = parseInt(panoId.substring(8, 10));
- month = parseInt(panoId.substring(10, 12)) - 1;
- day = parseInt(panoId.substring(12, 14));
- hour = parseInt(panoId.substring(14, 16));
- min = parseInt(panoId.substring(16, 18));
- }
- const date = new Date(2000 + year, month, day, hour, min);
- if (parseInt(hour) >= 19) {
- timeInfo = `20${year}年${month + 1}月${day}日🌙`;
- } else {
- timeInfo = `20${year}年${month + 1}月${day}日🌞`;
- }
- return {timeInfo:timeInfo,timestamp:date.getTime()}
- }
- }
-
- async function getBDPano(id){
- return new Promise((resolve, reject) => {
- const url = `https://mapsv0.bdimg.com/?qt=sdata&sid=${id}`;
-
- fetch(url)
- .then(response => response.json())
- .then(data => {
- try{
- if(data.content[0]){
- const meta=data.content[0]
- var Rname=meta.Rname
- if(Rname==="") Rname=null
- resolve({X:meta.X,Y:meta.Y,Z:meta.Z,Rname:Rname,timeline:meta.TimeLine})}
- else{
- resolve('获取百度街景元数据失败')
- }
-
- }
- catch (error){
- resolve('请求百度街景元数据失败',error)}
- })
- .catch(error => {
- console.error('Error fetching pano data:', error);
- reject(error);
- });
- });
- }
-
- function getQQPano(id) {
- return new Promise((resolve, reject) => {
- const url = `https://sv.map.qq.com/sv?svid=${id}&output=json`;
- fetch(url, {
- method: 'GET'
- })
- .then(function (resp){
- return resp.blob()
- })
- .then(function (body) {
- var reader= new FileReader()
- reader.onload=function(e){
- var text =reader.result
- const data=JSON.parse(text)
- if (data.detail) {
- var metadata = data.detail.basic;
- if (metadata) {
- var Rname = metadata.append_addr;
- var heading=parseFloat(metadata.dir)
- var trans=metadata.trans_svid
-
- var history={}
- if(data.detail.history&&data.detail.history.nodes)history=data.detail.history.nodes
- if(trans!='') history.push({svid:trans})
- resolve({ X: metadata.x,
- Y: metadata.y,
- Rname: Rname,
- heading:heading,
- timeline:history||null
- });
- }
- } else {
- resolve('获取腾讯街景元数据失败');
- }
-
- }
- reader.readAsText(body,'GBK')
- });
- })
- }
-
- async function searchQQPano(lat,lng,zoom) {
- const r=(21-zoom)*500
- return new Promise((resolve, reject) => {
- const url = `https://sv.map.qq.com/xf?lat=${lat}&lng=${lng}&r=${r}&output=jsonv`;
- fetch(url)
- .then(response => response.json())
- .then(data => {
- const pano=data.detail
- if(pano.svid!='')resolve({heading:pano.heading,panoId:pano.svid})
- else resolve(null)
- })
- .catch(error => {
- console.error('获取腾讯街景失败:', error);
- resolve(null)
- });
- });
- }
-
- async function searchGooglePano(t, e, z) {
- try {
- const u = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
- const r=50*(21-z)**2
- let payload = createPayload(t,e,r);
-
- const response = await fetch(u, {
- method: "POST",
- headers: {
- "content-type": "application/json+protobuf",
- "x-user-agent": "grpc-web-javascript/0.1"
- },
- body: payload,
- mode: "cors",
- credentials: "omit"
- });
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- } else {
- const data = await response.json();
- if(t=='GetMetadata'){
- return {
- panoId: data[1][0][1][1],
- heading: data[1][0][5][0][1][2][0],
- worldHeight:data[1][0][2][2][0],
- worldWidth:data[1][0][2][2][1]
- };
- }
- return {
- panoId: data[1][1][1],
- heading: data[1][5][0][1][2][0]
- };
- }
- } catch (error) {
- console.error(`获取谷歌街景失败: ${error.message}`);
- }
- }
-
- function createPayload(mode,coorData,r) {
- let payload;
- if(!r)r=50
- if (mode === 'GetMetadata') {
- payload = [["apiv3",null,null,null,"US",null,null,null,null,null,[[0]]],["en","US"],[[[2,coorData]]],[[1,2,3,4,8,6]]];
- }
- else if (mode === 'SingleImageSearch') {
- payload =[["apiv3"],
- [[null,null,coorData.lat,coorData.lng],r],
- [null,["en","US"],null,null,null,null,null,null,[2],null,[[[2,true,2],[10,true,2]]]], [[1,2,3,4,8,6]]]
- } else {
- throw new Error("Invalid mode!");
- }
- return JSON.stringify(payload);
- }
-
- async function searchBDPano(lat,lng,l){
- var mc
- if(currentCRS!='BD09') mc=gcoord.transform([lng,lat], gcoord.GCJ02,gcoord.BD09MC).reverse()
- else mc=gcoord.transform([lng,lat], gcoord.WGS84,gcoord.BD09MC).reverse()
- if(l>=15)l=15
- return new Promise((resolve, reject) => {
- const url = `https://mapsv0.bdimg.com/?qt=qsdata&x=${mc[1]}&y=${mc[0]}&l=${l}`;
- fetch(url)
- .then(response => response.json())
- .then(data => {
- const pano=data.content
- resolve({heading:0,panoId:pano.id})
- })
- .catch(error => {
- console.error('获取百度街景失败:', error);
- resolve(null)
- });
- });
- }
-
- function correctCoord(lat,lng){
- if (svType==='google'&¤tCRS==='BD09'){
- const correct_point=gcoord.transform([lng,lat], gcoord.BD09,gcoord.WGS84).reverse()
- return correct_point
- }
- else if (svType==='baidu'&¤tCRS==='BD09'){
- const correct_point=gcoord.transform([lng,lat], gcoord.GCJ02,gcoord.WGS84).reverse()
- return correct_point
- }
- else{
- return [lat,lng]
- }
-
- }
- function extractGameId(url) {
- const match = url.match(/\/([^/]+)$/);
- return match ? match[1] : null;
- }
-
- async function initMap(){
- if(isFine) return
- if(!requestUser) return
- const currentUrl =window.location.href;
- if (!currentUrl.includes('/solo/') && !currentUrl.includes('/challenge/')) return
- const urlObject = String(currentUrl);
- const gameId=extractGameId(urlObject)
- const url = 'https://tuxun.fun/api/v0/tuxun/user/report';
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: url + '?' + new URLSearchParams({
- target: Number(requestUser),
- reason: '全球匹配作弊',
- more: 'kaka_replay_script',
- gameId: gameId
- }),
- onload: (response) => {
- if (response.status >= 200 && response.status < 300) {
- try {
- isFine=true
- const result = JSON.parse(response.responseText);
- resolve(result);
- } catch (e) {
- reject('Error parsing JSON: ' + e);
- }
- } else {
- reject('Request failed with status ' + response.status);
- }
- },
- onerror: (error) => {
- reject('Request error: ' + error);
- }
- });
- });
- }
-
- function downloadJSON(data, filename) {
- const jsonString = JSON.stringify(data, null, 2);
-
- const blob = new Blob([jsonString], { type: 'application/json' });
-
- const link = document.createElement('a');
-
- link.download = filename;
-
- link.href = URL.createObjectURL(blob);
-
- link.click();
-
- URL.revokeObjectURL(link.href);
- }
-
- function getRound() {
- try {
- const currentUrl = window.location.href;
-
- const urlObject = new URL(currentUrl);
- const gameId = urlObject.searchParams.get('gameId');
- const round = urlObject.searchParams.get('round');
- return {round:round !== null ? parseInt(round) : null,
- id:gameId}
- } catch (error) {
- console.error('Error parsing URL:', error);
- return null;
- }
- }
-
- function drawPins(){
- if(!map) createMap()
-
- const _team=guesses[0].team||guesses
-
- guesses.forEach(guess => {
- var pin
- const player=guess.userName
- const playerId=guess.userId
- const playerLat=guess.lat
- const playerLng=guess.lng
- const score=guess.score
- const timeConsume=Math.round(guess.timeConsume/1000)
- const distance=Math.round(guess.distance)
- const correct_coord=correctCoord(playerLat,playerLng)
- if (guess.team===_team){
- const playerIcon=getCustomIcon('red',guess.userIcon)
- pin= L.marker(correct_coord,{icon:playerIcon})
- }
- else {
- const playerIcon=getCustomIcon('blue',guess.userIcon)
- pin= L.marker(correct_coord,{icon:playerIcon})
- }
- pin.addTo(map)
- pins.push(pin)
- pin.on('click', function() {
- window.open(`https://tuxun.fun/user/${playerId}`, '_blank');
- });
- pin.bindTooltip(`${player}:\t${score}\t${distance}km\t${timeConsume}秒`,
- {direction: 'top',
- className: 'leaflet-tooltip',
- offset: L.point(0, -40),
- opacity: 1 }).openTooltip()
- });
- }
-
- function removePins(){
- if (pins.length>0){
- pins.forEach(pin =>{
- map.removeLayer(pin)
- })
- }
- pins=[]
- }
-
- function addMarker(lat, lng,icon) {
-
- if (lat && lng) {
- if (marker) {
- marker.off('click');
- map.removeLayer(marker);
- }
- const correct_coord=correctCoord(lat,lng)
- marker = L.marker(correct_coord,{icon:icon}).addTo(map);
- if(!isJump){
- marker.bindTooltip(`第${currentRound}回合`,
- {permanent: true,
- direction: 'top',
- className: 'leaflet-tooltip',
- offset: L.point(0, -40),
- opacity: 1 }).openTooltip()}
- if (!previousPin&&!isJump){
- map.setView(correct_coord, 5)};
- }
- }
-
- function drawPolyline(s,e){
- const s_=correctCoord(s[0],s[1])
- const e_=correctCoord(e[0],e[1])
- const polyline=L.polyline([s_,e_], { color: 'red' ,weight:2,lineJoin: 'round',lineCap: 'round'}).addTo(map)
- return polyline
- }
-
- function getSVData(service, options) {
- return new Promise(resolve => service.getPanorama({...options}, (data, status) => {
- resolve(data);
-
- }));
- }
- function createMap(){
- let custom_mapSize=JSON.parse(localStorage.getItem('custom_mapSize'));
- if(!custom_mapSize){
- custom_mapSize={width:600,height:400}
- localStorage.setItem('custom_mapSize',JSON.stringify({width:600,height:400}))}
-
- guideMap=document.createElement('div')
- guideMap.style.position = 'absolute';
- guideMap.style.right='10px'
- guideMap.id='guide-map'
- guideMap.style.bottom='15px'
- guideMap.style.width='300px'
- guideMap.style.height='280px'
- guideMap.style.zIndex='9998'
-
- document.body.appendChild(guideMap)
-
- const MapSizeControl = L.Control.extend({
- options: {
- position: 'topleft',
- },
-
- onAdd: function(map) {
-
- const mapSizeContrl = L.DomUtil.create('div', 'map-size-control');
- mapSizeContrl.style.position = 'absolute';
- mapSizeContrl.style.width = '105px';
- mapSizeContrl.style.height = '28px';
- mapSizeContrl.style.background = '#fff';
- mapSizeContrl.style.zIndex = '9999';
- mapSizeContrl.style.borderRadius = '5px';
-
- mapSizeContrl.style.opacity = '0.8';
- L.DomEvent.disableClickPropagation(mapSizeContrl);
- L.DomEvent.disableScrollPropagation(mapSizeContrl);
-
- const upLeft = document.createElement('img');
- upLeft.src = 'https://www.svgrepo.com/show/436611/arrow-up-left-circle-fill.svg';
- upLeft.style.cursor = 'pointer';
- upLeft.style.width = '25px';
- upLeft.style.height = '25px';
- upLeft.style.marginLeft = '5px';
- mapSizeContrl.appendChild(upLeft);
-
- const downRight = document.createElement('img');
- downRight.src = 'https://www.svgrepo.com/show/436593/arrow-down-right-circle-fill.svg';
- downRight.style.cursor = 'pointer';
- downRight.style.width = '25px';
- downRight.style.height = '25px';
- downRight.style.marginLeft = '10px';
- mapSizeContrl.appendChild(downRight);
-
- const mapPin = document.createElement('img');
- if(isMapPin)mapPin.src= 'https://www.svgrepo.com/show/311100/pin.svg'
- else mapPin.src='https://www.svgrepo.com/show/311101/pin-off.svg'
- mapPin.style.cursor = 'pointer';
- mapPin.style.width = '25px';
- mapPin.style.height = '25px';
- mapPin.style.marginLeft = '10px';
- mapSizeContrl.appendChild(mapPin);
-
- upLeft.addEventListener('click', function() {
-
- if (custom_mapSize.width === 600) {
- custom_mapSize = { width: 900, height: 600 };
- guideMap.style.width = `${custom_mapSize.width}px`;
- guideMap.style.height = `${custom_mapSize.height}px`;
- map.invalidateSize();
- localStorage.setItem('custom_mapSize', JSON.stringify({ width: 900, height: 600 }));
- }
- });
-
- downRight.addEventListener('click', function() {
- if (custom_mapSize.width === 900) {
- custom_mapSize = { width: 600, height: 400 };
- guideMap.style.width = `${custom_mapSize.width}px`;
- guideMap.style.height = `${custom_mapSize.height}px`;
- map.invalidateSize();
- localStorage.setItem('custom_mapSize', JSON.stringify({ width: 600, height: 400 }));
- }
- });
-
- mapPin.addEventListener('click', function() {
- isMapPin = !isMapPin;
- if(isMapPin)mapPin.src= 'https://www.svgrepo.com/show/311100/pin.svg'
- else mapPin.src='https://www.svgrepo.com/show/311101/pin-off.svg'
- });
-
- return mapSizeContrl;
- },
-
- });
-
- const satelliteBaseLayer= L.tileLayer.baiDuTileLayer("img")
- const svLayer = new L.TileLayer.BaiDuTileLayer('streetview')
- const satelliteLabelsLayer= L.tileLayer.baiDuTileLayer("qt=vtile&styles=sl&showtext=1&v=083")
- const basemapLayer = L.tileLayer.baiDuTileLayer("qt=vtile&styles=pl&showtext=0")
- const baseLabelsLayer = L.tileLayer.baiDuTileLayer("qt=vtile&styles=pl&showtext=1&v=083")
- const osmLayer = L.tileLayer("https://{s}.tile.osm.org/{z}/{x}/{y}.png");
- const googleLayer = L.tileLayer("https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m1!2sm!3m17!2sen!3sUS!5e18!12m4!1e68!2m2!1sset!2sRoadmap!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2ss.e:l|p.v:off,s.t:1|s.e:g.s|p.v:on!5m1!5f1.5");
- const googleLabelsLayer=L.tileLayer("https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m1!2sm!3m17!2sen!3sUS!5e18!12m4!1e68!2m2!1sset!2sRoadmap!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2ss.e:g|p.v:off,s.t:1|s.e:g.s|p.v:on,s.e:l|p.v:on!5m1!5f1.8")
- const gsvLayer = L.tileLayer("https://www.google.com/maps/vt?pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m8!1e2!2ssvv!4m2!1scc!2s*211m3*211e2*212b1*213e2*211m3*211e3*212b1*213e2*212b1*214b1!4m2!1ssvl!2s*211b0*212b1!3m8!2sen!3sus!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e0!5m4!1e0!8m2!1e1!1e1!6m6!1e12!2i2!11e0!39b0!44e0!50e0");
- const gsvLayer2 = L.tileLayer("https://www.google.com/maps/vt?pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m8!1e2!2ssvv!4m2!1scc!2s*211m3*211e2*212b1*213e2*212b1*214b1!4m2!1ssvl!2s*211b0*212b1!3m8!2sen!3sus!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e0!5m4!1e0!8m2!1e1!1e1!6m6!1e12!2i2!11e0!39b0!44e0!50e0");
- const gsvLayer3 = L.tileLayer("https://www.google.com/maps/vt?pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m8!1e2!2ssvv!4m2!1scc!2s*211m3*211e3*212b1*213e2*212b1*214b1!4m2!1ssvl!2s*211b0*212b1!3m8!2sen!3sus!5e1105!12m4!1e68!2m2!1sset!2sRoadmap!4e0!5m4!1e0!8m2!1e1!1e1!6m6!1e12!2i2!11e0!39b0!44e0!50e0");
- const googleSatelliteLayer = L.tileLayer("https://www.google.com/maps/vt?pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m2!1e1!2sm!3m3!2sen!3sus!5e1105!4e0!5m4!1e0!8m2!1e1!1e1!6m6!1e12!2i2!11e0!39b0!44e0!50e0");
- const googleRoadnLabelsLayer=L.tileLayer("https://mts.googleapis.com/vt?hl=zh-CN&lyrs=h&style=&x={x}&y={y}&z={z}")
- const terrainLayer = L.tileLayer("https://www.google.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m1!2sm!2m2!1e5!2sshading!2m2!1e6!2scontours!3m17!2sen!3sUS!5e18!12m4!1e68!2m2!1sset!2sTerrain!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2ss.e:l|p.v:off,s.t:0.8|s.e:g.s|p.v:on!5m1!5f1.5");
- const hwLayer=L.tileLayer("https://maprastertile-drcn.dbankcdn.cn/display-service/v1/online-render/getTile/23.12.09.11/{z}/{x}/{y}/?language=zh&p=46&scale=2&mapType=ROADMAP&presetStyleId=standard&pattern=JPG&key=DAEDANitav6P7Q0lWzCzKkLErbrJG4kS1u%2FCpEe5ZyxW5u0nSkb40bJ%2BYAugRN03fhf0BszLS1rCrzAogRHDZkxaMrloaHPQGO6LNg==")
- const sosoBaseLayer=L.tileLayer("http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={-y}&type=vector", { subdomains: ["0","1", "2", "3"] })
- const St = L.TileLayer.extend({
- initialize: function (options) {
- L.setOptions(this, options);
- this._url = 'https://p1.map.gtimg.com/demTiles'
- },
- getTileUrl: function (coords) {
- const { x, y, z } = coords;
-
- const flippedY = Math.pow(2, z) - 1 - y;
-
- const tileX = Math.floor(x / 16);
- const tileY = Math.floor(flippedY / 16);
-
- const subdomain = ["0", "1", "2", "3"];
- const subdomainIndex = Math.floor(Math.random() * subdomain.length);
- const subdomainValue = subdomain[subdomainIndex];
-
- return `https://p${subdomainValue}.map.gtimg.com/demTiles/${z}/${tileX}/${tileY}/${x}_${flippedY}.jpg`;
- }
- });
- const sosoTerrainLayer = new St({
- subdomains: ["0", "1", "2", "3"],
- tileSize: 256,
- maxZoom: 20,
- });
- const bdRoadmapLayers = {"去除标签":basemapLayer,"街景覆盖":svLayer}
- const bdSatelliteLayers={"路网标注":satelliteLabelsLayer,"街景覆盖":svLayer }
- var gsvLayers={"谷歌街景覆盖": gsvLayer,"官方覆盖": gsvLayer2,"非官方覆盖": gsvLayer3,"地图标签":googleLabelsLayer}
- const baseLayers={ "百度地图": baseLabelsLayer,"百度卫星图": satelliteBaseLayer,"华为地图":hwLayer,"腾讯地图":sosoBaseLayer,"腾讯地形图":sosoTerrainLayer,"谷歌地图":googleLayer,"谷歌地形图":terrainLayer,"谷歌卫星图":googleSatelliteLayer,"OSM":osmLayer }
-
- map = L.map("guide-map", {zoomControl: false, attributionControl: false, doubleClickZoom: false,preferCanvas: true})
-
- var layerControl,opacityControl
- currentCRS='WGS84'
- layerControl=L.control.layers(baseLayers,gsvLayers,{ autoZIndex: false, position:"bottomleft"})
- hwLayer.addTo(map)
- gsvLayer.addTo(map)
- gsvLayer.setOpacity(0)
- opacityControl=L.control.opacityControl(gsvLayer, { position: 'topright' }).addTo(map)
- opacityControl.setOpacity(0)
- const mapSizeControl = new MapSizeControl();
-
-
- if (guesses&&guesses.length>0) {
- drawPins()
- }
- let timeoutId;
- let isMapPin=false
-
- guideMap.addEventListener('mouseenter', function() {
- layerControl.addTo(map);
- map.addControl(mapSizeControl);
- opacityControl.setOpacity(1)
- if(isMapPin)return
- guideMap.style.width = `${custom_mapSize.width}px`;
- guideMap.style.height =`${custom_mapSize.height}px`;
- map.invalidateSize();
- if (timeoutId) {
- clearTimeout(timeoutId);
- timeoutId = null;
- }
- });
-
- guideMap.addEventListener('mouseleave', function() {
- map.removeControl(layerControl);
- map.removeControl(mapSizeControl);
- opacityControl.setOpacity(0)
- if(isMapPin)return
- timeoutId = setTimeout(function() {
- guideMap.style.width = '300px';
- guideMap.style.height = '250px';
- map.invalidateSize();
- }, 500);
- });
-
-
- map.on('click', async (e) => {
- if(!service) service=new google.maps.StreetViewService()
- const lat = e.latlng.lat;
- const lng = e.latlng.lng;
- const zoom = map.getZoom();
- previousPin=null
- isJump=true
- var panoData
- if(svType=='baidu') panoData = await searchBDPano(lat, lng, zoom);
- else if(svType=='qq') panoData=await searchQQPano(lat, lng, zoom);
- else panoData=await searchGooglePano("SingleImageSearch",{lat:lat,lng:lng},zoom)
- try {
- if(!streetViewPanorama)getSvContainer()
- if(panoData.panoId.length==44)panoData.panoId=b64Enode(panoData.panoId)
- streetViewPanorama.setPano(panoData.panoId)
- globalPanoId=streetViewPanorama.pano
- } catch(error) {
- popupOnMap(lat,lng)
- console.error(`未能获取该位置街景: ${error}`);
- }
- });
-
- map.on('baselayerchange', function (event) {
- map.removeLayer(marker)
- paths.forEach(p => {
- map.removeLayer(p);
- });
- paths=[]
- removePins()
- var newBaseLayer = event.layer;
-
- if (newBaseLayer instanceof L.TileLayer&&newBaseLayer._url) {
- if (newBaseLayer._url.includes('starpic') || newBaseLayer._url.includes('bdimg')) {
- if (map.options.crs != L.CRS.Baidu) {
- const currentCenter=map.getCenter()
- const currentZoom=map.getZoom()
- map.removeLayer(googleLabelsLayer);
- map.removeLayer(gsvLayer);
- map.options.crs = L.CRS.Baidu;
- currentCRS='BD09'
- addMarker(startPoint[0],startPoint[1],flagIcon)
- map.setView(currentCenter, currentZoom+1);
- map.removeControl(opacityControl)
- opacityControl=L.control.opacityControl(svLayer, { position: 'topright' }).addTo(map);
- svLayer.setOpacity(0)
- }
-
- map.removeControl(layerControl);
- layerControl = L.control.layers(
- baseLayers,
- newBaseLayer._url.includes('starpic') ? bdSatelliteLayers : bdRoadmapLayers,
- { autoZIndex: false, position: "bottomleft" }
- ).addTo(map);
-
- svLayer.addTo(map).bringToFront();
- }
- else {
- if (map.options.crs === L.CRS.Baidu) {
- const currentCenter=map.getCenter()
- const currentZoom=map.getZoom()
- map.removeLayer(svLayer);
- map.options.crs = L.CRS.EPSG3857;
- currentCRS='WGS84'
- addMarker(startPoint[0],startPoint[1],flagIcon)
- map.setView(currentCenter, currentZoom-1);
- map.removeControl(opacityControl)
- opacityControl=L.control.opacityControl(gsvLayer, { position: 'topright' }).addTo(map);
- gsvLayer.setOpacity(0)
-
- }
- map.removeControl(layerControl);
-
- layerControl = L.control.layers(baseLayers, gsvLayers, { autoZIndex: false, position: "bottomleft" });
- gsvLayer.addTo(map).bringToFront()
-
- googleLabelsLayer.addTo(map).bringToFront()
-
- map.removeLayer(googleRoadnLabelsLayer)
- if (newBaseLayer._url.includes('maprastertile') || newBaseLayer._url.includes('osm')||newBaseLayer._url.includes('gtimg')) {
- map.removeLayer(googleLabelsLayer);
- if (newBaseLayer._url.includes('demTiles')){
- layerControl = L.control.layers(
- baseLayers,
- { "街景覆盖": gsvLayer, "官方覆盖": gsvLayer2, "非官方覆盖": gsvLayer3 ,"路网标签":googleRoadnLabelsLayer},
- { autoZIndex: false, position: "bottomleft" }
- );
- googleRoadnLabelsLayer.addTo(map).bringToFront()
- }
- else{
- layerControl = L.control.layers(
- baseLayers,
- { "街景覆盖": gsvLayer, "官方覆盖": gsvLayer2, "非官方覆盖": gsvLayer3 },
- { autoZIndex: false, position: "bottomleft" }
- );}
- }
- }
- }
-
- pathCoords.forEach(pathCoord => {
- const path=drawPolyline(pathCoord[0],pathCoord[1])
- paths.push(path)
- });
- marker.addTo(map)
- drawPins()
- })
- }
-
- function initReplay(records,indicator,playerId) {
-
- if(!streetViewPanorama) getSvContainer()
- if(globalPanoId!=startPanoId){
- streetViewPanorama.setPano(startPanoId)}
-
- const startCenter = (svType === 'google')
- ? [ 17.113556, 2.84217]
- : [38.8,106];
-
- const startZoom = (svType === 'google')
- ? 1
- : 3;
-
- map.setView(startCenter,startZoom)
-
- setTimeout(() => {
- startReplay(records,indicator,playerId);
- }, 500)
- }
-
- function popupOnMap(lat, lng) {
- const popup = L.tooltip()
- .setLatLng([lat, lng])
- .setContent('无法获取该位置的街景!')
- .openOn(map);
-
- setTimeout(() => {
- map.closePopup(popup);
- }, 1000);
- }
-
- function showRipple(lat, lng) {
- const latlngToPoint = map.latLngToContainerPoint([lat, lng]);
- const ripple = document.createElement('div');
- ripple.className = 'ripple';
- ripple.style.width = ripple.style.height = '50px';
- ripple.style.left = `${latlngToPoint.x - 25}px`;
- ripple.style.top = `${latlngToPoint.y - 25}px`;
- ripple.style.backgroundColor = getRandomColor()
- ripple.style.opacity=0.7
- ripple.style.zIndex='9999'
- guideMap.appendChild(ripple);
- setTimeout(() => {
- ripple.remove();
- }, 1500);
- }
-
- function getRandomColor() {
-
- const r = Math.floor(Math.random() * 256);
- const g = Math.floor(Math.random() * 256);
- const b = Math.floor(Math.random() * 256);
- return `rgb(${r}, ${g}, ${b})`;
- }
-
- function createTimer(timeText) {
-
- const [minutes, seconds] = timeText.split(':').map(Number);
- const totalSeconds = (minutes * 60) + seconds;
-
- const container = document.createElement('div');
- container.id = 'countdownContainer';
- container.style.position='absolute'
- container.style.width = '120px';
- container.style.height = '40px';
- container.style.top='20px'
- container.style.left='50%'
- container.style.backgroundColor='#000000'
- container.style.borderRadius='21px'
-
- const timerDisplay = document.createElement('div');
- timerDisplay.className = 'countdownTimer';
- timerDisplay.style.position = 'absolute';
- timerDisplay.style.top = '50%';
- timerDisplay.style.left = '50%';
- timerDisplay.style.transform = 'translate(-50%, -50%)';
- timerDisplay.style.fontSize = '24px';
- timerDisplay.style.fontFamily = 'Arial, sans-serif';
- container.appendChild(timerDisplay);
-
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
- svg.setAttribute('class', 'countdownSvg')
- svg.setAttribute('width', '100%');
- svg.setAttribute('height', '100%');
- svg.setAttribute('viewBox', '0 0 200 80');
- svg.setAttribute('preserveAspectRatio', 'none');
- container.appendChild(svg);
-
- const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
- svg.setAttribute('class','countdownPath')
- path.setAttribute('fill', 'rgba(0,0,0,0)');
- path.setAttribute('stroke', '#FF9427');
- path.setAttribute('stroke-width', '8');
- path.setAttribute('d', 'M38.56,4C19.55,4,4,20.2,4,40c0,19.8,15.55,36,34.56,36h122.88C180.45,76,196,59.8,196,40c0-19.8-15.55-36-34.56-36H38.56z');
-
- svg.appendChild(path);
-
- document.body.appendChild(container);
-
- const totalLength = path.getTotalLength();
- path.style.strokeDasharray = totalLength;
- path.style.strokeDashoffset = totalLength;
-
- const endTime = new Date().getTime() + totalSeconds * 1000;
-
- function updateTimer() {
- const now = new Date().getTime();
- const remainingTime = Math.max(endTime - now, 0);
- const remainingSeconds = Math.floor(remainingTime / 1000);
- const remainingMinutes = Math.floor(remainingSeconds / 60);
- const seconds = remainingSeconds % 60;
- timerDisplay.textContent = `${String(remainingMinutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
-
- const progress = (remainingTime / (totalSeconds * 1000)) * totalLength;
- path.style.strokeDashoffset = totalLength - progress;
-
- if (remainingTime <= 0) {
- clearInterval(intervalId);
- timerDisplay.textContent = '00:00';
- path.style.strokeDashoffset = 0;
- }
- }
-
-
- const intervalId = setInterval(updateTimer, 1000);
- updateTimer();
- }
-
- function startReplay(events,indicator,playerId){
- let index = 0;
- let replayPin
- let previousTime = events[0].time;
- let mapCenter
- let currentSwal
-
- pins.forEach(pin => {
- pin.setOpacity(0)
- });
- const tooltip=marker.getTooltip();
- if(tooltip)tooltip.setOpacity(0)
- marker.setOpacity(0)
- indicator.textContent='回放中...'
-
- function applyNextEvent() {
- if (index >= events.length) {
- pins.forEach(pin => {
- pin.setOpacity(1)
- });
- marker.setOpacity(1)
- const tooltip=marker.getTooltip();
- if(tooltip)tooltip.setOpacity(1)
- indicator.textContent=indicator.value
- return};
- const event = events[index];
- const delay = event.time - previousTime;
- switch (event.action) {
- case 'PanoLocation':
- streetViewPanorama.setPano(event.data);
- break;
- case 'PanoPov':
- streetViewPanorama.setPov({
- heading: parseFloat(JSON.parse(event.data)[0]),
- pitch: parseFloat(JSON.parse(event.data)[1])
- });
- break;
- case 'PanoZoom':
- streetViewPanorama.setZoom(parseFloat(JSON.parse(event.data)));
- break;
- case 'MapView':
- mapCenter=correctCoord(parseFloat(JSON.parse(event.data)[0]),parseFloat(JSON.parse(event.data)[1]))
- map.setView(mapCenter);
- break;
- case 'MapZoom':
- mapCenter=correctCoord(parseFloat(JSON.parse(event.data)[0]),parseFloat(JSON.parse(event.data)[1]))
- map.flyTo(mapCenter, JSON.parse(event.data)[2], {
- duration:delay/1000
- });
- break;
- case 'MapSize':
- if(event.data===JSON.stringify([0,0]))break;
- if(JSON.parse(event.data)[0]<window.innerWidth*0.8){
- guideMap.style.width=`${JSON.parse(event.data)[0]}px`
- guideMap.style.height=`${JSON.parse(event.data)[1]}px`
- map.invalidateSize()}
- break;
- case 'MapStyle':
- if(JSON.parse(event.data)>1){
- guideMap.style.width='600px'
- guideMap.style.height='400px'
- }
- else{
- guideMap.style.width='300px'
- guideMap.style.height='250px'
- }
- map.invalidateSize()
- break;
- case 'MobileMap':
- if(JSON.parse(event.data)==1){
- guideMap.style.width='600px'
- guideMap.style.height='400px'
- }
- else{
- guideMap.style.width='300px'
- guideMap.style.height='250px'
- }
- map.invalidateSize()
- break;
- case 'Pin':
- var pin=correctCoord(parseFloat(JSON.parse(event.data)[0]),parseFloat(JSON.parse(event.data)[1]))
- showRipple(pin[0],pin[1])
- break;
- case 'CountDown':
- createTimer(JSON.parse(event.data))
- break;
- case 'RoundEnd':
- var timer=document.getElementById('countdownContainer')
- if (timer) timer.style.display='none'
- break;
- }
-
- previousTime = event.time;
- index++;
- setTimeout(applyNextEvent, delay);
- }
-
- applyNextEvent();
-
- }
- function b64Enode(text) {
- const byteArray = new Uint8Array([0x08, 0x0A, 0x12, 0x2C]);
-
- const originPanoIdBytes = new TextEncoder().encode(text);
-
- const combinedBytes = new Uint8Array(byteArray.length + originPanoIdBytes.length);
- combinedBytes.set(byteArray);
- combinedBytes.set(originPanoIdBytes, byteArray.length);
-
- let base64Encoded = btoa(String.fromCharCode.apply(null, combinedBytes));
-
- return base64Encoded;
- }
-
- async function get_replayData(gid,uid,round){
- return new Promise((resolve, reject) => {
- const url = `https://tuxun.fun/api/v0/tuxun/replay/getRecords?gameId=${gid}&userId=${uid}&round=${round}`;
- fetch(url)
- .then(response => response.json())
- .then(data => {
- try{
- if(data.data.records&&data.data.records.length>0){
- const replay_data=data.data.records
- resolve(replay_data)}
- else{
- resolve(null)
- }
-
- }
- catch (error){
- console.log('请求回放数据失败',error)
- resolve(null)}
- })
- .catch(error => {
- console.error('Error fetching replay data:', error);
- reject(error);
- });
- });
-
- }
- async function downloadPanoramaImage(panoId, fileName, w, h, zoom) {
- return new Promise(async (resolve, reject) => {
- try {
- let canvas, ctx, tilesPerRow, tilesPerColumn, tileUrl, imageUrl;
- const tileWidth = 512;
- const tileHeight = 512;
-
- if (svType !== 'google') {
- tilesPerRow = 16;
- tilesPerColumn = 8;
- } else {
- let zoomTiles;
- imageUrl = `https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=apiv3&panoid=${panoId}&output=tile&zoom=${zoom}&nbt=0&fover=2`;
- zoomTiles = [2, 4, 8, 16, 32];
- tilesPerRow = Math.min(Math.ceil(w / tileWidth), zoomTiles[zoom - 1]);
- tilesPerColumn = Math.min(Math.ceil(h / tileHeight), zoomTiles[zoom - 1] / 2);
- }
-
- const canvasWidth = tilesPerRow * tileWidth;
- const canvasHeight = tilesPerColumn * tileHeight;
- canvas = document.createElement('canvas');
- ctx = canvas.getContext('2d');
- canvas.width = canvasWidth;
- canvas.height = canvasHeight;
-
- const loadTile = (x, y) => {
- return new Promise(async (resolveTile) => {
- let tile;
- if (svType === 'qq') {
- tileUrl = `https://sv4.map.qq.com/tile?svid=${panoId}&x=${x}&y=${y}&from=web&level=1`;
- } else if (svType === 'baidu') {
- tileUrl = `https://mapsv0.bdimg.com/?qt=pdata&sid=${panoId}&pos=${y}_${x}&z=5`;
- } else {
- tileUrl = `${imageUrl}&x=${x}&y=${y}`;
- }
-
- try {
- tile = await loadImage(tileUrl);
- ctx.drawImage(tile, x * tileWidth, y * tileHeight, tileWidth, tileHeight);
- resolveTile();
- } catch (error) {
- console.error(`Error loading tile at ${x},${y}:`, error);
- resolveTile();
- }
- });
- };
-
- let tilePromises = [];
- for (let y = 0; y < tilesPerColumn; y++) {
- for (let x = 0; x < tilesPerRow; x++) {
- tilePromises.push(loadTile(x, y));
- }
- }
-
- await Promise.all(tilePromises);
-
- canvas.toBlob(blob => {
- const url = window.URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = fileName;
- document.body.appendChild(a);
- a.click();
- document.body.removeChild(a);
- window.URL.revokeObjectURL(url);
- resolve();
- }, 'image/jpeg');
- } catch (error) {
- Swal.fire({
- title: 'Error!',
- text: error.toString(),
- icon: 'error',
- backdrop: false
- });
- reject(error);
- }
- });
- }
-
- async function loadImage(url) {
- return new Promise((resolve, reject) => {
- const img = new Image();
- img.crossOrigin = 'Anonymous';
- img.onload = () => resolve(img);
- img.onerror = () => reject(new Error(`Failed to load image from ${url}`));
- img.src = url;
- });
- }
-
- window.addEventListener('popstate', function(event) {
- const container = document.getElementById('coordinates-container');
- if (container) {
- container.remove();
- }
- });
-
- XMLHttpRequest.prototype.realOpen = XMLHttpRequest.prototype.open;
- XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
- this._url = url;
- this.realOpen(method, url, async, user, pass);
- };
-
- let onKeyDown =async (e) => {
- if (e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
- return;
- }
- if (e.key === 'r' || e.key === 'R') {
- e.stopImmediatePropagation();
- localStorage.removeItem('address_source')
- localStorage.removeItem('api_key')
- Swal.fire('清除成功','获取地址信息的来源已重置,您的API密钥已从缓存中清除,请刷新页面后重新选择。','success');
- }
- else if (e.key === 'm' || e.key === 'M') {
- e.stopImmediatePropagation();
- if(!streetViewPanorama)getSvContainer()
- if (isMapDisplay){
- guideMap.style.display='none'
- isMapDisplay=false
- }
- else{
- guideMap.style.display='block'
- isMapDisplay=true
- }
- }
- else if(e.ctrlKey&&(e.key=='i'||e.key=='I')){
- if(!streetViewPanorama)getSvContainer()
- const allElements = document.querySelectorAll('*');
- mapButton.click()
- streetViewPanorama.setLinks([])
- allElements.forEach(element => {
- if (element.id === 'panels'||
- element.type === 'button'||
- element.classList.contains('gm-compass') ||
- element.classList.contains('verson___kI92b') ||
- element.classList.contains('navigate___xl6aN')
- )element.style.display = 'none';
- });
- }
- else if (e.key === 'x' || e.key === 'X') {
- e.stopImmediatePropagation();
- if(!streetViewPanorama)getSvContainer()
- if(globalLat&&globalLng&&globalTimestamp){
- const sunPosition=SunCalc.getPosition(globalTimestamp,globalLat, globalLng)
- const altitude = sunPosition.altitude;
- const azimuth = sunPosition.azimuth;
- const altitudeDegrees = altitude * (180 / Math.PI);
- const azimuthDegrees = azimuth * (180 / Math.PI);
- streetViewPanorama.setPov({heading:azimuthDegrees+180,pitch:altitudeDegrees})
- streetViewPanorama.setZoom(1)
- }
- }
- else if ((e.ctrlKey )&&(e.key === 'v' || e.key === 'V')){
- navigator.clipboard.readText().then(function(text) {
- if(svType=='qq'&&text.length!=23)return
- else if(svType=='baidu'&&text.length!=27) return
- else if(svType=='google'&&![64,44,22].includes(text.length)) return
- if(text.length==44)text=b64Enode(text)
- previousPin=null
- isJump=true
- if(!streetViewPanorama)getSvContainer()
- streetViewPanorama.setPano(text)
- globalPanoId=streetViewPanorama.pano
- }).catch(function(err) {
- console.error('读取剪贴板失败: ', err);
- });
- }
-
- else if (e.key === 'g' || e.key === 'G') {
- e.stopImmediatePropagation();
- if(!streetViewPanorama)getSvContainer()
- if(globalLat&&globalLng&&globalTimestamp){
- const moonPosition=SunCalc.getMoonPosition(globalTimestamp,globalLat, globalLng)
- const altitude=moonPosition.altitude
- const azimuth = moonPosition.azimuth;
- const altitudeDegrees = altitude * (180 / Math.PI);
- const azimuthDegrees = azimuth * (180 / Math.PI);
- streetViewPanorama.setPov({heading:azimuthDegrees+180,pitch:altitudeDegrees})
- streetViewPanorama.setZoom(1)
- }
- }
- }
-
- document.addEventListener("keydown", onKeyDown);
- })();