Greasy Fork

Greasy Fork is available in English.

Google Street View Panorama Info

Displays the country name, coordinates, and panoId for a given Google Street View panorama

目前为 2025-01-21 提交的版本,查看 最新版本

// ==UserScript==
// @name         Google Street View Panorama Info
// @namespace    http://greasyfork.icu/users/1340965
// @version      1.9
// @description  Displays the country name, coordinates, and panoId for a given Google Street View panorama
// @author       ZecaGeo
// @run-at       document-end
// @match        https://www.google.com/maps/*
// @match        https://www.google.at/maps/*
// @grant        GM_log
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @icon         https://www.google.com/s2/favicons?sz=64&domain=geohints.com
// @connect      nominatim.openstreetmap.org
// @license      MIT
// ==/UserScript==
/* jshint esversion: 11 */

(() => {
    'use strict'

    /* globals */
    let
    VERSION = GM_info.script.version,
    DEBUG_MODE = true

    // debug output functions
    function toLog(typ, msg) {
        if (DEBUG_MODE) {
            if(console && console[typ] && console.group && console.groupEnd) {
                console[typ](msg)
            } else {
                GM_log(typ + ": " + msg.toString())
            }
        }
    }

    function log(msg) {
        toLog("log", msg);
    }

    function debug(msg) {
        toLog("debug", msg);
    }

    function error(msg) {
        toLog("error", msg);
    }

    // GM_xmlhttpRequest response info
    function responseInfo(r) {
        debug([	"",
                "finalUrl: \t\t" + (r.finalUrl || "-"),
                "status: \t\t" + (r.status || "-"),
                "statusText: \t" + (r.statusText || "-"),
                "readyState: \t" + (r.readyState || "-"),
                "responseHeaders: " + (r.responseHeaders.replaceAll('\r\n',";") || "-"),
                "responseText: \t" + (r.responseText || "-")
            ].join("\n"));
    }

    function cloneNode(originalNode, value, isClickable) {
        let node = originalNode.cloneNode(true)
        node.querySelector('h2').innerText = value
        if (isClickable) {
            node.style.cursor = "pointer"
            node.onclick = () => GM_setClipboard(value)
        }
        return node
    }

    function parseCoordinates(url) {
        const regex_coordinates = new RegExp(/@(-?\d+\.\d+),(-?\d+\.\d+)/)
        const regex_panoId = new RegExp(/!1s(.+)!2e/)

        const result_coordinates = regex_coordinates.exec(url)
        const result_panoId = regex_panoId.exec(url)

        return [...result_coordinates.slice(1), result_panoId]
    }


    async function updateTitleCard(_, observer) {
        const svElement = document.querySelector('.pB8Nmf div:last-child')
        if (svElement) {
            observer.disconnect()

            log('Starting DOM manipulation')

            let h2Element = document.createElement('h2')
            h2Element.setAttribute('class', 'lsdM5 fontBodySmall')
            h2Element.setAttribute('jsan', '7.lsdM5,7.fontBodySmall')

            let divElement = document.createElement('div')
            divElement.appendChild(h2Element)

            debug(divElement)
            debug(svElement)

            let [latitude, longitude, panoId] = parseCoordinates(window.location.href)

            let countryElement = cloneNode(divElement, '', false)
            svElement.parentNode.insertBefore(countryElement, svElement.nextSibling)

            let latitudeElement = cloneNode(divElement, latitude, true)
            svElement.parentNode.insertBefore(latitudeElement, countryElement.nextSibling)

            let longitudeElement = cloneNode(divElement, longitude, true)
            svElement.parentNode.insertBefore(longitudeElement, latitudeElement.nextSibling)

            let panoIdElement = cloneNode(divElement, panoId, true)
            svElement.parentNode.insertBefore(panoIdElement, longitudeElement.nextSibling)

            let url = `https://nominatim.openstreetmap.org/reverse?lat=${latitude}&lon=${longitude}&format=json&accept-language=en-US`
            countryElement.querySelector('h2').innerText = await getCountry(url)
        }
    }

    async function getCountry(url) {
        const response = await promiseRequest('GET', url)
        const data = JSON.parse(response.responseText)
        return data?.address?.country ?? "Country not found"
    }

    function promiseRequest(method, url) {
        log(["---PROMISEREQUEST---",
             "\tmethod: " + method,
             "\turl: " + url,
             "---PROMISEREQUEST---"
		    ].join("\n"));

        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: method,
                url: url,
                onload: result => {
                    responseInfo(result)
                    if(result.status >= 200 && result.status < 300) { // ok
                        resolve(result)
                    } else { // error
                        reject(result.responseText)
                    }
                },
                ontimeout: () => {
                    let l = new URL(url);
                    reject(' timeout detected: "no answer from ' + l.host + ' for ' + l.timeout / 1000 + 's"');
                },
                onerror: result => { // network error
                    responseInfo(result)
                    reject(' error: ' + result.status + ', message: ' + result.statusText)
                }
            });
        });
    }

    const init = () => {
        const observer = new MutationObserver(updateTitleCard)
        observer.observe(document.body, { childList: true, subtree: true })
    }

    init();
})();