Geoguessr Map-Making Auto-Tag

Tag your street views by date and address

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

  1. // ==UserScript==
  2. // @name Geoguessr Map-Making Auto-Tag
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.5
  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. let api_key = GM_getValue("api_key");
  19. if (!api_key) {
  20. api_key = prompt("Please enter your Google API key");
  21. GM_setValue("api_key", api_key);
  22. }
  23.  
  24. const option = confirm('Do you want to input data from the clipboard? If you click "Cancel", you will need to upload a JSON file.');
  25.  
  26. let data;
  27. if (option) {
  28.  
  29. const text = await navigator.clipboard.readText();
  30. try {
  31. data = JSON.parse(text);
  32. } catch (error) {
  33. alert('The input JSON data is invalid or incorrectly formatted.');
  34. return;
  35. }
  36. } else {
  37.  
  38. const input = document.createElement('input');
  39. input.type = 'file';
  40. document.body.appendChild(input);
  41.  
  42. data = await new Promise((resolve) => {
  43. input.addEventListener('change', async () => {
  44. const file = input.files[0];
  45. const reader = new FileReader();
  46.  
  47. reader.onload = (event) => {
  48. try {
  49. const result = JSON.parse(event.target.result);
  50. resolve(result);
  51.  
  52. document.body.removeChild(input);
  53. } catch (error) {
  54. alert('The input JSON data is invalid or incorrectly formatted.');
  55. }
  56. };
  57.  
  58. reader.readAsText(file);
  59. });
  60.  
  61.  
  62. input.click();
  63. });
  64. }
  65. const newData = [];
  66.  
  67. async function getMetaData(url) {
  68. try {
  69. const response = await fetch(url);
  70. const data = await response.json();
  71. if (data.status == "OK") {
  72. let year = 'nodate';
  73. const match = data.date.match(/\d{4}/);
  74. if (match) {
  75. year = match[0];
  76. }
  77. let panoType = 'unofficial';
  78. if (data.copyright.includes('Google')) {
  79. panoType = 'official';
  80. }
  81. return [year, panoType];
  82. } else {
  83. return ["nodata","nodata"];
  84. }
  85. } catch (error) {
  86. console.error(`Error fetching metadata: ${error}`);
  87. }
  88. }
  89.  
  90. function get_Meta(id) {
  91. const url = `https://maps.googleapis.com/maps/api/streetview/metadata?pano=${id}&key=${api_key}`;
  92. return getMetaData(url);
  93. }
  94.  
  95. function search_Meta(lat, lng) {
  96. const url = `https://maps.googleapis.com/maps/api/streetview/metadata?location=${lat},${lng}&key=${api_key}`;
  97. return getMetaData(url);
  98. }
  99.  
  100. let last_token = null;
  101. let last_token_expiry = 0;
  102.  
  103. async function get_Token(api_key) {
  104. let current_time = Date.now() / 1000;
  105. if (last_token && last_token_expiry > current_time) {
  106. return last_token;
  107. }
  108. let url = `https://tile.googleapis.com/v1/createSession?key=${api_key}`;
  109. let headers = {'Content-Type': 'application/json'};
  110. let data = { "mapType": "streetview",
  111. "language": "en-US",
  112. "region": "US"};
  113. let response = await fetch(url, {method: 'POST', headers: headers, body: JSON.stringify(data)});
  114. if (response.status == 200) {
  115. let token = (await response.json()).session;
  116. last_token_expiry = current_time + 5 * 60;
  117. last_token = token;
  118. return token;
  119. } else {
  120. alert(`Error: ${response.status}, ${await response.text()}`);
  121. }
  122. }
  123.  
  124. async function getAddress(url) {
  125. let country = 'nodata';
  126. let subdivision = 'nodata';
  127. let locality = 'nodata';
  128. try {
  129. let response = await fetch(url);
  130. if (response.status == 200) {
  131. let data = await response.json();
  132. for (let add of data.addressComponents) {
  133. if (add.types.includes('country')) {
  134. country = add.longName;
  135. }
  136. if (add.types.includes('administrative_area_level_1')) {
  137. subdivision = add.longName;
  138. }
  139. if (add.types.includes('locality')) {
  140. locality = add.longName;
  141. }
  142. }
  143. }
  144. } catch (error) {
  145. console.log(error);
  146. }
  147. return [country, subdivision, locality];
  148. }
  149.  
  150. async function get_add(id) {
  151. let tk = await get_Token(api_key);
  152. let url = `https://tile.googleapis.com/v1/streetview/metadata?session=${tk}&key=${api_key}&panoId=${id}`;
  153. return getAddress(url);
  154. }
  155.  
  156. async function search_add(lat,lng) {
  157. let tk = await get_Token(api_key);
  158. let url = `https://tile.googleapis.com/v1/streetview/metadata?session=${tk}&key=${api_key}&lat=${lat}&lng=${lng}&radius=50`;
  159. return getAddress(url);
  160. }
  161.  
  162.  
  163. var CHUNK_SIZE = 1000;
  164. var promises = [];
  165.  
  166. async function processCoord(coord, tags, svData) {
  167. if (!coord.extra) {
  168. coord.extra = {};
  169. }
  170. if (!coord.extra.tags) {
  171. coord.extra.tags = [];
  172. }
  173. var meta;
  174. var address;
  175. if (coord.panoId && (tags.includes('year') || tags.includes('type'))) {
  176. meta = await get_Meta(coord.panoId);
  177. } else if (tags.includes('year') || tags.includes('type')) {
  178. meta = await search_Meta(coord.lat, coord.lng);
  179. }
  180.  
  181. if (coord.panoId && (tags.includes('country') || tags.includes('subdivision') || tags.includes('locality'))) {
  182. address= await get_add(coord.panoId);
  183. } else if (tags.includes('country') || tags.includes('subdivision') || tags.includes('locality')) {
  184. address= await search_add(coord.lat, coord.lng);
  185. }
  186.  
  187. if (meta && meta.length >= 2) {
  188. var year_tag = meta[0];
  189. var type_tag = meta[1];
  190.  
  191. if (tags.includes('year')) coord.extra.tags.push(year_tag);
  192. if (tags.includes('type')) coord.extra.tags.push(type_tag);
  193. }
  194.  
  195. if (address && address.length >= 3) {
  196. var country_tag=address[0]
  197. var subdivision_tag=address[1]
  198. var locality_tag=address[2]
  199.  
  200. if (tags.includes('country')) coord.extra.tags.push(country_tag);
  201. if (tags.includes('subdivision')) coord.extra.tags.push(subdivision_tag);
  202. if (tags.includes('locality')) coord.extra.tags.push(locality_tag);
  203. }
  204.  
  205. if (svData) {
  206.  
  207. let trekkerTag = (svData.hasOwnProperty('dn') && svData.dn) ? 'trekker' : null;
  208.  
  209. if (tags.includes('type') && trekkerTag) {
  210. coord.extra.tags.push(trekkerTag);
  211. }
  212. }
  213.  
  214. newData.push(coord);
  215. }
  216.  
  217. async function processChunk(chunk, tags) {
  218. var service = new google.maps.StreetViewService();
  219. var promises = chunk.map(async coord => {
  220. let panoId = coord.panoId;
  221. let latLng = {lat: coord.lat, lng: coord.lng};
  222. let svData;
  223.  
  224. if ((panoId || latLng) && tags.includes('type')) {
  225. svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});
  226. }
  227.  
  228. await processCoord(coord, tags, svData)
  229. });
  230.  
  231. await Promise.all(promises);
  232. }
  233.  
  234. function getSVData(service, options) {
  235. return new Promise(resolve => service.getPanorama(options, (data, status) => {
  236. resolve(data);
  237. }));
  238. }
  239.  
  240. async function processData(tags) {
  241. try {
  242.  
  243. for (let i = 0; i < data.customCoordinates.length; i += CHUNK_SIZE) {
  244. let chunk = data.customCoordinates.slice(i, i + CHUNK_SIZE);
  245. await processChunk(chunk, tags);
  246. }
  247.  
  248. GM_setClipboard(JSON.stringify(newData));
  249. alert("New JSON data has been copied to the clipboard!");
  250. } catch (error) {
  251. alert("Invalid JSON data");
  252. console.error('Error processing JSON data:', error);
  253. }
  254. }
  255. processData(tags);
  256. }
  257.  
  258. var mainButtonContainer = document.createElement('div');
  259. mainButtonContainer.style.position = 'fixed';
  260. mainButtonContainer.style.right = '20px';
  261. mainButtonContainer.style.bottom = '20px';
  262. document.body.appendChild(mainButtonContainer);
  263.  
  264. var buttonContainer = document.createElement('div');
  265. buttonContainer.style.position = 'fixed';
  266. buttonContainer.style.right = '20px';
  267. buttonContainer.style.bottom = '60px';
  268. document.body.appendChild(buttonContainer);
  269.  
  270. function createButton(text, tags) {
  271. var button = document.createElement('button');
  272. button.textContent = text;
  273. button.style.display = 'none';
  274. button.addEventListener('click', async function() { await runScript(tags); });
  275. buttonContainer.appendChild(button);
  276. return button;
  277. }
  278.  
  279. var mainButton = document.createElement('button');
  280. mainButton.textContent = 'Auto-Tag';
  281. mainButton.addEventListener('click', function() {
  282.  
  283. for (var i = 0; i < buttonContainer.children.length; i++) {
  284. var button = buttonContainer.children[i];
  285. if (button.style.display === 'none') {
  286. button.style.display = 'block';
  287. } else {
  288. button.style.display = 'none';
  289. }
  290. }
  291. });
  292. mainButtonContainer.appendChild(mainButton);
  293.  
  294.  
  295.  
  296. createButton('Tag by Year', ['year']);
  297. createButton('Tag by Type',[ 'type']);
  298. createButton('Tag by Country', ['country']);
  299. createButton('Tag by Subdivision', ['subdivision']);
  300. createButton('Tag by Locality', ['locality']);
  301. })();