Geoguessr Minimap Wizard

Lets you see satellite imagery, terrain, OpenStreetMap, etc.

// ==UserScript==
// @name         Geoguessr Minimap Wizard
// @namespace    alienperfect
// @version      1.1
// @description  Lets you see satellite imagery, terrain, OpenStreetMap, etc.
// @author       Alien Perfect
// @match        https://www.geoguessr.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geoguessr.com
// @run-at       document-start
// @grant        none
// ==/UserScript==

// You can design your own map.
// 1. Go to https://mapstyle.withgoogle.com/ and create your own style.
// 2. Replace everything between "// Start" and "// End" below with your own JSON styling.
// 3. Change the name for the map below, if you want.

const customMap = {
    name: "Custom",
    alt: "Show custom map",
    style:
    // Start
    [
        {
            "featureType": "all",
            "elementType": "labels",
            "stylers": [
                {"visibility": "off"}
            ]
        }
    ]
    // End
}

let defaultMap = "roadmap";
let mapList = ["roadmap", "terrain", "satellite", "hybrid", "osm", "custom"];

function getDefaultMap() {
    return localStorage.getItem("@defaultMap") || defaultMap;
}

function setDefaultMap(id) {
    let def = id || defaultMap;
    localStorage.setItem("@defaultMap", def);
    console.log("default map is", id);
}

function setMap(map, id) {
    setDefaultMap(id);
    map.setMapTypeId(id);
}

function addMapSelector(map) {
    const mapSelector = document.createElement("select");

    mapSelector.className = "map-selector";
    mapSelector.title = "Click to change the map";

	mapSelector.addEventListener("change", function() {
        setMap(map, this.value);
    });

    for (let id of mapList) {
        let name = map.mapTypes.get(id).name || id;
        let alt = map.mapTypes.get(id).alt || name;
        let option = document.createElement("option");

        option.style.backgroundColor = "#fff";
        option.textContent = name;
        option.title = alt;
        option.value = id;

        mapSelector.appendChild(option);
    }

    mapSelector.value = getDefaultMap();
	map.controls[google.maps.ControlPosition.TOP_RIGHT].push(mapSelector);
}

function loadExtraMaps(map) {
    const OSM = new google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            var tilesPerGlobe = 1 << zoom;
            var x = coord.x % tilesPerGlobe;

            if (x < 0) {
                x = tilesPerGlobe+x;
            }

            return (`https://tile.openstreetmap.org/${zoom}/${x}/${coord.y}.png`);
        },
        maxZoom: 18,
        tileSize: new google.maps.Size(256, 256),
        name: "OSM",
        alt: "OpenStreetMap",
    });

    const custom = new google.maps.StyledMapType(
        customMap.style, {name: customMap.name, alt: customMap.alt},
    );

    map.mapTypes.set("osm", OSM);
    map.mapTypes.set("custom", custom);
}

function initGoogleMap() {
    google.maps.Map = class extends google.maps.Map {
        constructor(...args) {
            super(...args);
            loadExtraMaps(this);

            google.maps.event.addListenerOnce(this, "tilesloaded", () => {
                this.setMapTypeId(getDefaultMap());
                addMapSelector(this);

                google.maps.event.addListener(this, "maptypeid_changed", () => {
                    let map = this.getMapTypeId();
                    let defaultMap = getDefaultMap();
                    // If map changed by itself, set it back.
                    if (map !== defaultMap) {
                        this.setMapTypeId(defaultMap);
                        console.log("changed by itself");
                    }
                });
            });
        }
    }
}

function addStyle(css) {
    let style = document.createElement('style');
    let head = document.getElementsByTagName("head")[0];

    style.innerHTML = css;
    head.appendChild(style);
}

(function() {
    document.addEventListener('DOMContentLoaded', () => {
        addStyle(`
            [class*="round-indicator_roundIndicator__"] {
                max-width: fit-content;
            }
            .map-selector {
                background-color: #fff;
                height: 30px;
                max-width = 25%;
                border: 2px solid #fff;
                box-shadow: 0 2px 6px rgba(0,0,0,.3);
                color: rgb(25,25,25);
                cursor: pointer;
                font-family: Roboto, Arial, sans-serif;
                font-size: 15px;
                margin: 10px 10px 22px;
                padding: 0 5px;
                outline: none;
                border-radius: 3px;
                z-index: 100;
            }
        `);

        new MutationObserver(function() {
            let script = document.querySelector("[src*='maps.googleapis.com/maps/api']");

            if (script) {
                this.disconnect();
                script.onload = () => initGoogleMap();
            }
        }).observe(document.head, {childList: true, subtree: true});
    })
})();