Geoguessr Map-Making Auto-Tag

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

目前為 2024-07-02 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Geoguessr Map-Making Auto-Tag
// @namespace    https://gf.qytechs.cn/users/1179204
// @version      3.85.4
// @description  Tag your street views by date, exactTime, address, generation, elevation
// @author       KaKa
// @match        https://map-making.app/maps/*
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @license      MIT
// @icon         https://www.svgrepo.com/show/423677/tag-price-label.svg
// ==/UserScript==

(function() {
    'use strict';
    let accuracy=60 /* You could modifiy accuracy here, default setting is 60s */
    let tagBox = ['Year', 'Month','Day', 'Time','Country', 'Subdivision', 'Locality', 'Generation', 'Elevation','Type','Fix','Detect']
    let months = ['January', 'February', 'March', 'April', 'May', 'June','July', 'August', 'September', 'October', 'November', 'December'];
    let mapData

    function getMap() {
        return new Promise(function(resolve, reject) {
            var requestURL = window.location.origin + "/api" + window.location.pathname + "/locations";

            fetch(requestURL)
                .then(function(response) {
                if (!response.ok) {
                    throw new Error('HTTP error, status = ' + response.status);
                }
                return response.json();
            })
                .then(function(jsonData) {
                resolve(jsonData);
            })
                .catch(function(error) {
                console.error('Fetch Error:', error);
                reject('Error fetching meta data of the map!');
            });
        });
    }

    async function getSelection() {
        return new Promise((resolve, reject) => {
            var exportButtonText = 'Export';
            var buttons = document.querySelectorAll('button.button');

            for (var i = 0; i < buttons.length; i++) {
                if (buttons[i].textContent.trim() === exportButtonText) {
                    buttons[i].click();
                    var modalDialog = document.querySelector('.modal__dialog.export-modal');
                }
            }

            setTimeout(() => {
                const radioButton = document.querySelector('input[type="radio"][name="selection"][value="1"]');
                const spanText = radioButton.nextElementSibling.textContent.trim();
                if (spanText==="Export selection (0 locations)") {
                    swal.fire('Selection not found!', 'Please select at least one location as selection!','warning')
                    reject(new Error('Export selection is empty!'));
                }
                if (radioButton) radioButton.click()
                else{
                    reject(new Error('Radio button not found'));}
            }, 100);


            setTimeout(() => {
                const copyButton = document.querySelector('.export-modal__export-buttons button:first-of-type');
                if (!copyButton) {
                    reject(new Error('Copy button not found'));
                }
                copyButton.click();

            }, 200);
            setTimeout(() => {
                const closeButton = document.querySelector('.modal__close');
                if (closeButton) closeButton.click();
                else reject(new Error('Close button not found'));
            }, 400);

            setTimeout(async () => {
                try {
                    const data = await navigator.clipboard.readText()
                    const selection = JSON.parse(data);
                    resolve(selection);
                } catch (error) {
                    console.error("Error getting selection:", error);
                    reject(error);
                }
            }, 800);
        });
    }

    function matchSelection(selection, locations) {
        const matchingLocations = [];
        const customCoordinates = selection.customCoordinates;

        const locationSet = new Set(locations.map(loc => JSON.stringify(loc.location)));

        for (const coord of customCoordinates) {
            const coordString = JSON.stringify({ lat: coord.lat, lng: coord.lng });

            if (locationSet.has(coordString)) {
                const matchingLoc = locations.find(loc => JSON.stringify(loc.location) === coordString);
                if (matchingLoc) {
                    matchingLocations.push(matchingLoc);
                }
            }
        }
        return matchingLocations;
    }

    function findRange(elevation, ranges) {
        for (let i = 0; i < ranges.length; i++) {
            const range = ranges[i];
            if (elevation >= range.min && elevation <= range.max) {
                return `${range.min}-${range.max}m`;
            }
        }
        if (!elevation) {
            return 'noElevation';
        }
        return `${JSON.stringify(elevation)}m`;
    }

    function updateSelection(entries) {
        var requestURL = window.location.origin + "/api" + window.location.pathname + "/locations";
        var payload = {
            edits: []
        };

        entries.forEach(function(entry) {
            var createEntry = {
                id: -1,
                author: entry.author,
                mapId: entry.mapId,
                location: entry.location,
                panoId: entry.panoId,
                panoDate: entry.panoDate,
                heading: entry.heading,
                pitch: entry.pitch,
                zoom: entry.zoom,
                tags: entry.tags,
                flags: entry.flags,
                createdAt: entry.createdAt,

            };
            payload.edits.push({
                action: {
                    type: 3
                },
                create: [createEntry],
                remove: [entry.id]
            });
        });

        var xhr = new XMLHttpRequest();
        xhr.open("POST", requestURL);
        xhr.setRequestHeader("Content-Type", "application/json");

        xhr.onload = function() {
            if (xhr.status >= 200 && xhr.status < 300) {
                console.log("Request succeeded");
            } else {
                console.error("Request failed with status", xhr.status);
            }
        };

        xhr.onerror = function() {
            swal.fire({
                icon: 'error',
                title: 'Oops...',
                text: 'Failed to update the map! Please retrieve JSON data from your clipboard.'
            });
        };

        xhr.send(JSON.stringify(payload));
    }

    async function runScript(tags,sR) {
        let taggedLocs=[];
        let exportMode,selections,fixStrategy

        if (tags.length<1){
            swal.fire('Feature not found!', 'Please select at least one feature!','warning')
            return}
        if (tags.includes('fix')){
            const { value: fixOption,dismiss: fixDismiss } = await Swal.fire({
                title:'Fix Strategy',
                icon:'question',
                text: 'Would you like to fix the location based on the map-making data. (more suitable for those locs with a specific date coverage) Else it will update the broken loc with recent coverage.',
                showCancelButton: true,
                showCloseButton:true,
                allowOutsideClick: false,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: 'Yes',
                cancelButtonText: 'No',

            })
            if(fixOption)fixStrategy='exactly'
            else if(!fixOption&&fixDismiss==='cancel'){
                fixStrategy=null
            }
            else{
                return
            }
        };

        const { value: option,dismiss: inputDismiss } = await Swal.fire({
            title: 'Export',
            text: 'Do you want to update and save your map? If you click "Cancel", the script will just paste JSON data to the clipboard after finish tagging.',
            icon: 'question',
            showCancelButton: true,
            showCloseButton:true,
            allowOutsideClick: false,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: 'Yes',
            cancelButtonText: 'Cancel'
        });

        if (option) {
            exportMode='save'
        }
        else if(!selections&&inputDismiss==='cancel'){
            exportMode=null
        }
        else{
            return
        }
        const loadingSwal = Swal.fire({
            title: 'Preparing',
            text: 'Fetching selected locs from map-making. Please wait...',
            allowOutsideClick: false,
            allowEscapeKey: false,
            showConfirmButton: false,
            icon:"info",
            didOpen: () => {
                Swal.showLoading();
            }
        });
        const selectedLocs=await getSelection()
        mapData=await getMap()
        selections=await matchSelection(selectedLocs,mapData)
        loadingSwal.close()
        async function UE(t, e, s, d) {
            try {
                const r = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
                let payload = createPayload(t, e,s,d);

                const response = await fetch(r, {
                    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 {
                    return await response.json();
                }
            } catch (error) {
                console.error(`There was a problem with the UE function: ${error.message}`);
            }
        }

        function createPayload(mode,coorData,s,d,r) {
            let payload;
            if(!r)r=50 // default search radius
            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') {
                var lat = coorData.lat;
                var lng = coorData.lng;
                lat = lat % 1 !== 0 && lat.toString().split('.')[1].length >6 ? parseFloat(lat.toFixed(6)) : lat;
                lng = lng % 1 !== 0 && lng.toString().split('.')[1].length > 6 ? parseFloat(lng.toFixed(6)) : lng;
                if(s&&d){
                    payload=[["apiv3"],[[null,null,lat,lng],r],[[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]]]
                }else{
                    payload =[["apiv3",null,null,null,"US",null,null,null,null,null, [[0]]],
                              [[null,null,lat,lng],r],
                              [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]]];}
            } else {
                throw new Error("Invalid mode!");
            }
            return JSON.stringify(payload);
        }

        function monthToTimestamp(m) {

            const [year, month] = m.split('-');

            const startDate =Math.round( new Date(year, month-1,1).getTime()/1000);

            const endDate =Math.round( new Date(year, month, 1).getTime()/1000)-1;

            return { startDate, endDate };
        }

        async function binarySearch(c, start,end) {
            let capture
            let response
            while (end - start >= accuracy) {
                let mid= Math.round((start + end) / 2);
                response = await UE("SingleImageSearch", c, start,end,10);
                if (response&&response[0][2]== "Search returned no images." ){
                    start=mid+start-end
                    end=start-mid+end
                    mid=Math.round((start+end)/2)
                } else {
                    start=mid
                    mid=Math.round((start+end)/2)
                }
                capture=mid
            }

            return capture
        }

        function getMetaData(svData) {
            let year = 'Year not found',month = 'Month not found'
            let panoType='unofficial'
            let subdivision='Subdivision not found',locality='Locality not found'
            if (svData) {
                if (svData.imageDate) {
                    const matchYear = svData.imageDate.match(/\d{4}/);
                    if (matchYear) {
                        year = matchYear[0];
                    }

                    const matchMonth = svData.imageDate.match(/-(\d{2})/);
                    if (matchMonth) {
                        month = matchMonth[1];
                    }
                }
                if (svData.copyright.includes('Google')) {
                    panoType = 'Official';
                }

                if(svData.location.description){
                    let parts = svData.location.description.split(',');
                    if(parts.length > 1){
                        subdivision = parts[parts.length-1].trim();
                        locality = parts[parts.length-2].trim();
                    } else {
                        subdivision = svData.location.description;

                    }
                }
            return [year,month,panoType,subdivision,locality]
            }
            else{
                return null
            }
        }

        function getGeneration(svData,country) {
            if (svData&&svData.tiles) {
                if (svData.tiles.worldSize.height === 1664) { // Gen 1
                    return 'Gen1';
                } else if (svData.tiles.worldSize.height === 6656) { // Gen 2 or 3

                    let lat;
                    for (let key in svData.Sv) {
                        lat = svData.Sv[key].lat;
                        break;
                    }

                    let date;
                    if (svData.imageDate) {
                        date = new Date(svData.imageDate);
                    } else {
                        date = 'nodata';
                    }

                    if (date!=='nodata'&&((country === 'BD' && (date >= new Date('2021-04'))) ||
                                          (country === 'EC' && (date >= new Date('2022-03'))) ||
                                          (country === 'FI' && (date >= new Date('2020-09'))) ||
                                          (country === 'IN' && (date >= new Date('2021-10'))) ||
                                          (country === 'LK' && (date >= new Date('2021-02'))) ||
                                          (country === 'KH' && (date >= new Date('2022-10'))) ||
                                          (country === 'LB' && (date >= new Date('2021-05'))) ||
                                          (country === 'NG' && (date >= new Date('2021-06'))) ||
                                          (country === 'ST') ||
                                          (country === 'US' && lat > 52 && (date >= new Date('2019-01'))))) {
                        return 'Shitcam';
                    }

                    let gen2Countries = ['AU', 'BR', 'CA', 'CL', 'JP', 'GB', 'IE', 'NZ', 'MX', 'RU', 'US', 'IT', 'DK', 'GR', 'RO',
                                         'PL', 'CZ', 'CH', 'SE', 'FI', 'BE', 'LU', 'NL', 'ZA', 'SG', 'TW', 'HK', 'MO', 'MC', 'SM',
                                         'AD', 'IM', 'JE', 'FR', 'DE', 'ES', 'PT'];
                    if (gen2Countries.includes(country)) {

                        return 'Gen2or3';
                    }
                    else{
                        return 'Gen3';}
                }
                else if(svData.tiles.worldSize.height === 8192){
                    return 'Gen4';
                }
            }
            return 'Unknown';
        }

        async function getLocal(coord, timestamp) {
            const apiUrl = "https://api.geotimezone.com/public/timezone?";
            const systemTimezoneOffset = -new Date().getTimezoneOffset() * 60;

            try {
                const [lat, lng] = coord;
                const url = `${apiUrl}latitude=${lat}&longitude=${lng}`;

                const responsePromise = new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: url,
                        responseType: "json",
                        onload: function(response) {
                            if (response.status >= 200 && response.status < 300) {
                                resolve(response.response);
                            } else {
                                Swal.fire('Error fecthing exact time!', "Request failed: " + response.statusText,'error')
                                reject(new Error("Request failed: " + response.statusText));
                            }
                        },
                        onerror: function(error) {
                            reject(new Error("There was a network error: " + error));
                        }
                    });
                });

                function extractOffset(text) {
                    const regex = /UTC[+-]?\d+/;
                    const match = text.match(regex);
                    if (match) {
                        return parseInt(match[0].substring(3));
                    } else {
                        return null;
                    }
                }
                const data = await responsePromise;
                const offset = extractOffset(data.offset);
                const targetTimezoneOffset = offset * 3600;
                const offsetDiff = systemTimezoneOffset - targetTimezoneOffset;
                const convertedTimestamp = Math.round(timestamp - offsetDiff);
                return convertedTimestamp;
            } catch (error) {
                throw error;
            }
        }

        var CHUNK_SIZE = 1200;
        if (tags.includes('time')){
            CHUNK_SIZE = 500
        }
        var promises = [];

        async function processCoord(coord, tags, svData,ccData) {

           try{
               if (svData||ccData){
                let meta=getMetaData(svData)
                let yearTag=meta[0]
                let monthTag=parseInt(meta[1])
                let typeTag=meta[2]
                let subdivisionTag=meta[3]
                let localityTag=meta[4]
                let countryTag,elevationTag
                let genTag,trekkerTag,floorTag
                let dayTag,timeTag,exactTime,timeRange

               if(monthTag){monthTag=months[monthTag-1]}
               if (!monthTag){monthTag='Month not found'}

               var date=monthToTimestamp(svData.imageDate)

               if(tags.includes('day')||tags.includes('time')){
                   const initialSearch=await UE('SingleImageSearch',{'lat':coord.location.lat,'lng':coord.location.lng},date.startDate,date.endDate)
                   if (initialSearch){
                       if (initialSearch.length!=3)exactTime=null;
                       else{
                           exactTime=await binarySearch({'lat':coord.location.lat,'lng':coord.location.lng}, date.startDate,date.endDate)
                       }
                   }


               }

               if(!exactTime){dayTag='Day not found'
                              timeTag='Time not found'
                             }
               else{

                   const currentDate = new Date();
                   const currentOffset =-(currentDate.getTimezoneOffset())*60
                   const dayOffset = currentOffset-Math.round((coord.location.lng / 15) * 3600);
                   const LocalDay=new Date(Math.round(exactTime-dayOffset)*1000)
                   dayTag = LocalDay.toISOString().split('T')[0];

                   if(tags.includes('time')) {

                       var localTime=await getLocal([coord.location.lat,coord.location.lng],exactTime)
                       var timeObject=new Date(localTime*1000)
                       timeTag =`${timeObject.getHours().toString().padStart(2, '0')}:${timeObject.getMinutes().toString().padStart(2, '0')}:${timeObject.getSeconds().toString().padStart(2, '0')}`;
                       var hour = timeObject.getHours();

                       if (hour < 11) {
                           timeRange = 'Morning';
                       } else if (hour >= 11 && hour < 13) {
                           timeRange = 'Noon';
                       } else if (hour >= 13 && hour < 17) {
                           timeRange = 'Afternoon';
                       } else if(hour >= 17 && hour < 19) {
                           timeRange = 'Evening';
                       }
                       else{
                           timeRange = 'Night';
                       }
                   }
               }

               try {if (ccData.length!=3) ccData=ccData[1][0]
                    else ccData=ccData[1]
                   }

               catch (error) {
                   ccData=null
               }

               if (ccData){
                   try{
                       countryTag = ccData[5][0][1][4]}
                   catch(error){
                       countryTag=null
                   }
                   try{
                       elevationTag=ccData[5][0][1][1][0]}
                   catch(error){
                       elevationTag=null
                   }
                   try{
                       trekkerTag=ccData[6][5]}
                   catch(error){
                       trekkerTag=null
                   }
                   try{
                       floorTag=ccData[5][0][1][3][2][0]
                   }
                   catch(error){
                       floorTag=null
                   }
                   if (tags.includes('detect')){
                   const defaultDate=3
                   }
               }

               if (trekkerTag){
                   trekkerTag=trekkerTag.toString()
                   if( trekkerTag.includes('scout')){
                       trekkerTag='trekker'
                   }}

               if(elevationTag){
                   elevationTag=Math.round(elevationTag*100)/100
                   if(sR){
                       elevationTag=findRange(elevationTag,sR)
                   }
                   else{
                       elevationTag=elevationTag.toString()+'m'
                   }
               }

               if (!countryTag)countryTag='Country not found'
               if (!elevationTag)elevationTag='Elevation not found'

               if (tags.includes('generation')&&typeTag=='Official'&&countryTag){
                   genTag = getGeneration(svData,countryTag)
                   coord.tags.push(genTag)}

                if (tags.includes('year'))coord.tags.push(yearTag)

                if (tags.includes('month'))coord.tags.push(monthTag)

                if (tags.includes('day'))coord.tags.push(dayTag)

                if (tags.includes('time')) coord.tags.push(timeTag)

                if (tags.includes('time')&&timeRange) coord.tags.push(timeRange)

                if (tags.includes('type'))coord.tags.push(typeTag)

                if (tags.includes('type')&&trekkerTag&&typeTag=='Official')coord.tags.push('trekker')

                if (tags.includes('type')&&floorTag&&typeTag=='Official')coord.tags.push(floorTag)

                if (tags.includes('country'))coord.tags.push(countryTag)

                if (tags.includes('subdivision')&&typeTag=='Official')coord.tags.push(subdivisionTag)

                if (tags.includes('locality')&&typeTag=='Official')coord.tags.push(localityTag)

                if (tags.includes('elevation'))coord.tags.push(elevationTag)
               }
           }
            catch (error) {
                if(!tags.includes('fix'))coord.tags.push('Pano not found');
                else{
                    var panoYear,panoMonth
                    if (coord.panoDate){
                        panoYear=parseInt(coord.panoDate.substring(0,4))
                        panoMonth=parseInt(coord.panoDate.substring(5,7))
                    }
                    try{
                        const resultPano=await UE('SingleImageSearch',{lat: coord.location.lat, lng: coord.location.lng},null,null,5)
                        if (fixStrategy&&coord.panoDate){
                            const panos=resultPano[1][5][0][8]
                            for(const pano of panos){
                                if( pano[1][0]===panoYear&&pano[1][1]===panoMonth){
                                    const panoIndex=pano[0]
                                    const fixedPanoId=resultPano[1][5][0][3][0][panoIndex][0][1]
                                    coord.panoId=fixedPanoId
                                    coord.location.lat=resultPano[1][5][0][1][0][2]
                                    coord.location.lng=resultPano[1][5][0][1][0][3]
                                    coord.tags.push('Fixed')
                                }
                            }}
                        else {
                            coord.tags.push('Failed to fix')
                        }

                    }
                    catch (error){
                        coord.tags.push('Failed to fix')
                    }

                }
            }

            if (coord.tags) {coord.tags=Array.from(new Set(coord.tags))}
            taggedLocs.push(coord);
        }

        async function processChunk(chunk, tags) {
            var service = new google.maps.StreetViewService();
            var promises = chunk.map(async coord => {
                let panoId = coord.panoId;
                let latLng = {lat: coord.location.lat, lng: coord.location.lng};
                let svData;
                let ccData;
                if ((panoId || latLng)) {
                    if(tags!=['country']&&tags!=['elevation']&&tags!=['detect']){
                        svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});}
                }

                if (!panoId && (tags.includes('generation')||('country')||('elevation')||('type'))) {
                    ccData = await UE('SingleImageSearch', latLng);
                }

                else if (panoId && (tags.includes('generation')||('country')||('elevation')||('type'))) {
                    ccData = await UE('GetMetadata', panoId);
                }

                if (latLng && (tags.includes('detect'))) {
                    var detectYear,detectMonth
                    if (coord.panoDate){
                        detectYear=parseInt(coord.panoDate.substring(0,4))
                        detectMonth=parseInt(coord.panoDate.substring(5,7))
                    }
                    else{
                        if(coord.panoId){
                            const metaData=await getSVData(service,{pano: panoId})
                            if (metaData){
                                if(metaData.imageDate){
                                    detectYear=parseInt(metaData.imageDate.substring(0,4))
                                    detectMonth=parseInt(metaData.imageDate.substring(5,7))
                                }
                            }
                        }
                    }
                    if (detectYear&&detectMonth){
                        const metaData = await UE('SingleImageSearch', latLng,10);
                        if (metaData){
                            if(metaData.length>1){
                                const defaultDate=metaData[1][6][7]
                                if (defaultDate[0]===detectYear&&defaultDate[1]!=detectMonth){
                                    coord.tags.push('Dangerous')}
                            }
                        }
                    }
                }
                if (tags!=['detect']){
                    await processCoord(coord, tags, svData,ccData)}
            });
            await Promise.all(promises);

        }

        function getSVData(service, options) {
            return new Promise(resolve => service.getPanorama({...options}, (data, status) => {
                resolve(data);

            }));
        }

        async function processData(tags) {
            let successText='The JSON data has been pasted to your clipboard!';
            try {
                const totalChunks = Math.ceil(selections.length / CHUNK_SIZE);
                let processedChunks = 0;

                const swal = Swal.fire({
                    title: 'Tagging',
                    text: 'If you try to tag a large number of locs by exact time or elevation, it could take quite some time. Please wait...',
                    allowOutsideClick: false,
                    allowEscapeKey: false,
                    showConfirmButton: false,
                    icon:"info",
                    didOpen: () => {
                        Swal.showLoading();
                    }
                });

                for (let i = 0; i < selections.length; i += CHUNK_SIZE) {
                    let chunk = selections.slice(i, i + CHUNK_SIZE);
                    await processChunk(chunk, tags);
                    processedChunks++;

                    const progress = Math.min((processedChunks / totalChunks) * 100, 100);
                    Swal.update({
                        html: `<div>${progress.toFixed(2)}% completed</div>
                       <div class="swal2-progress">
                           <div class="swal2-progress-bar" role="progressbar" aria-valuenow="${progress}" aria-valuemin="0" aria-valuemax="100" style="width: ${progress}%;">
                           </div>
                       </div>`
                    });
                    if(exportMode){
                        updateSelection(chunk)
                        successText='Tagging completed! Do you want to refresh the page?(The JSON data is also pasted to your clipboard)'
                    }
                }

                swal.close();
                var newJSON=[]
                taggedLocs.forEach((loc)=>{
                    newJSON.push({lat:loc.location.lat,
                                  lng:loc.location.lng,
                                  heading:loc.heading,
                                  pitch: loc.pitch !== undefined && loc.pitch !== null ? loc.pitch : 90,
                                  zoom: loc.zoom !== undefined && loc.zoom !== null ? loc.zoom : 0,
                                  panoId:loc.panoId,
                                  extra:{tags:loc.tags}
                                 })
                })
                GM_setClipboard(JSON.stringify(newJSON))
                Swal.fire({
                    title: 'Success!',
                    text: successText,
                    icon: 'success',
                    showCancelButton: true,
                    confirmButtonColor: '#3085d6',
                    cancelButtonColor: '#d33',
                    confirmButtonText: 'OK'
                }).then((result) => {
                    if (result.isConfirmed) {
                        if(exportMode){
                            location.reload();}
                    }
                });
            } catch (error) {
                swal.close();
                Swal.fire('Error Tagging!', '','error');
                console.error('Error processing JSON data:', error);
            }
        }

        if(selections){
            if(selections.length>=1){processData(tags);}
            else{
                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');}
        }else{Swal.fire('Error Parsing JSON Data!', 'The input JSON data is invaild or incorrectly formatted.','error');}
    }

    function createCheckbox(text, tags) {
        var label = document.createElement('label');
        var checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.value = text;
        checkbox.name = 'tags';
        checkbox.id = tags;
        label.appendChild(checkbox);
        label.appendChild(document.createTextNode(text));
        buttonContainer.appendChild(label);
        return checkbox;
    }

    var mainButton = document.createElement('button');
    mainButton.textContent = 'Auto-Tag';
    mainButton.style.position = 'fixed';
    mainButton.style.right = '20px';
    mainButton.style.bottom = '20px';
    mainButton.style.borderRadius = "18px";
    mainButton.style.fontSize = "16px";
    mainButton.style.padding = "10px 20px";
    mainButton.style.border = "none";
    mainButton.style.color = "white";
    mainButton.style.cursor = "pointer";
    mainButton.style.backgroundColor = "#4CAF50";
    mainButton.addEventListener('click', function () {
        if (buttonContainer.style.display === 'none') {
            buttonContainer.style.display = 'block';
        } else {
            buttonContainer.style.display = 'none';
        }
    });
    document.body.appendChild(mainButton);

    var buttonContainer = document.createElement('div');
    buttonContainer.style.position = 'fixed';
    buttonContainer.style.right = '20px';
    buttonContainer.style.bottom = '60px';
    buttonContainer.style.display = 'none';
    buttonContainer.style.fontSize = '15px';
    document.body.appendChild(buttonContainer);

    var selectAllCheckbox = document.createElement('input');
    selectAllCheckbox.type = 'checkbox';
    selectAllCheckbox.id = 'selectAll';
    selectAllCheckbox.addEventListener('change', function () {
        var checkboxes = document.getElementsByName('tags');
        for (var i = 0; i < checkboxes.length; i++) {
            if (!['Fix', 'Detect'].includes(checkboxes[i].value)){
            checkboxes[i].checked = this.checked;
            }
        }
    });
    var selectAllLabel = document.createElement('label');
    selectAllLabel.textContent = 'Select All';
    selectAllLabel.htmlFor = 'selectAll';



    var triggerButton = document.createElement('button');
    triggerButton.textContent = 'Start Tagging';
    triggerButton.addEventListener('click', function () {
        var checkboxes = document.getElementsByName('tags');
        var checkedTags = [];
        for (var i = 0; i < checkboxes.length; i++) {
            if (checkboxes[i].checked) {
                checkedTags.push(checkboxes[i].id);
            }
        }
        if (checkedTags.includes('elevation')) {
            Swal.fire({
                title: 'Set A Range For Elevation',
                text: 'If you select "Cancel", the script will return the exact elevation for each location.',
                icon: 'question',
                showCancelButton: true,
                showCloseButton: true,
                allowOutsideClick: false,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: 'Yes',
                cancelButtonText: 'Cancel'
            }).then((result) => {
                if (result.isConfirmed) {
                    Swal.fire({
                        title: 'Define Range for Each Segment',
                        html: `
                    <label> <br>Enter range for each segment, separated by commas</br></label>
                    <textarea id="segmentRanges" class="swal2-textarea" placeholder="such as:-1-10,11-35"></textarea>
                `,
                        icon: 'question',
                        showCancelButton: true,
                        showCloseButton: true,
                        allowOutsideClick: false,
                        focusConfirm: false,
                        preConfirm: () => {
                            const segmentRangesInput = document.getElementById('segmentRanges').value.trim();
                            if (!segmentRangesInput) {
                                Swal.showValidationMessage('Please enter range for each segment');
                                return false;
                            }
                            const segmentRanges = segmentRangesInput.split(',');
                            const validatedRanges = segmentRanges.map(range => {
                                const matches = range.trim().match(/^\s*(-?\d+)\s*-\s*(-?\d+)\s*$/);
                                if (matches) {
                                    const min = Number(matches[1]);
                                    const max = Number(matches[2]);
                                    return { min, max };
                                } else {
                                    Swal.showValidationMessage('Invalid range format. Please use format: minValue-maxValue');
                                    return false;
                                }
                            });
                            return validatedRanges.filter(Boolean);
                        },
                        confirmButtonColor: '#3085d6',
                        cancelButtonColor: '#d33',
                        confirmButtonText: 'Yes',
                        cancelButtonText: 'Cancel',
                        inputValidator: (value) => {
                            if (!value.trim()) {
                                return 'Please enter range for each segment';
                            }
                        }
                    }).then((result) => {
                        if (result.isConfirmed) {
                            runScript(checkedTags, result.value)
                        } else {
                            Swal.showValidationMessage('You canceled input');
                        }
                    });
                }
                else if (result.dismiss === Swal.DismissReason.cancel) {
                    runScript(checkedTags)
                }
            });
        }
        else {
            runScript(checkedTags)
        }
    })
    buttonContainer.appendChild(triggerButton);
    buttonContainer.appendChild(selectAllCheckbox);
    buttonContainer.appendChild(selectAllLabel);
    tagBox.forEach(tag => {
        createCheckbox(tag, tag.toLowerCase());
    });

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址