Greasy Fork 支持简体中文。

GeoGuessr Custom Maps

playing with modified maps in geoguessr games

// ==UserScript==
// @name         GeoGuessr Custom Maps
// @description  playing with modified maps in geoguessr games
// @version      1.1.7
// @match        *://www.geoguessr.com/*
// @author       KaKa
// @license      BSD
// @require      https://update.greasyfork.org/scripts/502813/1423193/Geoguessr%20Tag.js
// @icon         https://www.svgrepo.com/show/392367/interaction-interface-layer-layers-location-map.svg
// @namespace https://greasyfork.org/users/1179204
// ==/UserScript==

(function() {

    /*=========================================================================Modifiy your guess map here==================================================================================*/

    let customOptions={

        Language:'en',                                  //en,zh,ja,fr,de,es

        Google_StreetView_Layer_Lines_Style:'Default',   // More styles see below

        Google_StreetView_Layer_Shortcut:'V',

        Google_Labels_Layer_Shortcut:'G',

        Google_Terrain_Layer_Shortcut:'T',

        Google_Satellite_Layer_Shortcut:'B',

        Apple_StreetView_Layer_Shortcut:'P',

        Yandex_StreetView_Layer_Shortcut:'Y',

        OpenWeather_Shortcut:'Q',

        OpenWeather_Style:'radar',                      //'radar': Global Precipitation;  'CL':Cloud;  'APM':Pressure;  'TA2'Temperature;  'WS10':Wind Speed;

        OpenWeather_Date:'now',                        // foramt:yyyy-mm-dd, less than one week ago

        Bing_Maps_Style:'r',                           // 'a':satellite(without labels);  'h':hybrid;  'r':roadmap,'sre':terrain

        Map_Tiler_Style:'basic',                       //basic,satellite,bright,landscape,ocean,outdoor,topo,streets,dataviz

        Carto_Style:'light_all',                       //light_all,dark_all

        Thunderforest_Style:'spinal-map'}              //spinal-map,landscape,outdoors,atlas,transport,


    let tileServices=["Google_Maps","OpenStreetMap","Bing_Maps","Map_Tiler","Thunderforest","Carto","Yandex_Maps","Petal_Maps"]

    let colorOptions={

        Default:['1098ad','99e9f2'],

        Crimson:['f03e3e','ffc9c9'],

        Deep_Pink:['d6336c','fcc2d7'],

        Blue_Violet:['ae3ec9','eebefa'],

        Slate_Blue:['7048e8','d0bfff'],

        Royal_Blue:['4263eb','bac8ff'],

        Dodger_Blue: ['1c7ed6','a5d8ff'],

        Sea_Green:['0ca678','96f2d7'],

        Lime_Green:['37b24d','b2f2bb'],

        OliveDrab:['74b816','d8f5a2'],

        Orange:['f59f00','ffec99'],

        Dark_Orange:['f76707','ffd8a8'],

        Brown:['bd5f1b','f7ca9e'],
    }

    /*======================================================================================================================================================================================*/

    let map,google,customMapType,initLayer=tag
    let currentLayers=JSON.parse(localStorage.getItem('custom_layers'));
    if(!currentLayers){
        currentLayers=["Google_Maps","Google_Labels"]
        localStorage.setItem('custom_layers', JSON.stringify(currentLayers));
    }

    const openWeatherBaseURL = "https://g.sat.owm.io/vane/2.0/weather";
    const radarURL = `https://b.sat.owm.io/maps/2.0/radar/{z}/{x}/{y}?appid=9de243494c0b295cca9337e1e96b00e2&day=${getNow(customOptions.OpenWeather_Date)}`;

    const openWeatherURL = (customOptions.OpenWeather_Style === 'radar')
    ? radarURL
    : `${openWeatherBaseURL}/${customOptions.OpenWeather_Style}/{z}/{x}/{y}?appid=9de243494c0b295cca9337e1e96b00e2&&date=${getTimestamp(customOptions.OpenWeather_Date)}&fill_bound=true`;

    let tileUrls = {
        Petal_Maps: `https://maprastertile-dra.dbankcdn.com/display-service/v1/online-render/getTile/24.08.06.20/{z}/{x}/{y}/?language=${customOptions.Language}&p=46&scale=1&mapType=ROADMAP&presetStyleId=standard&key=DAEDANitav6P7Q0lWzCzKkLErbrJG4kS1u%2FCpEe5ZyxW5u0nSkb40bJ%2BYAugRN03fhf0BszLS1rCrzAogRHDZkxaMrloaHPQGO6LNg==`,
        OpenStreetMap: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
        Map_Tiler:`https://api.maptiler.com/maps/${customOptions.Map_Tiler_Style}-v2/256/{z}/{x}/{y}.png?key=0epLOAjD7fw17tghcyee`,
        Thunderforest:`https://b.tile.thunderforest.com/${customOptions.Thunderforest_Style}/{z}/{x}/{y}@2x.png?apikey=6a53e8b25d114a5e9216df5bf9b5e9c8`,
        Carto:`https://cartodb-basemaps-3.global.ssl.fastly.net/${customOptions.Carto_Style}/{z}/{x}/{y}.png`,
        Google_Maps:`https://www.google.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m1!2sm!3m17!2sen!3sUS!5e18!12m4!1e68!2m2!1sset!2sRoadmap!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2ss.e:l|p.v:off,s.t:0.8|s.e:g.s|p.v:on!5m1!5f1.5`,
        Google_Maps_new:`https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i713465579!3m12!2sen!3sUS!5e18!12m4!1e68!2m2!1sset!2sRoadmap!12m3!1e37!2m1!1ssmartmaps!4e0!5m2!1e3!5f2!23i47083502`,
        Google_Terrain:`https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m1!2sm!2m2!1e5!2sshading!2m2!1e6!2scontours!3m17!2s${customOptions.Language}!3sUS!5e18!12m4!1e68!2m2!1sset!2sTerrain!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2ss.e:l|p.v:off,s.t:0.8|s.e:g.s|p.v:on!5m1!5f1.5`,
        Google_Satellite:`https://mt3.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}`,
        Google_StreetView:`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m8%211e2%212ssvv%214m2%211scc%212s*211m3*211e2*212b1*213e2*212b1*214b1%214m2%211ssvl%212s*212b1%213m17%212sen%213sUS%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.e%3Ag.f%7Cp.c%3A%23${colorOptions[customOptions.Google_StreetView_Layer_Lines_Style][0]}%7Cp.w%3A1%2Cs.e%3Ag.s%7Cp.c%3A%23${colorOptions[customOptions.Google_StreetView_Layer_Lines_Style][1]}%7Cp.w%3A3%215m1%215f1.35`,
        Google_Labels:`https://maps.googleapis.com/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m1!2sm!3m17!2s${customOptions.Language}!3sUS!5e18!12m4!1e68!2m2!1sset!2sRoadmap!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2ss.e:g|p.v:off,s.t:1|s.e:g.s|p.v:off,s.e:l|p.v:on!5m1!5f2`,
        Apple_StreetView:`https://lookmap.eu.pythonanywhere.com/bluelines_raster_2x/{z}/{x}/{y}.png`,
        Yandex_StreetView:`https://core-stv-renderer.maps.yandex.net/2.x/tiles?l=stv&x={x}&y={y}&z={z}&scale=1&v=2024.09.06.19.22-1_24.09.08-0-21241`,
        Yandex_Maps:`https://core-renderer-tiles.maps.yandex.net/tiles?l=map&v=24.09.06-3-b240906182700&x={x}&y={y}&z={z}&scale=1&lang=${customOptions.Language}_US`,
        OpenWeather: openWeatherURL
    }

    function getMap(){
        let element = document.getElementsByClassName("guess-map_canvas__cvpqv")[0]
        if(!element) element=document.getElementsByClassName("run-game-guess-map-contents_canvas__XQRwC")[0]
        if(!element) return
        try{
            //if (!element) element=document.getElementsByClassName("coordinate-result-map_map__Yh2Il")[0]
            const keys = Object.keys(element)

            const key = keys.find(key => key.startsWith("__reactFiber$"))
            const props = element[key]
            map=props.return.return.memoizedProps.map
            if(!map) map=props.return.memoizedState.memoizedState.current.instance
            google=unsafeWindow.google
            customMapType=setMapType()
            const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
            map.mapTypes.set("roadmap",layers)}
        catch(error){
            console.error('Failed to get map')
        }
    }

    function OC(controlDiv, layer) {
        controlDiv.style.margin='20px'
        controlDiv.style.backgroundColor = '#fff';
        controlDiv.style.height = '30px';
        controlDiv.style.boxShadow = 'rgba(0, 0, 0, 0.3) 0px 1px 4px -1px';
        controlDiv.style.borderRadius = '5px';

        var opacitySlider = document.createElement('input');

        opacitySlider.setAttribute('type', 'range');
        opacitySlider.setAttribute('min', '0');
        opacitySlider.setAttribute('max', '100');
        opacitySlider.setAttribute('value', '100');
        opacitySlider.setAttribute('step', '1');
        opacitySlider.style.width = '90px';
        opacitySlider.style.height='20px'
        opacitySlider.style.marginTop='5px'
        opacitySlider.addEventListener('input', function() {
            var opacity = opacitySlider.value / 100
            layer.set('opacity',opacity)
        });

        controlDiv.appendChild(opacitySlider);
    }

    function addOpacityControl(m, layer) {
        var opacityControlDiv = document.createElement('div');
        new OC(opacityControlDiv, layer);
        opacityControlDiv.id=layer.name
        opacityControlDiv.index = 1;
        m.controls[google.maps.ControlPosition.TOP_RIGHT].push(opacityControlDiv);
    }

    function removeOpacityControl(id) {
        var controls = map.controls[google.maps.ControlPosition.TOP_RIGHT];
        if(controls&&controls.getLength()>0){
            if(!id) controls.removeAt(0)
            else{
                for (var i = 0; i < controls.getLength(); i++) {
                    var control = controls.getAt(i);
                    if (control.id === id) {
                        controls.removeAt(i);
                        break;
                    }
                }
            }
        }
    }

    function MR(e, t) {
        return new Promise(n => {
            google.maps.event.addListenerOnce(e, t, n);
        });
    }

    function getNow(date) {
        if(date!='now'){
            return date
        }
        const now = new Date();
        now.setHours(now.getHours() - 1);
        return now.toISOString().slice(0, 14)+'00';
    }

    function getTimestamp(date){
        var parsedDate
        if (date=== 'now') {
            parsedDate= new Date()
            return Math.floor(parsedDate.getTime() / 1000)
        }
        parsedDate = new Date(date);

        if (isNaN(parsedDate.getTime())) {
            throw new Error('Invalid date format');
        }
        return Math.floor(parsedDate.getTime() / 1000);

    }

    function extractTileCoordinates(url) {
        const regex = /!1i(\d+)!2i(\d+)!3i(\d+)!4i(\d+)/;
        const matches = url.match(regex);

        if (matches && matches.length === 5) {
            const z = matches[1];
            const x = matches[2];
            const y = matches[3];
            return { z, x, y };
        } else {
            return null;
        }
    }

    function BaiduProjection() {
        var R = 6378206;
        var R_MINOR = 6356584.314245179;
        var bounds = new google.maps.LatLngBounds(
            new google.maps.LatLng(-19994619.55417086, -20037725.11268234),
            new google.maps.LatLng(19994619.55417086, 20037725.11268234)
        );

        this.fromLatLngToPoint = function(latLng) {

            var lat = latLng.lat() * Math.PI / 180;
            var lng = latLng.lng() * Math.PI / 180;

            var x = lng * R;
            var y = Math.log(Math.tan(Math.PI / 4 + lat / 2)) * R;

            var scale = 1 / Math.pow(2, 18);
            var origin = new google.maps.Point(bounds.getSouthWest().lng(), bounds.getNorthEast().lat());
            return new google.maps.Point(
                (x - origin.x) * scale,
                (origin.y - y) * scale
            );
        };

        this.fromPointToLatLng = function(point) {
            var scale = 1 / Math.pow(2, 18);
            var origin = new google.maps.Point(bounds.getSouthWest().lng(), bounds.getNorthEast().lat());

            var x = point.x / scale + origin.x;
            var y = origin.y - point.y / scale;

            var lng = x / R * 180 / Math.PI;
            var lat = (2 * Math.atan(Math.exp(y / R)) - Math.PI / 2) * 180 / Math.PI;

            return new google.maps.LatLng(lat, lng);
        };

        return this;
    }

    function getBingTiles(tileX, tileY, zoom,type) {
        var quadKey = tileXYToQuadKey(tileX, tileY, zoom);
        var baseUrl = 'https://ecn.t0.tiles.virtualearth.net/tiles/';

        return baseUrl + type + quadKey + '.jpeg?g=14519';
    }

    function tileXYToQuadKey(tileX, tileY, zoom) {
        var quadKey = '';
        for (var i = zoom; i > 0; i--) {
            var digit = 0;
            var mask = 1 << (i - 1);
            if ((tileX & mask) !== 0) {
                digit += 1;
            }
            if ((tileY & mask) !== 0) {
                digit += 2;
            }
            quadKey += digit.toString();
        }
        return quadKey;
    }

    function setMapType(){
        class customMapType extends google.maps.ImageMapType {
            constructor(layers, options = null) {
                const defaultOptions = {
                    getTileUrl: function(coord, zoom) {
                        return null;
                    },
                    tileSize: new google.maps.Size(256, 256),
                    maxZoom: 20,
                    name: 'CustomMapType',
                };


                super({...defaultOptions, ...options});
                this.layers = layers;
            }

            getTile(t, n, r) {
                const o = this.layers.map(i => {
                    if (typeof i.getTile !== 'function') {
                        console.error('getTile method is missing in layer:', i);
                    }
                    return i.getTile(t, n, r);
                });
                const s = document.createElement("div");
                s.append(...o);

                Promise.all(o.map(i => MR(i, "load"))).then(() => {
                    google.maps.event.trigger(s, "load");
                });

                return s;
            }


            releaseTile(tile) {
                let index = 0;
                for (const child of tile.children) {
                    if (child instanceof HTMLElement) {
                        this.layers[index]?.releaseTile(child);
                        index += 1;
                    }
                }
            }
        }
        return customMapType
    }

    function setTileLayer(layerName){

        var tileLayer
        const tileUrl = tileUrls[layerName];
        if (layerName==='Bing_Maps'){
            tileLayer = new google.maps.ImageMapType({
                getTileUrl: function(coord, zoom) {
                    return getBingTiles(coord.x,coord.y,zoom,customOptions.Bing_Maps_Style)
                        .replace('{z}', zoom)
                        .replace('{x}', coord.x)
                        .replace('{y}', coord.y);
                },
                tileSize: new google.maps.Size(256, 256),
                name: layerName,
                maxZoom:20
            });}
        else{
            tileLayer = new google.maps.ImageMapType({
                getTileUrl: function(coord, zoom) {
                    return tileUrl
                        .replace('{z}', zoom)
                        .replace('{x}', coord.x)
                        .replace('{y}', coord.y);
                },
                tileSize: new google.maps.Size(256, 256),
                name:layerName,
                maxZoom:20,
            })}
        if (layerName.includes('StreetView') || layerName.includes('Weather')) {
            if (!document.getElementById(layerName)) addOpacityControl(map, tileLayer);
        }
        return tileLayer
    }


    let onKeyDown = (e) => {
        if ( e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) return
        if (e.key >= '1' && e.key <= '7') {
            e.stopImmediatePropagation();
            if(!map) getMap()
            const tileIndex=parseInt(e.key)
            const layerName=tileServices[tileIndex]
            if(!currentLayers.includes(layerName)){
                initLayer(`layer:${layerName}`)
                currentLayers[0]=layerName
                currentLayers = currentLayers.filter(layer => layer !== 'Google_Labels')
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }

            else {
                currentLayers[0]='Google_Maps'
                if(!currentLayers.includes('Google_Labels'))currentLayers.push('Google_Labels')
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
        }

        else if (e.key === '0') {
            e.stopImmediatePropagation();
            if(!map) getMap()
            currentLayers[0]='Google_Maps'
            initLayer('layer:Classic Style')
            if(!currentLayers.includes('Google_Labels'))currentLayers.push('Google_Labels')
            const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
            map.mapTypes.set("roadmap",layers)
        }

        else if (e.key === customOptions.Google_StreetView_Layer_Shortcut.toLowerCase()|| e.key === customOptions.Google_StreetView_Layer_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            initLayer(`layer:Google_StreetView`)
            if(!currentLayers.includes('Google_StreetView')){
                currentLayers.splice(1, 0, 'Google_StreetView');
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
            else{currentLayers = currentLayers.filter(layer => layer !== 'Google_StreetView')
                 removeOpacityControl('Google_StreetView')
                 const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                 map.mapTypes.set("roadmap",layers)}
        }

        else if (e.key === customOptions.Apple_StreetView_Layer_Shortcut.toLowerCase()|| e.key === customOptions.Apple_StreetView_Layer_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            if(!currentLayers.includes('Apple_StreetView')){
                initLayer(`layer:Apple_StreetView`)
                currentLayers.splice(1, 0, 'Apple_StreetView');
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
            else{currentLayers = currentLayers.filter(layer => layer !== 'Apple_StreetView')
                 removeOpacityControl('Apple_StreetView')
                 const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                 map.mapTypes.set("roadmap",layers)}
        }

        else if (e.key === customOptions.Yandex_StreetView_Layer_Shortcut.toLowerCase()|| e.key === customOptions.Yandex_StreetView_Layer_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            initLayer(`layer:Yandex_StreetView`)
            if(!currentLayers.includes('Yandex_StreetView')){
                currentLayers.splice(1, 0, 'Yandex_StreetView');
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
            else{currentLayers = currentLayers.filter(layer => layer !== 'Yandex_StreetView')
                 removeOpacityControl('Yandex_StreetView')
                 const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                 map.mapTypes.set("roadmap",layers)}
        }

        else if (e.key === customOptions.Google_Labels_Layer_Shortcut.toLowerCase()|| e.key === customOptions.Google_Labels_Layer_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            initLayer(`layer:Google_Labels`)
            if(!currentLayers.includes('Google_Labels')){
                currentLayers.push('Google_Labels');
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)))
                map.mapTypes.set("roadmap",layers)}

            else{currentLayers = currentLayers.filter(layer => layer !== 'Google_Labels')
                 const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)))
                 map.mapTypes.set("roadmap",layers)}
        }

        else if (e.key === customOptions.Google_Terrain_Layer_Shortcut.toLowerCase()|| e.key === customOptions.Google_Terrain_Layer_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            initLayer(`layer:Google_Terrain`)
            if(!currentLayers.includes('Google_Terrain')){
                currentLayers[0]='Google_Terrain'
                if(!currentLayers.includes('Google_Labels'))currentLayers.push('Google_Labels')
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)}
            else {
                currentLayers[0]='Google_Maps'
                if(!currentLayers.includes('Google_Labels'))currentLayers.push('Google_Labels')
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
        }

        else if (e.key === customOptions.Google_Satellite_Layer_Shortcut.toLowerCase()|| e.key === customOptions.Google_Satellite_Layer_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            initLayer(`layer:Google_Satellite`)
            if(!currentLayers.includes('Google_Satellite')){
                currentLayers[0]='Google_Satellite'
                if(!currentLayers.includes('Google_Labels'))currentLayers.push('Google_Labels')
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)}
            else {
                currentLayers[0]='Google_Maps'
                if(!currentLayers.includes('Google_Labels'))currentLayers.push('Google_Labels')
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
        }

        else if (e.key === customOptions.OpenWeather_Shortcut.toLowerCase()|| e.key === customOptions.OpenWeather_Shortcut) {
            e.stopImmediatePropagation();
            if(!map) getMap()
            if(!currentLayers.includes('OpenWeather')){
                initLayer(`layer:OpenWeather`)
                currentLayers.splice(1, 0, 'OpenWeather');
                const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                map.mapTypes.set("roadmap",layers)
            }
            else{currentLayers = currentLayers.filter(layer => layer !== 'OpenWeather')
                 removeOpacityControl('OpenWeather')
                 const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
                 map.mapTypes.set("roadmap",layers)}
        }

        else if (e.key === 'e'|| e.key ==='E') {
            if(!map) getMap()
            e.stopImmediatePropagation();
            currentLayers=['Google_Maps_new']
            const layers =new customMapType(currentLayers.map(layerName => setTileLayer(layerName)));
            map.mapTypes.set("roadmap",layers)
        }

        localStorage.setItem('custom_layers', JSON.stringify(currentLayers));
        map.setMapTypeId("roadmap")
    }

    document.addEventListener("keydown", onKeyDown);
    const intervalId = setInterval(() => {
        getMap();
        if (map) {
            clearInterval(intervalId)}
    }, 500);

})();