Geoguessr Map-Making Auto-Tag

Tag your street views by date, exactTime, address, generation, elevation

当前为 2024-06-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Geoguessr Map-Making Auto-Tag
  3. // @namespace https://www.geoguessr.com/user/6494f9bbab07ca3ea843220f
  4. // @version 3.80
  5. // @description Tag your street views by date, exactTime, address, generation, elevation
  6. // @author KaKa
  7. // @match https://map-making.app/maps/*
  8. // @grant GM_setClipboard
  9. // @grant GM_xmlhttpRequest
  10. // @require https://cdn.jsdelivr.net/npm/sweetalert2@11
  11. // @license MIT
  12. // @icon https://www.svgrepo.com/show/423677/tag-price-label.svg
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. let accuracy=60 /* You could modifiy accuracy here, default setting is 1 minutes */
  18. let tagBox = ['Year', 'Month','Day', 'Time','Country', 'Subdivision', 'Locality', 'Generation', 'Elevation','Type']
  19. let mapData
  20. getMap().then((map) =>{
  21. mapData=map
  22.  
  23. })
  24. function getMap() {
  25. return new Promise(function(resolve, reject) {
  26. var requestURL = window.location.origin + "/api" + window.location.pathname + "/locations";
  27.  
  28. GM_xmlhttpRequest({
  29. method: "GET",
  30. url: requestURL,
  31. onload: function(response) {
  32.  
  33. if (response.status === 200) {
  34.  
  35. try {
  36. var jsonData = JSON.parse(response.responseText);
  37. resolve(jsonData);
  38. } catch (error) {
  39. console.error("Error parsing JSON:", error);
  40.  
  41. reject("Error parsing JSON data!");
  42. }
  43. } else {
  44.  
  45. console.error("HTTP Error:", response.status);
  46. reject("HTTP Error!");
  47. }
  48. },
  49. onerror: function(error) {
  50.  
  51. console.error("Request Error:", error);
  52. reject("Error fetching meta data of the map!");
  53. }
  54. });
  55. });
  56. }
  57.  
  58. function extractTags() {
  59. var elements = document.querySelectorAll('label.selection-row__label');
  60. var tags = [];
  61. elements.forEach(function(labelElement) {
  62. var labelText = labelElement.textContent.trim();
  63. if(labelText.includes('Everything')){
  64. tags.push(labelText)
  65. }
  66. else{
  67. var tagText = labelText.split('Tag:')[1].trim();
  68. tags.push(tagText)};
  69. });
  70. return tags;
  71. }
  72.  
  73. function extractSelection(locs, tags) {
  74. if (!locs || locs.length === 0) {
  75. Swal.fire('Selection not found', 'please select one location for auto-tagging at least!', 'warning');
  76. }
  77. if( tags.includes('Everything')){
  78. return locs
  79. }
  80.  
  81. var result = [];
  82. locs.forEach(function(loc) {
  83. if (loc.tags && loc.tags.length > 0) {
  84. loc.tags.forEach(function(tag) {
  85. if (tags.includes(tag)) {
  86. result.push(loc);
  87. }
  88. });
  89. }
  90. });
  91. return result;
  92. }
  93.  
  94. async function getSelection(){
  95. const locs= await getMap()
  96. const tags=extractTags()
  97. const selection=extractSelection(locs,tags)
  98. return selection
  99. }
  100.  
  101. function updateSelection(entries) {
  102. var requestURL = window.location.origin + "/api" + window.location.pathname + "/locations";
  103. var payload = {
  104. edits: []
  105. };
  106.  
  107. entries.forEach(function(entry) {
  108. var createEntry = {
  109. id: -1,
  110. author: entry.author,
  111. mapId: entry.mapId,
  112. location: entry.location,
  113. panoId: entry.panoId,
  114. panoDate: entry.panoDate,
  115. heading: entry.heading,
  116. pitch: entry.pitch,
  117. zoom: entry.zoom,
  118. tags: entry.tags,
  119. flags: entry.flags,
  120. createdAt: entry.createdAt,
  121.  
  122. };
  123. payload.edits.push({
  124. action: {
  125. type: 3
  126. },
  127. create: [createEntry],
  128. remove: [entry.id]
  129. });
  130.  
  131.  
  132. });
  133.  
  134. var xhr = new XMLHttpRequest();
  135. xhr.open("POST", requestURL);
  136. xhr.setRequestHeader("Content-Type", "application/json");
  137.  
  138. xhr.onload = function() {
  139. if (xhr.status >= 200 && xhr.status < 300) {
  140. console.log("Request succeeded");
  141. } else {
  142. console.error("Request failed with status", xhr.status);
  143. }
  144. };
  145.  
  146. xhr.onerror = function() {
  147. console.error("Request failed");
  148. };
  149.  
  150. xhr.send(JSON.stringify(payload));
  151. }
  152.  
  153. async function runScript(tags,sR,uN) {
  154. let taggedLocs=[];
  155. let selections
  156.  
  157. if(!selections){
  158. selections=await getSelection()
  159. }
  160.  
  161. function mergeList(list1, list2) {
  162. const mergedDict = list1.reduce((dict, currentValue, index) => {
  163.  
  164. if (list2[index] !== undefined) {
  165. dict[currentValue] = list2[index];
  166. }
  167. return dict;
  168. }, {});
  169.  
  170. return mergedDict;
  171. }
  172.  
  173. async function UE(t, e, s, d) {
  174.  
  175. try {
  176. const r = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
  177. let payload = createPayload(t, e);
  178. var lat=parseFloat(e.location.lat.toFixed(6))
  179. var lng=parseFloat(e.location.lng.toFixed(6))
  180.  
  181. if(d){
  182. payload=JSON.stringify([["apiv3"],[[null,null,lat,lng],10],[[null,null,null,null,null,null,null,null,null,null,[s,d]],null,null,null,null,null,null,null,[2],null,[[[2,true,2]]]],[[2,6]]])
  183. }
  184. const response = await fetch(r, {
  185. method: "POST",
  186. headers: {
  187. "content-type": "application/json+protobuf",
  188. "x-user-agent": "grpc-web-javascript/0.1"
  189. },
  190. body: payload,
  191. mode: "cors",
  192. credentials: "omit"
  193. });
  194.  
  195. if (!response.ok) {
  196. throw new Error(`HTTP error! status: ${response.status}`);
  197. } else {
  198. return await response.json();
  199. }
  200. } catch (error) {
  201. console.error(`There was a problem with the UE function: ${error.message}`);
  202. }
  203. }
  204.  
  205. function createPayload(mode,coorData) {
  206. let payload;
  207. if (mode === 'GetMetadata') {
  208. payload = [["apiv3",null,null,null,"US",null,null,null,null,null,[[0]]],["en","US"],[[[2,coorData]]],[[1,2,3,4,8,6]]];
  209. } else if (mode === 'SingleImageSearch') {
  210. payload =[["apiv3",null,null,null,"US",null,null,null,null,null, [[0]]],
  211. [[null,null,coorData.location.lat,coorData.location.lng],50],
  212. [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]]];
  213. } else {
  214. throw new Error("Invalid mode!");
  215. }
  216. return JSON.stringify(payload);
  217. }
  218.  
  219. function monthToTimestamp(m) {
  220.  
  221. const [year, month] = m.split('-');
  222.  
  223. const startDate =Math.round( new Date(year, month-1,1).getTime()/1000);
  224.  
  225. const endDate =Math.round( new Date(year, month, 1).getTime()/1000)-1;
  226.  
  227. return { startDate, endDate };
  228. }
  229.  
  230. async function binarySearch(c, start,end) {
  231. let capture
  232. let response
  233. while (end - start >= accuracy) {
  234. let mid= Math.round((start + end) / 2);
  235. response = await UE("SingleImageSearch", c, start,end);
  236. if (response&&response[0][2]== "Search returned no images." ){
  237. start=mid+start-end
  238. end=start-mid+end
  239. mid=Math.round((start+end)/2)
  240. } else {
  241. start=mid
  242. mid=Math.round((start+end)/2)
  243. }
  244. capture=mid
  245. }
  246.  
  247. return capture
  248. }
  249.  
  250. function getMetaData(svData) {
  251. if (svData) {
  252. let levelId=svData.dn
  253. let year = 'noyear',month = 'nomonth'
  254. let panoType='Unofficial'
  255. let subdivision='nosub',locality='nolocality'
  256. let coverageCount='0'
  257. if (svData.imageDate) {
  258. const matchYear = svData.imageDate.match(/\d{4}/);
  259. if (matchYear) {
  260. year = matchYear[0];
  261. }
  262.  
  263. const matchMonth = svData.imageDate.match(/-(\d{2})/);
  264. if (matchMonth) {
  265. month = matchMonth[1];
  266. }
  267. }
  268.  
  269. if (svData.copyright.includes('Google')) {
  270. panoType = 'Official';
  271. }
  272. if (svData.time){
  273. coverageCount = svData.time.length.toString();
  274. }
  275. if(svData.location.description){
  276. let parts = svData.location.description.split(',');
  277. if(parts.length > 1){
  278. subdivision = parts[parts.length-1].trim();
  279. locality = parts[parts.length-2].trim();
  280. } else {
  281. subdivision = svData.location.description;
  282.  
  283. }
  284. }
  285. return [year,month,panoType,subdivision,locality,levelId,coverageCount]
  286. }
  287.  
  288. else{
  289. return null}
  290. }
  291.  
  292. function getGeneration(svData,country) {
  293.  
  294. if (svData&&svData.tiles) {
  295. if (svData.tiles.worldSize.height === 1664) { // Gen 1
  296. return 'Gen1';
  297. } else if (svData.tiles.worldSize.height === 6656) { // Gen 2 or 3
  298.  
  299. let lat;
  300. for (let key in svData.Sv) {
  301. lat = svData.Sv[key].lat;
  302. break;
  303. }
  304.  
  305. let date;
  306. if (svData.imageDate) {
  307. date = new Date(svData.imageDate);
  308. } else {
  309. date = 'nodata';
  310. }
  311.  
  312. if (date!=='nodata'&&((country === 'BD' && (date >= new Date('2021-04'))) ||
  313. (country === 'EC' && (date >= new Date('2022-03'))) ||
  314. (country === 'FI' && (date >= new Date('2020-09'))) ||
  315. (country === 'IN' && (date >= new Date('2021-10'))) ||
  316. (country === 'LK' && (date >= new Date('2021-02'))) ||
  317. (country === 'KH' && (date >= new Date('2022-10'))) ||
  318. (country === 'LB' && (date >= new Date('2021-05'))) ||
  319. (country === 'NG' && (date >= new Date('2021-06'))) ||
  320. (country === 'ST') ||
  321. (country === 'US' && lat > 52 && (date >= new Date('2019-01'))))) {
  322. return 'Shitcam';
  323. }
  324.  
  325. let gen2Countries = ['AU', 'BR', 'CA', 'CL', 'JP', 'GB', 'IE', 'NZ', 'MX', 'RU', 'US', 'IT', 'DK', 'GR', 'RO',
  326. 'PL', 'CZ', 'CH', 'SE', 'FI', 'BE', 'LU', 'NL', 'ZA', 'SG', 'TW', 'HK', 'MO', 'MC', 'SM',
  327. 'AD', 'IM', 'JE', 'FR', 'DE', 'ES', 'PT'];
  328. if (gen2Countries.includes(country)) {
  329.  
  330. return 'Gen2or3';
  331. }
  332. else{
  333. return 'Gen3';}
  334. }
  335. else if(svData.tiles.worldSize.height === 8192){
  336. return 'Gen4';
  337. }
  338. }
  339. return 'Unknown';
  340. }
  341.  
  342. async function getLocal(coord, timestamp) {
  343. const apiUrl = "https://api.wheretheiss.at/v1/coordinates/";
  344. const systemTimezoneOffset = -new Date().getTimezoneOffset() * 60;
  345.  
  346. try {
  347. const [lat, lng] = coord;
  348. const url = `${apiUrl}${lat},${lng}`;
  349.  
  350. const responsePromise = new Promise((resolve, reject) => {
  351. GM_xmlhttpRequest({
  352. method: "GET",
  353. url: url,
  354. responseType: "json",
  355. onload: function(response) {
  356. if (response.status >= 200 && response.status < 300) {
  357. resolve(response.response);
  358. } else {
  359. reject(new Error("Request failed: " + response.statusText));
  360. }
  361. },
  362. onerror: function(error) {
  363. reject(new Error("There was a network error: " + error));
  364. }
  365. });
  366. });
  367.  
  368. const data = await responsePromise;
  369. const targetTimezoneOffset = data.offset * 3600;
  370. const offsetDiff = systemTimezoneOffset - targetTimezoneOffset;
  371. const convertedTimestamp = Math.round(timestamp - offsetDiff);
  372. return convertedTimestamp;
  373. } catch (error) {
  374. throw error;
  375. }
  376. }
  377.  
  378. async function getElevation(locations) {
  379. function findRange(elevation, ranges) {
  380. for (let i = 0; i < ranges.length; i++) {
  381. const range = ranges[i];
  382. if (elevation >= range.min && elevation <= range.max) {
  383. return `${range.min}-${range.max}m`;
  384. }
  385. }
  386. if (!elevation) {
  387. return 'noElevation';
  388. }
  389. return `${JSON.stringify(elevation)}m`;
  390. }
  391.  
  392. const batchSize = 100;
  393. const totalBatches = Math.ceil(locations.length / batchSize);
  394. const promises = [];
  395.  
  396. for (let i = 0; i < totalBatches; i++) {
  397. const batchLocations = locations.slice(i * batchSize, (i + 1) * batchSize);
  398. const coordinates = batchLocations.map(loc => `${loc.location.lat},${loc.location.lng}`).join('|');
  399. const url = `https://api.open-elevation.com/api/v1/lookup?locations=${coordinates}`;
  400.  
  401. const promise = new Promise((resolve, reject) => {
  402. GM_xmlhttpRequest({
  403. method: "GET",
  404. url: url,
  405. responseType: "json",
  406. onload: function(response) {
  407. try {
  408. const data = JSON.parse(response.responseText);
  409. if (data && data.results && data.results.length > 0) {
  410. const elevations = data.results.map(result => result.elevation);
  411. batchLocations.forEach((location, index) => {
  412. if (location.tags) {
  413. if (sR) {
  414. const range = findRange(elevations[index], sR);
  415. location.tags.push(`${range}`);
  416. } else {
  417. location.tags.push(`${JSON.stringify(elevations[index])}m`);
  418. }
  419. } else {
  420. location.tags = [(sR ? `${findRange(elevations[index], sR)}m` : `${JSON.stringify(elevations[index])}m`)]
  421.  
  422. }
  423. });
  424. } else {
  425. batchLocations.forEach(location => {
  426. if (location.tags) {
  427. location.tags.push('noElevation');
  428. } else {
  429. location.tags=['noElevation']
  430. }
  431. });
  432. }
  433. resolve();
  434. } catch (error) {
  435. reject(error);
  436. }
  437. },
  438. onerror: function(error) {
  439. reject(new Error("There was a network error: " + error));
  440. }
  441. });
  442. });
  443. promises.push(promise);
  444. }
  445.  
  446. await Promise.all(promises);
  447. return locations;
  448. }
  449.  
  450. var CHUNK_SIZE = 1200;
  451. var promises = [];
  452.  
  453. async function processCoord(coord, tags, svData,ccData) {
  454. if (svData){
  455. let meta=getMetaData(svData)
  456. let yearTag=meta[0]
  457. let monthTag=meta[1]
  458. let typeTag=meta[2]
  459. let subdivisionTag=meta[3]
  460. let localityTag=meta[4]
  461. let countryTag
  462. let genTag
  463. let trekkerTag=meta[5]
  464. let coverageTag=meta[6]
  465. let dayTag,timeTag,exactTime,timeRange
  466. var date=monthToTimestamp(meta[0]+'-'+meta[1])
  467.  
  468. if(tags.includes('day')||tags.includes('time')){
  469. exactTime=await binarySearch(coord, date.startDate,date.endDate)
  470. if (exactTime<=date.startDate||exactTime>=date.endDate){
  471. exactTime=null
  472. }
  473. }
  474.  
  475. if(!exactTime){dayTag='noday'
  476. timeTag='notime'
  477. }
  478. else{
  479.  
  480. const currentDate = new Date();
  481. const currentOffset =-(currentDate.getTimezoneOffset())*60
  482. const dayOffset = currentOffset-Math.round((coord.location.lng / 15) * 3600);
  483. const LocalDay=new Date(Math.round(exactTime-dayOffset)*1000)
  484. dayTag = LocalDay.toISOString().split('T')[0];
  485.  
  486. if(tags.includes('time')) {
  487.  
  488. var localTime=await getLocal([coord.location.lat,coord.location.lng],exactTime)
  489. var timeObject=new Date(localTime*1000)
  490. timeTag =`${timeObject.getHours().toString().padStart(2, '0')}:${timeObject.getMinutes().toString().padStart(2, '0')}:${timeObject.getSeconds().toString().padStart(2, '0')}`;
  491. var hour = timeObject.getHours();
  492.  
  493. if (hour < 11) {
  494. timeRange = 'Morning';
  495. } else if (hour >= 11 && hour < 13) {
  496. timeRange = 'Noon';
  497. } else if (hour >= 13 && hour < 17) {
  498. timeRange = 'Afternoon';
  499. } else if(hour >= 17 && hour < 19) {
  500. timeRange = 'Evening';
  501. }
  502. else{
  503. timeRange = 'Night';
  504. }
  505. }
  506. }
  507. if (ccData){
  508. try {
  509. countryTag = ccData[1][0][5][0][1][4]
  510. }
  511. catch (error) {
  512. try {
  513. countryTag = ccData[1][5][0][1][4]
  514. } catch (error) {
  515. countryTag='nocountry'
  516. }
  517. }
  518. if (!countryTag)countryTag='nocountry'
  519. }
  520.  
  521. if (tags.includes('generation')&&typeTag=='Official'){
  522. genTag = getGeneration(svData,countryTag)
  523. coord.tags.push(genTag)}
  524.  
  525. if (tags.includes('year'))coord.tags.push(yearTag)
  526.  
  527. if (tags.includes('month'))coord.tags.push(yearTag.slice(-2)+'-'+monthTag)
  528.  
  529. if (tags.includes('day'))coord.tags.push(dayTag)
  530.  
  531. if (tags.includes('time')) coord.tags.push(timeTag)
  532.  
  533. if (tags.includes('time')&&timeRange) coord.tags.push(timeRange)
  534.  
  535. if (tags.includes('type'))coord.tags.push(typeTag)
  536.  
  537. if (tags.includes('type')&&trekkerTag&&typeTag=='Official')coord.tags.push('trekker')
  538.  
  539. if (tags.includes('country')&&typeTag=='Official')coord.tags.push(countryTag)
  540.  
  541. if (tags.includes('subdivision')&&typeTag=='Official')coord.tags.push(subdivisionTag)
  542.  
  543. if (tags.includes('locality')&&typeTag=='Official')coord.tags.push(localityTag)
  544.  
  545. if (tags.includes('coverageCount')&&typeTag=='Official')coord.tags.push(coverageTag)
  546. }
  547.  
  548. else {
  549. if(tags.some(tag => tagBox.includes(tag))){
  550. coord.tags.push('nopano')
  551. }
  552. }
  553.  
  554.  
  555. if (coord.tags) {coord.tags=Array.from(new Set(coord.tags))}
  556. taggedLocs.push(coord);
  557. }
  558.  
  559. async function processChunk(chunk, tags) {
  560. if (tags.includes('elevation')){
  561. try {
  562. chunk = await getElevation(chunk);
  563. } catch (error) {
  564. console.error('error fecthing elevtion data:', error);
  565. }
  566. }
  567. var service = new google.maps.StreetViewService();
  568. var promises = chunk.map(async coord => {
  569. let panoId = coord.panoId;
  570. let latLng = {lat: coord.location.lat, lng: coord.location.lng};
  571. let svData;
  572. let ccData;
  573.  
  574. if ((panoId || latLng)) {
  575. if(tags!=['elevation']){
  576. svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});}
  577. }
  578. if (!panoId && (tags.includes('generation')||('country'))) {
  579. ccData = await UE('SingleImageSearch', coord);
  580. } else if (panoId && (tags.includes('generation')||('country'))) {
  581. ccData = await UE('GetMetadata', panoId);
  582. }
  583.  
  584. await processCoord(coord, tags, svData,ccData)
  585. });
  586. await Promise.all(promises);
  587.  
  588. }
  589.  
  590. function getSVData(service, options) {
  591. return new Promise(resolve => service.getPanorama({...options}, (data, status) => {
  592. resolve(data);
  593. }));
  594. }
  595.  
  596. async function processData(tags) {
  597. try {
  598. const totalChunks = Math.ceil(selections.length / CHUNK_SIZE);
  599. let processedChunks = 0;
  600.  
  601. const swal = Swal.fire({
  602. title: 'Processing Data',
  603. text: 'Please wait...',
  604. allowOutsideClick: false,
  605. allowEscapeKey: false,
  606. showConfirmButton: false,
  607. didOpen: () => {
  608. Swal.showLoading();
  609. }
  610. });
  611.  
  612. for (let i = 0; i < selections.length; i += CHUNK_SIZE) {
  613. let chunk = selections.slice(i, i + CHUNK_SIZE);
  614. await processChunk(chunk, tags);
  615. processedChunks++;
  616.  
  617. const progress = Math.min((processedChunks / totalChunks) * 100, 100);
  618. Swal.update({
  619. html: `<div>${progress.toFixed(2)}% completed</div>
  620. <div class="swal2-progress">
  621. <div class="swal2-progress-bar" role="progressbar" aria-valuenow="${progress}" aria-valuemin="0" aria-valuemax="100" style="width: ${progress}%;">
  622. </div>
  623. </div>`
  624. });
  625. }
  626.  
  627. updateSelection(taggedLocs)
  628. swal.close();
  629. Swal.fire({
  630. title: 'Success!',
  631. text: 'Tagging completed! Do you want to refresh the page?',
  632. icon: 'success',
  633. showCancelButton: true,
  634. confirmButtonColor: '#3085d6',
  635. cancelButtonColor: '#d33',
  636. confirmButtonText: 'Refresh'
  637. }).then((result) => {
  638. if (result.isConfirmed) {
  639. location.reload();
  640. }
  641. });
  642. } catch (error) {
  643. swal.close();
  644. Swal.fire('Error!', 'Invalid JSON data','error');
  645. console.error('Error processing JSON data:', error);
  646. }
  647. }
  648.  
  649. if(selections){
  650. if(selections.length>=1){processData(tags);}
  651. else{
  652. Swal.fire('Error Parsing JSON Data!', 'The input JSON data is empty! If you update the map after the page is loaded, please save it and refresh the page before tagging','error');}
  653. }else{Swal.fire('Error Parsing JSON Data!', 'The input JSON data is invaild or incorrectly formatted.','error');}
  654. }
  655.  
  656. function createCheckbox(text, tags) {
  657. var label = document.createElement('label');
  658. var checkbox = document.createElement('input');
  659. checkbox.type = 'checkbox';
  660. checkbox.value = text;
  661. checkbox.name = 'tags';
  662. checkbox.id = tags;
  663. label.appendChild(checkbox);
  664. label.appendChild(document.createTextNode(text));
  665. buttonContainer.appendChild(label);
  666. return checkbox;
  667. }
  668.  
  669. var mainButton = document.createElement('button');
  670. mainButton.textContent = 'Auto-Tag';
  671. mainButton.style.position = 'fixed';
  672. mainButton.style.right = '20px';
  673. mainButton.style.bottom = '20px';
  674. mainButton.style.borderRadius = "18px";
  675. mainButton.style.fontSize = "16px";
  676. mainButton.style.padding = "10px 20px";
  677. mainButton.style.border = "none";
  678. mainButton.style.color = "white";
  679. mainButton.style.cursor = "pointer";
  680. mainButton.style.backgroundColor = "#4CAF50";
  681. mainButton.addEventListener('click', function () {
  682. if (buttonContainer.style.display === 'none') {
  683. buttonContainer.style.display = 'block';
  684. } else {
  685. buttonContainer.style.display = 'none';
  686. }
  687. });
  688. document.body.appendChild(mainButton);
  689.  
  690. var buttonContainer = document.createElement('div');
  691. buttonContainer.style.position = 'fixed';
  692. buttonContainer.style.right = '20px';
  693. buttonContainer.style.bottom = '60px';
  694. buttonContainer.style.display = 'none';
  695. buttonContainer.style.fontSize = '16px';
  696. document.body.appendChild(buttonContainer);
  697.  
  698. var selectAllCheckbox = document.createElement('input');
  699. selectAllCheckbox.type = 'checkbox';
  700. selectAllCheckbox.id = 'selectAll';
  701. selectAllCheckbox.addEventListener('change', function () {
  702. var checkboxes = document.getElementsByName('tags');
  703. for (var i = 0; i < checkboxes.length; i++) {
  704. checkboxes[i].checked = this.checked;
  705. }
  706. });
  707. var selectAllLabel = document.createElement('label');
  708. selectAllLabel.textContent = 'Select All';
  709. selectAllLabel.htmlFor = 'selectAll';
  710.  
  711.  
  712.  
  713. var triggerButton = document.createElement('button');
  714. triggerButton.textContent = 'Star Tagging';
  715. triggerButton.addEventListener('click', function () {
  716. var checkboxes = document.getElementsByName('tags');
  717. var checkedTags = [];
  718. for (var i = 0; i < checkboxes.length; i++) {
  719. if (checkboxes[i].checked) {
  720. checkedTags.push(checkboxes[i].id);
  721. }
  722. }
  723. if (checkedTags.includes('elevation')) {
  724. Swal.fire({
  725. title: 'Set A Range For Elevation',
  726. text: 'If you select "Cancel", the script will return the exact elevation for each location.',
  727. icon: 'question',
  728. showCancelButton: true,
  729. showCloseButton: true,
  730. allowOutsideClick: false,
  731. confirmButtonColor: '#3085d6',
  732. cancelButtonColor: '#d33',
  733. confirmButtonText: 'Yes',
  734. cancelButtonText: 'Cancel'
  735. }).then((result) => {
  736. if (result.isConfirmed) {
  737. Swal.fire({
  738. title: 'Define Range for Each Segment',
  739. html: `
  740. <label> <br>Enter range for each segment, separated by commas</br></label>
  741. <textarea id="segmentRanges" class="swal2-textarea" placeholder="such as:-1-10,11-35"></textarea>
  742. `,
  743. icon: 'question',
  744. showCancelButton: true,
  745. showCloseButton: true,
  746. allowOutsideClick: false,
  747. focusConfirm: false,
  748. preConfirm: () => {
  749. const segmentRangesInput = document.getElementById('segmentRanges').value.trim();
  750. if (!segmentRangesInput) {
  751. Swal.showValidationMessage('Please enter range for each segment');
  752. return false;
  753. }
  754. const segmentRanges = segmentRangesInput.split(',');
  755. const validatedRanges = segmentRanges.map(range => {
  756. const matches = range.trim().match(/^\s*(-?\d+)\s*-\s*(-?\d+)\s*$/);
  757. if (matches) {
  758. const min = Number(matches[1]);
  759. const max = Number(matches[2]);
  760. return { min, max };
  761. } else {
  762. Swal.showValidationMessage('Invalid range format. Please use format: minValue-maxValue');
  763. return false;
  764. }
  765. });
  766. return validatedRanges.filter(Boolean);
  767. },
  768. confirmButtonColor: '#3085d6',
  769. cancelButtonColor: '#d33',
  770. confirmButtonText: 'Yes',
  771. cancelButtonText: 'Cancel',
  772. inputValidator: (value) => {
  773. if (!value.trim()) {
  774. return 'Please enter range for each segment';
  775. }
  776. }
  777. }).then((result) => {
  778. if (result.isConfirmed) {
  779. runScript(checkedTags, result.value)
  780. } else {
  781. Swal.showValidationMessage('You canceled input');
  782. }
  783. });
  784. }
  785. else if (result.dismiss === Swal.DismissReason.cancel) {
  786. runScript(checkedTags)
  787. }
  788. });
  789. }
  790. else {
  791. runScript(checkedTags)
  792. }
  793. })
  794. buttonContainer.appendChild(triggerButton);
  795. buttonContainer.appendChild(selectAllCheckbox);
  796. buttonContainer.appendChild(selectAllLabel);
  797. tagBox.forEach(tag => {
  798. createCheckbox(tag, tag.toLowerCase());
  799. });
  800.  
  801. })();