Geoguessr Map-Making Auto-Tag

Tag your street views by date&address&generations

目前为 2024-03-30 提交的版本。查看 最新版本

// ==UserScript==
// @name         Geoguessr Map-Making Auto-Tag
// @namespace    http://tampermonkey.net/
// @version      3.1
// @description  Tag your street views by date&address&generations
// @author       KaKa
// @match        https://map-making.app/maps/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_setClipboard
// @license      MIT
// @icon         https://www.google.com/s2/favicons?domain=geoguessr.com
// ==/UserScript==

(function() {
    'use strict';

    async function runScript(tags) {

        const option = confirm('Do you want to input data from the clipboard? If you click "Cancel", you will need to upload a JSON file.');

        let data;
        if (option) {

            const text = await navigator.clipboard.readText();
            try {
                data = JSON.parse(text);
            } catch (error) {
                alert('The input JSON data is invalid or incorrectly formatted.');
                return;
            }
        } else {

            const input = document.createElement('input');
            input.type = 'file';
            document.body.appendChild(input);

            data = await new Promise((resolve) => {
                input.addEventListener('change', async () => {
                    const file = input.files[0];
                    const reader = new FileReader();

                    reader.onload = (event) => {
                        try {
                            const result = JSON.parse(event.target.result);
                            resolve(result);

                            document.body.removeChild(input);
                        } catch (error) {
                            alert('The input JSON data is invalid or incorrectly formatted.');
                        }
                    };

                    reader.readAsText(file);
                });


                input.click();
            });
        }
        const newData = [];


        async function UE(t, e) {
            try {
                const r = `https://maps.googleapis.com/$rpc/google.internal.maps.mapsjs.v1.MapsJsInternalService/${t}`;
                let payload=createPayload(t,e)
                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) {
            let payload;
            if (mode === 'GetMetadata') {
                payload = [["apiv3",null,null,null,"US",null,null,null,null,null,[[0]]],["en","US"],[[[2,coorData.panoId]]],[[1,2,3,4,8,6]]];
            } else if (mode === 'SingleImageSearch') {
                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]]];
            } else {
                throw new Error("Invalid mode!");
            }
            return JSON.stringify(payload);
        }


        function getMetaData(svData) {
            if (svData) {
            let levelId=svData.dn
            let year = 'noyear',month = 'nomonth'
            let panoType='Unofficial'
            let subdivision='nosub',locality='nolocality'

            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,levelId]
            }

            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 === 'NG' && (date >= new Date('2021-06'))) ||
                                          (country === 'ST') ||
                                          (country === 'US' && lat > 52 && (date >= new Date('2019-01'))))) {
                        return 'Shitcam';
                    }

                    if ((country === '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' ) {

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

        var CHUNK_SIZE = 1200;
        var promises = [];

        async function processCoord(coord, tags, svData,ccData) {
            if (!coord.extra) {
                coord.extra = {};
            }
            if (!coord.extra.tags) {
                coord.extra.tags = [];
            }

            if (svData){
                let meta=getMetaData(svData)

                let yearTag=meta[0]
                let monthTag=meta[1]
                let typeTag=meta[2]
                let subdivisionTag=meta[3]
                let localityTag=meta[4]
                let countryTag
                let genTag
                let trekkerTag=meta[5]

                if (ccData){
                    try {
                        countryTag = ccData[1][0][5][0][1][4]
                    }
                    catch (error) {
                        try {
                            countryTag = ccData[1][5][0][1][4]
                        } catch (error) {
                            countryTag='nocountry'
                        }
                    }
                    if (!countryTag)countryTag='nocountry'
                }


                genTag = getGeneration(svData,countryTag)

                if (tags.includes('generation')&&typeTag=='Official')coord.extra.tags.push(genTag)

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

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

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

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

                if (tags.includes('country')&&typeTag=='Official')coord.extra.tags.push(countryTag)

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

                if (tags.includes('locality')&&typeTag=='Official')coord.extra.tags.push(localityTag)
            }
            else {coord.extra.tags.push('nopano')}

            if (coord.extra.tags) {coord.extra.tags=Array.from(new Set(coord.extra.tags))}

            newData.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.lat, lng: coord.lng};
                    let svData;
                    let ccData;

                    if ((panoId || latLng)) {
                        svData = await getSVData(service, panoId ? {pano: panoId} : {location: latLng, radius: 50});
                    }

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

                    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) {
            try {

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

                GM_setClipboard(JSON.stringify(newData));
                alert("New JSON data has been copied to the clipboard!");
            } catch (error) {
                alert("Invalid JSON data");
                console.error('Error processing JSON data:', error);
            }
        }
        processData(tags);
    }

    var mainButtonContainer = document.createElement('div');
    mainButtonContainer.style.position = 'fixed';
    mainButtonContainer.style.right = '20px';
    mainButtonContainer.style.bottom = '20px';
    document.body.appendChild(mainButtonContainer);

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

    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 triggerButton = document.createElement('button');
    triggerButton.textContent = 'Star 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);
            }
        }
        runScript(checkedTags);
    });
    buttonContainer.appendChild(triggerButton);

    var mainButton = document.createElement('button');
    mainButton.textContent = 'Auto-Tag';
    mainButton.addEventListener('click', function() {
        if (buttonContainer.style.display === 'none') {
            buttonContainer.style.display = 'block';
        } else {
            buttonContainer.style.display = 'none';
        }
    });
    mainButtonContainer.appendChild(mainButton);

    createCheckbox('Year', 'year');
    createCheckbox('Month', 'month');
    createCheckbox('Type', 'type');
    createCheckbox('Country', 'country');
    createCheckbox('Subdivision', 'subdivision');
    createCheckbox('Locality', 'locality');
    createCheckbox('Generations', 'generation');
})();

QingJ © 2025

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