Geoguessr Map-Making Auto-Tag

Tag your street views by date and address

当前为 2023-09-28 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Geoguessr Map-Making Auto-Tag
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.7
  5. // @description Tag your street views by date and address
  6. // @author KaKa
  7. // @match https://map-making.app/maps/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_setClipboard
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. async function runScript(tags) {
  18.  
  19. const option = confirm('Do you want to input data from the clipboard? If you click "Cancel", you will need to upload a JSON file.');
  20.  
  21. let data;
  22. if (option) {
  23.  
  24. const text = await navigator.clipboard.readText();
  25. try {
  26. data = JSON.parse(text);
  27. } catch (error) {
  28. alert('The input JSON data is invalid or incorrectly formatted.');
  29. return;
  30. }
  31. } else {
  32.  
  33. const input = document.createElement('input');
  34. input.type = 'file';
  35. document.body.appendChild(input);
  36.  
  37. data = await new Promise((resolve) => {
  38. input.addEventListener('change', async () => {
  39. const file = input.files[0];
  40. const reader = new FileReader();
  41.  
  42. reader.onload = (event) => {
  43. try {
  44. const result = JSON.parse(event.target.result);
  45. resolve(result);
  46.  
  47. document.body.removeChild(input);
  48. } catch (error) {
  49. alert('The input JSON data is invalid or incorrectly formatted.');
  50. }
  51. };
  52.  
  53. reader.readAsText(file);
  54. });
  55.  
  56.  
  57. input.click();
  58. });
  59. }
  60. const newData = [];
  61.  
  62.  
  63. async function UE(t, e) {
  64. try {
  65. const r = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
  66. let payload=createPayload(t,e)
  67. const response = await fetch(r, {
  68. method: "POST",
  69. headers: {
  70. "content-type": "application/json+protobuf",
  71. "x-user-agent": "grpc-web-javascript/0.1"
  72. },
  73. body: payload,
  74. mode: "cors",
  75. credentials: "omit"
  76. });
  77.  
  78. if (!response.ok) {
  79. throw new Error(`HTTP error! status: ${response.status}`);
  80. } else {
  81. return await response.json();
  82. }
  83. } catch (error) {
  84. console.error(`There was a problem with the UE function: ${error.message}`);
  85. }
  86. }
  87.  
  88.  
  89. function createPayload(mode,coorData) {
  90. let payload;
  91. if (mode === 'GetMetadata') {
  92. payload = [["apiv3",null,null,null,"US",null,null,null,null,null,[[0]]],["en","US"],[[[2,coorData.panoId]]],[[1,2,3,4,8,6]]];
  93. } else if (mode === 'SingleImageSearch') {
  94. payload =[["apiv3",null,null,null,"US",null,null,null,null,null, [[0]]], [[null,null,coorData.lat,coorData.lng],50], [null,["en","US"],null,null,null,null,null,null,[2],null,[[[2,1,2],[3,1,2],[10,1,2]]]], [[1,2,3,4,8,6]]];
  95. } else {
  96. throw new Error("Invalid mode!");
  97. }
  98. return JSON.stringify(payload);
  99. }
  100.  
  101.  
  102. function getMetaData(svData) {
  103.  
  104. let year = 'nodate';
  105. const match = svData.imageDate.match(/\d{4}/);
  106. if (match) {
  107. year = match[0];
  108. }
  109.  
  110. let panoType = 'Unofficial';
  111. if (svData.copyright.includes('Google')) {
  112. panoType = 'Official';
  113. }
  114.  
  115. let subdivision='nodata'
  116. let locality='nodata'
  117. if(svData.location.description){
  118. let parts = svData.location.description.split(',');
  119. if(parts.length > 1){
  120. subdivision = parts[parts.length-1].trim();
  121. locality = parts[parts.length-2].trim();
  122. } else {
  123. subdivision = svData.location.description;
  124. locality='nodata'
  125. }
  126. }
  127. return [year, panoType,subdivision,locality];
  128. }
  129.  
  130. function getGeneration(svData,country) {
  131. if (svData && svData.tiles) {
  132. if (svData.tiles.worldSize.height === 1664) { // Gen 1
  133. return 'Gen1';
  134. } else if (svData.tiles.worldSize.height === 6656) { // Gen 2 or 3
  135.  
  136. let lat;
  137. for (let key in svData.Sv) {
  138. lat = svData.Sv[key].lat;
  139. break;
  140. }
  141. const date = new Date(svData.imageDate);
  142.  
  143. if ((country === 'BD' && (date >= new Date('2021-04'))) ||
  144. (country === 'EC' && (date >= new Date('2022-03'))) ||
  145. (country === 'FI' && (date >= new Date('2020-09'))) ||
  146. (country === 'IN' && (date >= new Date('2021-10'))) ||
  147. (country === 'LK' && (date >= new Date('2021-02'))) ||
  148. (country === 'NG' && (date >= new Date('2021-06'))) ||
  149. (country === 'US' && lat > 52 && (date >= new Date('2019-01')))) {
  150. return 'Shitcam';
  151. }
  152.  
  153. if ((country === 'AU' )||(country === 'BR' )||(country === 'CA' )||(country === 'CL' )
  154. ||(country === 'JP' )||(country === 'GB' )||(country === 'IE' )||(country === 'NZ' )
  155. ||(country === 'MX' )||(country === 'RU' )||(country === 'US' )||(country === 'IT' )
  156. ||(country === 'DK' )||(country === 'GR' )||(country === 'RO' )||(country === 'PL' )
  157. ||(country === 'CZ' )||(country === 'CH' )||(country === 'SE' )||(country === 'FI' )
  158. ||(country === 'BE' )||(country === 'LU' )||(country === 'NL' )||(country === 'ZA' )
  159. ||(country === 'SG' )||(country === 'TW' )||(country === 'HK' )||(country === 'MO' )
  160. ||(country === 'MC' )||(country === 'SM' )||(country === 'AD' )||(country === 'IM' )
  161. ||(country === 'JE' ) ||(country === 'FR' )||(country === 'DE' )||(country === 'ES' )||
  162. (country === 'PT' )) {
  163.  
  164. return 'Gen2or3';
  165. }
  166. return 'Gen3';
  167. } else if(svData.tiles.worldSize.height === 8192){
  168. return 'Gen4';
  169. }
  170. }
  171. return 'Unofficial';
  172. }
  173.  
  174. var CHUNK_SIZE = 1000;
  175. var promises = [];
  176.  
  177. async function processCoord(coord, tags, svData,ccData) {
  178. if (!coord.extra) {
  179. coord.extra = {};
  180. }
  181. if (!coord.extra.tags) {
  182. coord.extra.tags = [];
  183. }
  184.  
  185. let meta=getMetaData(svData)
  186.  
  187. let yearTag=meta[0]
  188. let typeTag=meta[1]
  189. let subdivisionTag=meta[2]
  190. let localityTag=meta[3]
  191. let countryTag
  192. let genTag
  193. let trekkerTag=svData.gn
  194.  
  195.  
  196. if(trekkerTag||typeTag === 'Unofficial'){
  197. subdivisionTag = 'nodata';
  198. localityTag = 'nodata'
  199. }
  200.  
  201. if (ccData){try {
  202. countryTag = ccData[1][0][5][0][1][4];
  203. } catch (error) {
  204. try {
  205. countryTag = ccData[1][5][0][1][4];
  206. } catch (error) {
  207. countryTag='nodata'
  208. }
  209. }
  210. }
  211.  
  212.  
  213. genTag = getGeneration(svData,countryTag)
  214.  
  215. if (tags.includes('generation')) {
  216. coord.extra.tags.push(genTag);
  217. }
  218. if (tags.includes('year')) {
  219. coord.extra.tags.push(yearTag);
  220. }
  221. if (tags.includes('type')) {
  222. coord.extra.tags.push(typeTag);
  223. }
  224. if (tags.includes('type')&& trekkerTag) {
  225. coord.extra.tags.push('trekker');
  226. }
  227. if (tags.includes('country')&& countryTag) {
  228. coord.extra.tags.push(countryTag);
  229. }
  230.  
  231. if (tags.includes('subdivision')&& subdivisionTag) {
  232. coord.extra.tags.push(subdivisionTag);
  233. }
  234.  
  235. if (tags.includes('locality')&& localityTag) {
  236. coord.extra.tags.push(localityTag);
  237. }
  238.  
  239. newData.push(coord);
  240. }
  241.  
  242. async function processChunk(chunk, tags) {
  243. var service = new google.maps.StreetViewService();
  244. var promises = chunk.map(async coord => {
  245. let panoId = coord.panoId;
  246. let latLng = {lat: coord.lat, lng: coord.lng};
  247. let svData;
  248. let ccData;
  249.  
  250. if ((panoId || latLng)) {
  251. svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});
  252. }
  253.  
  254. if (!panoId && (tags.includes('generation')||('country'))) {
  255. ccData = await UE('SingleImageSearch', coord);
  256. } else if (panoId) {
  257. ccData = await UE('GetMetadata', coord);
  258. }
  259.  
  260. await processCoord(coord, tags, svData,ccData)
  261. });
  262.  
  263. await Promise.all(promises);
  264. }
  265.  
  266. function getSVData(service, options, language) {
  267. return new Promise(resolve => service.getPanorama({...options}, (data, status) => {
  268. resolve(data);
  269. }));
  270. }
  271.  
  272.  
  273. async function processData(tags) {
  274. try {
  275.  
  276. for (let i = 0; i < data.customCoordinates.length; i += CHUNK_SIZE) {
  277. let chunk = data.customCoordinates.slice(i, i + CHUNK_SIZE);
  278. await processChunk(chunk, tags);
  279. }
  280.  
  281. GM_setClipboard(JSON.stringify(newData));
  282. alert("New JSON data has been copied to the clipboard!");
  283. } catch (error) {
  284. alert("Invalid JSON data");
  285. console.error('Error processing JSON data:', error);
  286. }
  287. }
  288. processData(tags);
  289. }
  290.  
  291. var mainButtonContainer = document.createElement('div');
  292. mainButtonContainer.style.position = 'fixed';
  293. mainButtonContainer.style.right = '20px';
  294. mainButtonContainer.style.bottom = '20px';
  295. document.body.appendChild(mainButtonContainer);
  296.  
  297. var buttonContainer = document.createElement('div');
  298. buttonContainer.style.position = 'fixed';
  299. buttonContainer.style.right = '20px';
  300. buttonContainer.style.bottom = '60px';
  301. buttonContainer.style.display = 'none';
  302. document.body.appendChild(buttonContainer);
  303.  
  304. function createCheckbox(text, tags) {
  305. var label = document.createElement('label');
  306. var checkbox = document.createElement('input');
  307. checkbox.type = 'checkbox';
  308. checkbox.value = text;
  309. checkbox.name = 'tags';
  310. checkbox.id = tags;
  311. label.appendChild(checkbox);
  312. label.appendChild(document.createTextNode(text));
  313. buttonContainer.appendChild(label);
  314. return checkbox;
  315. }
  316.  
  317. var triggerButton = document.createElement('button');
  318. triggerButton.textContent = 'Star Tagging';
  319. triggerButton.addEventListener('click', function() {
  320. var checkboxes = document.getElementsByName('tags');
  321. var checkedTags = [];
  322. for (var i=0; i<checkboxes.length; i++) {
  323. if (checkboxes[i].checked) {
  324. checkedTags.push(checkboxes[i].id);
  325. }
  326. }
  327. runScript(checkedTags);
  328. });
  329. buttonContainer.appendChild(triggerButton);
  330.  
  331. var mainButton = document.createElement('button');
  332. mainButton.textContent = 'Auto-Tag';
  333. mainButton.addEventListener('click', function() {
  334. if (buttonContainer.style.display === 'none') {
  335. buttonContainer.style.display = 'block';
  336. } else {
  337. buttonContainer.style.display = 'none';
  338. }
  339. });
  340. mainButtonContainer.appendChild(mainButton);
  341.  
  342. createCheckbox('Year', 'year');
  343. createCheckbox('Type', 'type');
  344. createCheckbox('Country', 'country');
  345. createCheckbox('Subdivision', 'subdivision');
  346. createCheckbox('Locality', 'locality');
  347. createCheckbox('Generations', 'generation');
  348. })();