Google中国卫星道路纠偏 V6.9

基于V6.7稳定拖动与独立搜索;增加餐厅/酒店/推荐活动/博物馆/公共交通/药店/ATM快捷搜索按钮;Google卫星WGS84 + 道路GCJ02自动纠偏

Ten skrypt nie powinien być instalowany bezpośrednio. Jest to biblioteka dla innych skyptów do włączenia dyrektywą meta // @require https://update.greasyfork.org/scripts/593597/1916994/Google%E4%B8%AD%E5%9B%BD%E5%8D%AB%E6%98%9F%E9%81%93%E8%B7%AF%E7%BA%A0%E5%81%8F%20V69.js

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Greasemonkey lub Violentmonkey.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

Aby zainstalować ten skrypt, wymagana jest instalacje jednego z następujących rozszerzeń: Tampermonkey, Violentmonkey.

Aby zainstalować ten skrypt, wymagana będzie instalacja rozszerzenia Tampermonkey lub Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

Aby zainstalować ten skrypt, musisz zainstalować rozszerzenie menedżera skryptów użytkownika.

(Mam już menedżera skryptów użytkownika, pozwól mi to zainstalować!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Będziesz musiał zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

Musisz zainstalować rozszerzenie menedżera stylów użytkownika, aby zainstalować ten styl.

(Mam już menedżera stylów użytkownika, pozwól mi to zainstalować!)

// ==UserScript==
// @name         Google中国卫星道路纠偏 V6.9
// @namespace    avhzy-google-map-fix
// @version      6.9.0
// @description  基于V6.8稳定拖动/快捷按钮;修复地名搜索定位,使用Google Maps标准搜索入口并等待真实解析坐标后再同步纠偏地图
// @match        https://www.google.com/maps/*
// @match        https://maps.google.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js
// @resource     LEAFLET_CSS https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    const PI = Math.PI;
    const A = 6378245.0;
    const EE = 0.00669342162296594323;

    const MAP_Z = 100000;
    const UI_Z = 2147483000;
    const STORAGE_KEY = 'avhzy-v69-last-view';
    const PENDING_SEARCH_KEY = 'avhzy-v69-pending-search';

    let autoFollowGoogle = true;
    let roadsVisible = true;
    let roadOpacity = 1.0;

    let isDraggingMap = false;
    let isZoomingMap = false;
    let suppressGoogleSyncUntil = 0;

    let lastGoogleURL = location.href;
    let lastGoogleLat = null;
    let lastGoogleLng = null;

    let zoomAnimationFrame = 0;
    let pendingZoomEvent = null;

    GM_addStyle(
        GM_getResourceText('LEAFLET_CSS')
    );

    GM_addStyle(`
        #avhzyChinaMapV69 {
            position: fixed !important;
            inset: 0 !important;
            z-index: ${MAP_Z} !important;
            background: #000;
            overflow: hidden;
            pointer-events: auto !important;
        }

        #avhzyChinaMapV69 .leaflet-top.leaflet-left {
            top: 76px;
            left: 10px;
        }

        #avhzyV69SearchBar {
            position: fixed;
            left: 18px;
            top: 14px;
            z-index: ${UI_Z};

            width: 398px;
            max-width: calc(100vw - 36px);
            height: 48px;

            display: flex;
            align-items: center;
            gap: 4px;

            padding: 0 7px 0 17px;
            border-radius: 24px;

            background: rgba(255,255,255,.98);

            box-shadow:
                0 2px 8px
                rgba(0,0,0,.28);

            box-sizing: border-box;

            font:
                14px
                Arial,
                sans-serif;
        }

        #avhzyV69SearchInput {
            flex: 1;
            min-width: 0;

            height: 40px;

            border: 0;
            outline: 0;

            background: transparent;

            color: #202124;

            font-size: 15px;

            padding: 0 3px;
        }

        #avhzyV69SearchInput::placeholder {
            color: #70757a;
        }

        #avhzyV69SearchBar button {
            flex: 0 0 auto;

            width: 38px;
            height: 38px;

            border: 0;
            border-radius: 50%;

            background: transparent;

            cursor: pointer;

            font-size: 18px;
            line-height: 38px;

            padding: 0;

            color: #3c4043;
        }

        #avhzyV69SearchBar button:hover {
            background: #f1f3f4;
        }

        #avhzyV69QuickBar {
            position: fixed;

            left: 432px;
            right: 18px;
            top: 14px;

            z-index: ${UI_Z};

            height: 48px;

            display: flex;
            align-items: center;

            gap: 7px;

            overflow-x: auto;
            overflow-y: hidden;

            white-space: nowrap;

            scrollbar-width: none;

            pointer-events: auto;

            padding: 0 2px;

            box-sizing: border-box;
        }

        #avhzyV69QuickBar::-webkit-scrollbar {
            display: none;
        }

        #avhzyV69QuickBar button {
            flex: 0 0 auto;

            height: 38px;

            padding: 0 14px;

            border: 0;

            border-radius: 19px;

            background:
                rgba(255,255,255,.98);

            box-shadow:
                0 1px 4px
                rgba(0,0,0,.24);

            color: #202124;

            cursor: pointer;

            font:
                600 14px
                Arial,
                "Microsoft YaHei",
                sans-serif;

            display: inline-flex;
            align-items: center;

            gap: 7px;
        }

        #avhzyV69QuickBar button:hover {
            background: #f1f3f4;
        }

        .avhzy-v69-chip-icon {
            font-size: 16px;
            line-height: 1;
        }

        #avhzyV69SearchState {
            position: fixed;

            left: 29px;
            top: 68px;

            z-index: ${UI_Z};

            display: none;

            max-width: 520px;

            padding: 7px 11px;

            border-radius: 6px;

            background:
                rgba(32,33,36,.9);

            color: #fff;

            font:
                12px
                Arial,
                sans-serif;

            pointer-events: none;
        }

        #avhzyV69Panel {
            position: fixed;

            right: 14px;
            top: 86px;

            z-index: ${UI_Z};

            width: 198px;

            padding: 10px 12px;

            border-radius: 9px;

            background:
                rgba(255,255,255,.96);

            box-shadow:
                0 2px 12px
                rgba(0,0,0,.28);

            font:
                12px
                Arial,
                sans-serif;

            color: #222;
        }

        #avhzyV69Panel button {
            width: 100%;

            min-height: 31px;

            margin-top: 7px;

            cursor: pointer;

            border: 1px solid #ccc;

            border-radius: 4px;

            background: #fff;
        }

        #avhzyV69Panel input[type="range"] {
            width: 100%;
        }

        #avhzyV69Reopen {
            position: fixed;

            right: 15px;
            bottom: 100px;

            z-index: ${UI_Z};

            padding: 10px 14px;

            cursor: pointer;

            border-radius: 7px;

            border: 1px solid #bbb;

            background: white;

            box-shadow:
                0 2px 10px
                rgba(0,0,0,.25);
        }

        @media (max-width: 1050px) {
            #avhzyV69SearchBar {
                width: 360px;
            }

            #avhzyV69QuickBar {
                left: 390px;
            }
        }

        @media (max-width: 760px) {
            #avhzyV69SearchBar {
                left: 10px;
                right: 10px;

                width: auto;

                max-width: none;
            }

            #avhzyV69QuickBar {
                left: 10px;
                right: 10px;

                top: 68px;
            }

            #avhzyChinaMapV69
            .leaflet-top.leaflet-left {
                top: 126px;
            }

            #avhzyV69Panel {
                top: 128px;

                right: 8px;

                width: 178px;
            }

            #avhzyV69SearchState {
                top: 118px;
            }
        }
    `);


    /************************************************************
     * GCJ-02
     ************************************************************/

    function outOfChina(lng, lat) {

        return (
            lng < 72.004 ||
            lng > 137.8347 ||
            lat < 0.8293 ||
            lat > 55.8271
        );
    }


    function transformLat(x, y) {

        let ret =
            -100.0 +
            2.0 * x +
            3.0 * y +
            0.2 * y * y +
            0.1 * x * y +
            0.2 *
            Math.sqrt(
                Math.abs(x)
            );


        ret +=
            (
                20.0 *
                Math.sin(
                    6.0 *
                    x *
                    PI
                )

                +

                20.0 *
                Math.sin(
                    2.0 *
                    x *
                    PI
                )
            )
            *
            2.0 / 3.0;


        ret +=
            (
                20.0 *
                Math.sin(
                    y *
                    PI
                )

                +

                40.0 *
                Math.sin(
                    y /
                    3.0 *
                    PI
                )
            )
            *
            2.0 / 3.0;


        ret +=
            (
                160.0 *
                Math.sin(
                    y /
                    12.0 *
                    PI
                )

                +

                320.0 *
                Math.sin(
                    y *
                    PI /
                    30.0
                )
            )
            *
            2.0 / 3.0;


        return ret;
    }


    function transformLng(x, y) {

        let ret =
            300.0 +
            x +
            2.0 * y +
            0.1 * x * x +
            0.1 * x * y +
            0.1 *
            Math.sqrt(
                Math.abs(x)
            );


        ret +=
            (
                20.0 *
                Math.sin(
                    6.0 *
                    x *
                    PI
                )

                +

                20.0 *
                Math.sin(
                    2.0 *
                    x *
                    PI
                )
            )
            *
            2.0 / 3.0;


        ret +=
            (
                20.0 *
                Math.sin(
                    x *
                    PI
                )

                +

                40.0 *
                Math.sin(
                    x /
                    3.0 *
                    PI
                )
            )
            *
            2.0 / 3.0;


        ret +=
            (
                150.0 *
                Math.sin(
                    x /
                    12.0 *
                    PI
                )

                +

                300.0 *
                Math.sin(
                    x /
                    30.0 *
                    PI
                )
            )
            *
            2.0 / 3.0;


        return ret;
    }


    function wgsToGcj(lng, lat) {

        if (
            outOfChina(
                lng,
                lat
            )
        ) {

            return {
                lng,
                lat
            };
        }


        let dLat =
            transformLat(
                lng - 105.0,
                lat - 35.0
            );


        let dLng =
            transformLng(
                lng - 105.0,
                lat - 35.0
            );


        const radLat =
            lat /
            180.0 *
            PI;


        let magic =
            Math.sin(
                radLat
            );


        magic =
            1 -
            EE *
            magic *
            magic;


        const sqrtMagic =
            Math.sqrt(
                magic
            );


        dLat =
            (
                dLat *
                180.0
            )
            /
            (
                (
                    A *
                    (
                        1 -
                        EE
                    )
                )
                /
                (
                    magic *
                    sqrtMagic
                )
                *
                PI
            );


        dLng =
            (
                dLng *
                180.0
            )
            /
            (
                A /
                sqrtMagic *
                Math.cos(
                    radLat
                )
                *
                PI
            );


        return {

            lng:
                lng +
                dLng,

            lat:
                lat +
                dLat
        };
    }


    /************************************************************
     * Google Maps URL
     ************************************************************/

    function estimateZoomFromMeters(
        meters
    ) {

        if (
            !Number.isFinite(
                meters
            )
            ||
            meters <= 0
        ) {

            return 15;
        }


        return Math.max(

            3,

            Math.min(

                21,

                Math.round(

                    20 -

                    Math.log2(

                        Math.max(
                            meters,
                            1
                        )

                        /

                        100
                    )
                )
            )
        );
    }


    function getGooglePosition() {

        const url =
            decodeURIComponent(
                location.href
            );


        /*
         * @28.01,120.78,17z
         */

        let match =
            url.match(
                /@(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(\d+(?:\.\d+)?)z/
            );


        if (
            match
        ) {

            return {

                lat:
                    Number(
                        match[1]
                    ),

                lng:
                    Number(
                        match[2]
                    ),

                zoom:
                    Number(
                        match[3]
                    )
            };
        }


        /*
         * @28.01,120.78,8029m
         */

        match =
            url.match(
                /@(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?),(\d+(?:\.\d+)?)m/
            );


        if (
            match
        ) {

            return {

                lat:
                    Number(
                        match[1]
                    ),

                lng:
                    Number(
                        match[2]
                    ),

                zoom:
                    estimateZoomFromMeters(

                        Number(
                            match[3]
                        )
                    )
            };
        }


        /*
         * 地点详情
         */

        match =
            url.match(
                /!3d(-?\d+(?:\.\d+)?).*?!4d(-?\d+(?:\.\d+)?)/
            );


        if (
            match
        ) {

            return {

                lat:
                    Number(
                        match[1]
                    ),

                lng:
                    Number(
                        match[2]
                    ),

                zoom:
                    17
            };
        }


        return null;
    }


    function getQueryFromURL() {

        try {

            const url =
                new URL(
                    location.href
                );


            const query =
                url
                    .searchParams
                    .get(
                        'query'
                    );


            if (
                query
            ) {

                return query;
            }


            const match =
                decodeURIComponent(
                    url.pathname
                )
                .match(
                    /\/maps\/(?:search|place)\/([^/]+)/i
                );


            if (
                match &&
                match[1]
            ) {

                return match[1]
                    .replace(
                        /\+/g,
                        ' '
                    );
            }

        } catch (_) {
        }


        return '';
    }


    /************************************************************
     * V6.9 搜索定位核心
     ************************************************************/

    function buildStandardSearchURL(
        query
    ) {

        return (
            'https://www.google.com/maps/search/?api=1&query='
            +
            encodeURIComponent(
                String(
                    query
                ).trim()
            )
        );
    }


    function getPendingSearch() {

        try {

            const raw =
                sessionStorage
                    .getItem(
                        PENDING_SEARCH_KEY
                    );


            if (
                !raw
            ) {

                return null;
            }


            const value =
                JSON.parse(
                    raw
                );


            if (
                !value ||
                typeof value.query !==
                'string'
            ) {

                return null;
            }


            return value;

        } catch (_) {

            return null;
        }
    }


    function setPendingSearch(
        query,
        center
    ) {

        const value =
            {

                query:
                    String(
                        query
                    ).trim(),

                startLat:
                    center.lat,

                startLng:
                    center.lng,

                startedAt:
                    Date.now()
            };


        try {

            sessionStorage
                .setItem(

                    PENDING_SEARCH_KEY,

                    JSON.stringify(
                        value
                    )
                );

        } catch (_) {
        }


        return value;
    }


    function clearPendingSearch() {

        try {

            sessionStorage
                .removeItem(
                    PENDING_SEARCH_KEY
                );

        } catch (_) {
        }
    }


    function shouldAcceptSearchPosition(
        position,
        pending,
        href,
        now
    ) {

        if (
            !position
        ) {

            return false;
        }


        if (
            !pending
        ) {

            return true;
        }


        /*
         * Google已经进入明确的place页面。
         */

        if (
            /\/maps\/place\//i
                .test(
                    href
                )

            ||

            /!3d-?\d/
                .test(
                    href
                )
        ) {

            return true;
        }


        /*
         * 新坐标和搜索前旧中心不同。
         */

        const moved =

            Math.abs(

                position.lat -

                pending.startLat
            )

            >
            0.000001

            ||

            Math.abs(

                position.lng -

                pending.startLng
            )

            >
            0.000001;


        if (
            moved
        ) {

            return true;
        }


        /*
         * 防止极少数情况下无限等待。
         */

        return (

            now -

            pending.startedAt

            >

            12000
        );
    }


    /************************************************************
     * 保存视图
     ************************************************************/

    function loadStoredView() {

        try {

            const raw =
                sessionStorage
                    .getItem(
                        STORAGE_KEY
                    );


            if (
                !raw
            ) {

                return null;
            }


            const value =
                JSON.parse(
                    raw
                );


            if (
                ![
                    value.lat,
                    value.lng,
                    value.zoom
                ]
                .every(
                    Number.isFinite
                )
            ) {

                return null;
            }


            return value;

        } catch (_) {

            return null;
        }
    }


    function saveStoredView(
        center,
        zoom
    ) {

        try {

            sessionStorage
                .setItem(

                    STORAGE_KEY,

                    JSON.stringify(
                        {
                            lat:
                                center.lat,

                            lng:
                                center.lng,

                            zoom:
                                zoom
                        }
                    )
                );

        } catch (_) {
        }
    }


    /************************************************************
     * Leaflet
     ************************************************************/

    const mapDiv =
        document.createElement(
            'div'
        );


    mapDiv.id =
        'avhzyChinaMapV69';


    document.body.appendChild(
        mapDiv
    );


    const stored =
        loadStoredView();


    const initial =

        getGooglePosition()

        ||

        stored

        ||

        {

            lat:
                27.9943,

            lng:
                120.6994,

            zoom:
                15
        };


    const map =
        L.map(

            mapDiv,

            {

                zoomControl:
                    true,

                attributionControl:
                    false,

                zoomAnimation:
                    true,

                fadeAnimation:
                    false,

                markerZoomAnimation:
                    true,

                zoomSnap:
                    1,

                zoomDelta:
                    1,

                wheelDebounceTime:
                    18,

                wheelPxPerZoomLevel:
                    60,

                doubleClickZoom:
                    true,

                dragging:
                    true,

                scrollWheelZoom:
                    true,

                inertia:
                    true,

                inertiaDeceleration:
                    2800,

                inertiaMaxSpeed:
                    1800,

                easeLinearity:
                    0.22
            }
        );


    map.setView(

        [
            initial.lat,
            initial.lng
        ],

        Math.max(

            3,

            Math.min(

                21,

                Math.round(
                    initial.zoom
                )
            )
        ),

        {
            animate:
                false
        }
    );


    /************************************************************
     * Google Satellite
     ************************************************************/

    const satellite =
        L.tileLayer(

            'https://mt{s}.google.com/vt?' +
            'lyrs=s' +
            '&hl=zh-CN' +
            '&x={x}' +
            '&y={y}' +
            '&z={z}',

            {

                subdomains:
                    [
                        '0',
                        '1',
                        '2',
                        '3'
                    ],

                minZoom:
                    3,

                maxZoom:
                    21,

                maxNativeZoom:
                    21,

                tileSize:
                    256,

                keepBuffer:
                    5,

                updateWhenZooming:
                    true,

                updateWhenIdle:
                    true
            }
        );


    satellite.addTo(
        map
    );


    /************************************************************
     * Google Roads
     ************************************************************/

    map.createPane(
        'avhzyRoadPane'
    );


    const roadPane =
        map.getPane(
            'avhzyRoadPane'
        );


    roadPane.style.zIndex =
        '450';


    roadPane.style.pointerEvents =
        'none';


    roadPane.style.willChange =
        'transform, translate';


    const roads =
        L.tileLayer(

            'https://mt{s}.google.com/vt?' +
            'lyrs=h' +
            '&hl=zh-CN' +
            '&gl=CN' +
            '&x={x}' +
            '&y={y}' +
            '&z={z}',

            {

                subdomains:
                    [
                        '0',
                        '1',
                        '2',
                        '3'
                    ],

                pane:
                    'avhzyRoadPane',

                minZoom:
                    3,

                maxZoom:
                    21,

                maxNativeZoom:
                    21,

                tileSize:
                    256,

                opacity:
                    roadOpacity,

                keepBuffer:
                    5,

                updateWhenZooming:
                    true,

                updateWhenIdle:
                    true
            }
        );


    roads.addTo(
        map
    );


    /************************************************************
     * 道路纠偏
     ************************************************************/

    function calculateRoadOffset(
        center,
        zoom
    ) {

        if (
            outOfChina(
                center.lng,
                center.lat
            )
        ) {

            return {

                x:
                    0,

                y:
                    0
            };
        }


        const gcj =
            wgsToGcj(
                center.lng,
                center.lat
            );


        const wgsPixel =
            map.project(
                center,
                zoom
            );


        const gcjPixel =
            map.project(

                L.latLng(
                    gcj.lat,
                    gcj.lng
                ),

                zoom
            );


        return {

            x:
                wgsPixel.x -
                gcjPixel.x,

            y:
                wgsPixel.y -
                gcjPixel.y
        };
    }


    function renderStatus(
        offset
    ) {

        const element =
            document
                .getElementById(
                    'avhzyV69Offset'
                );


        if (
            !element
        ) {

            return;
        }


        const center =
            map.getCenter();


        element.innerHTML =

            '偏移:' +

            offset.x.toFixed(1) +

            ' / ' +

            offset.y.toFixed(1) +

            ' px'

            +

            '<br>' +

            center.lat.toFixed(6) +

            ', ' +

            center.lng.toFixed(6)

            +

            '<br>Zoom ' +

            map
                .getZoom()
                .toFixed(0);
    }


    function applyRoadOffset(
        center,
        zoom,
        updateText = true
    ) {

        if (
            !roadsVisible
        ) {

            return;
        }


        const targetCenter =

            center

            ||

            map.getCenter();


        const targetZoom =

            zoom ===
            undefined

            ?

            map.getZoom()

            :

            zoom;


        const offset =
            calculateRoadOffset(
                targetCenter,
                targetZoom
            );


        roadPane.style.translate =

            offset.x +

            'px ' +

            offset.y +

            'px';


        if (
            updateText
        ) {

            renderStatus(
                offset
            );
        }
    }


    function scheduleZoomOffset(
        event
    ) {

        pendingZoomEvent =
            event;


        if (
            zoomAnimationFrame
        ) {

            return;
        }


        zoomAnimationFrame =
            requestAnimationFrame(

                function () {

                    zoomAnimationFrame =
                        0;


                    if (
                        !pendingZoomEvent
                    ) {

                        return;
                    }


                    applyRoadOffset(

                        pendingZoomEvent.center,

                        pendingZoomEvent.zoom,

                        false
                    );


                    pendingZoomEvent =
                        null;
                }
            );
    }


    /************************************************************
     * 拖动 / Zoom
     ************************************************************/

    map.on(
        'dragstart',
        function () {

            isDraggingMap =
                true;
        }
    );


    map.on(
        'dragend',
        function () {

            isDraggingMap =
                false;


            suppressGoogleSyncUntil =

                performance.now()

                +

                900;


            applyRoadOffset();


            saveStoredView(

                map.getCenter(),

                map.getZoom()
            );
        }
    );


    map.on(
        'zoomstart',
        function () {

            isZoomingMap =
                true;
        }
    );


    map.on(
        'zoomanim',
        scheduleZoomOffset
    );


    map.on(
        'zoomend',
        function () {

            isZoomingMap =
                false;


            suppressGoogleSyncUntil =

                performance.now()

                +

                700;


            applyRoadOffset();


            saveStoredView(

                map.getCenter(),

                map.getZoom()
            );
        }
    );


    /************************************************************
     * 搜索框
     ************************************************************/

    const searchBar =
        document.createElement(
            'form'
        );


    searchBar.id =
        'avhzyV69SearchBar';


    searchBar.innerHTML = `

        <input
            id="avhzyV69SearchInput"
            type="text"
            autocomplete="off"
            spellcheck="false"
            placeholder="搜索 Google 地图"
            aria-label="搜索 Google 地图"
        >

        <button
            id="avhzyV69Clear"
            type="button"
            title="清空"
        >
            ×
        </button>

        <button
            id="avhzyV69SearchButton"
            type="submit"
            title="搜索"
        >
            🔍
        </button>
    `;


    document.body.appendChild(
        searchBar
    );


    const searchInput =
        document.getElementById(
            'avhzyV69SearchInput'
        );


    const existingQuery =
        getQueryFromURL();


    const existingPending =
        getPendingSearch();


    if (
        existingPending &&
        existingPending.query
    ) {

        searchInput.value =
            existingPending.query;

    } else if (
        existingQuery
    ) {

        searchInput.value =
            existingQuery;
    }


    const searchState =
        document.createElement(
            'div'
        );


    searchState.id =
        'avhzyV69SearchState';


    document.body.appendChild(
        searchState
    );


    function showSearchState(
        text,
        timeout = 0
    ) {

        searchState.textContent =
            text;


        searchState.style.display =
            'block';


        if (
            timeout >
            0
        ) {

            setTimeout(

                function () {

                    if (
                        searchState.textContent ===
                        text
                    ) {

                        searchState.style.display =
                            'none';
                    }
                },

                timeout
            );
        }
    }


    if (
        existingPending
    ) {

        showSearchState(

            '正在等待 Google 定位:'

            +

            existingPending.query
        );
    }


    function parseCoordinates(
        text
    ) {

        const match =
            String(
                text
            )
            .trim()
            .match(
                /^\s*(-?\d{1,2}(?:\.\d+)?)\s*[,, ]\s*(-?\d{1,3}(?:\.\d+)?)\s*$/
            );


        if (
            !match
        ) {

            return null;
        }


        const lat =
            Number(
                match[1]
            );


        const lng =
            Number(
                match[2]
            );


        if (
            !Number.isFinite(
                lat
            )
            ||
            !Number.isFinite(
                lng
            )
            ||
            lat < -90
            ||
            lat > 90
            ||
            lng < -180
            ||
            lng > 180
        ) {

            return null;
        }


        return {
            lat,
            lng
        };
    }


    function performTextSearch(
        query
    ) {

        const cleanQuery =
            String(
                query ||
                ''
            )
            .trim();


        if (
            !cleanQuery
        ) {

            searchInput.focus();

            return;
        }


        /*
         * 经纬度直接定位。
         */

        const coords =
            parseCoordinates(
                cleanQuery
            );


        if (
            coords
        ) {

            clearPendingSearch();


            map.setView(

                [
                    coords.lat,
                    coords.lng
                ],

                Math.max(
                    16,
                    map.getZoom()
                ),

                {
                    animate:
                        false
                }
            );


            applyRoadOffset();


            saveStoredView(

                map.getCenter(),

                map.getZoom()
            );


            showSearchState(

                '已跳转到输入坐标',

                1600
            );


            return;
        }


        /*
         * 普通地名:
         *
         * 保存搜索前中心。
         */

        const center =
            map.getCenter();


        saveStoredView(

            center,

            map.getZoom()
        );


        /*
         * 写入pending状态。
         *
         * 页面重新加载后仍可知道:
         * 正在等待Google解析新地点。
         */

        setPendingSearch(

            cleanQuery,

            center
        );


        showSearchState(

            'Google 正在定位:'

            +

            cleanQuery
        );


        /*
         * V6.9最关键变化:
         *
         * 不再使用:
         *
         * /maps/search/地名/@旧坐标...
         *
         * 只用标准Google Maps搜索入口。
         */

        location.href =
            buildStandardSearchURL(
                cleanQuery
            );
    }


    searchBar.addEventListener(
        'submit',
        function (
            event
        ) {

            event.preventDefault();


            performTextSearch(
                searchInput.value
            );
        }
    );


    document
        .getElementById(
            'avhzyV69Clear'
        )
        .addEventListener(

            'click',

            function () {

                searchInput.value =
                    '';


                searchInput.focus();
            }
        );


    /************************************************************
     * 快捷按钮
     ************************************************************/

    const quickBar =
        document.createElement(
            'div'
        );


    quickBar.id =
        'avhzyV69QuickBar';


    const quickItems =
        [

            {
                label:
                    '餐厅',

                query:
                    '餐厅',

                icon:
                    '🍴'
            },

            {
                label:
                    '酒店',

                query:
                    '酒店',

                icon:
                    '🛏'
            },

            {
                label:
                    '推荐活动',

                query:
                    '旅游景点',

                icon:
                    '📷'
            },

            {
                label:
                    '博物馆',

                query:
                    '博物馆',

                icon:
                    '🏛'
            },

            {
                label:
                    '公共交通',

                query:
                    '公共交通',

                icon:
                    '🚌'
            },

            {
                label:
                    '药店',

                query:
                    '药店',

                icon:
                    '⚕'
            },

            {
                label:
                    '自动取款机',

                query:
                    'ATM',

                icon:
                    '🏧'
            }
        ];


    quickItems.forEach(

        function (
            item
        ) {

            const button =
                document.createElement(
                    'button'
                );


            button.type =
                'button';


            button.innerHTML =

                '<span class="avhzy-v69-chip-icon">'

                +

                item.icon

                +

                '</span>'

                +

                '<span>'

                +

                item.label

                +

                '</span>';


            button.title =
                '搜索' +
                item.label;


            button.addEventListener(

                'click',

                function () {

                    searchInput.value =
                        item.label;


                    performTextSearch(
                        item.query
                    );
                }
            );


            quickBar.appendChild(
                button
            );
        }
    );


    document.body.appendChild(
        quickBar
    );


    /************************************************************
     * 控制面板
     ************************************************************/

    const panel =
        document.createElement(
            'div'
        );


    panel.id =
        'avhzyV69Panel';


    panel.innerHTML = `

        <div
            style="
                font-weight:bold;
                font-size:14px;
                margin-bottom:5px;
            "
        >
            🇨🇳 Google纠偏 V6.9
        </div>

        <div
            style="
                color:#188038;
                margin-bottom:6px;
            "
        >
            ● 地名搜索定位修复
        </div>

        <div
            id="avhzyV69Offset"
            style="
                color:#666;
                font-size:11px;
                line-height:16px;
                margin-bottom:7px;
            "
        >
            正在计算...
        </div>

        <div>
            道路透明度
        </div>

        <input
            id="avhzyV69Opacity"
            type="range"
            min="0"
            max="100"
            value="100"
        >

        <button
            id="avhzyV69Road"
        >
            隐藏道路
        </button>

        <button
            id="avhzyV69Sync"
        >
            📍 同步 Google 当前地点
        </button>

        <button
            id="avhzyV69Follow"
        >
            自动跟随搜索:开启
        </button>

        <button
            id="avhzyV69Native"
        >
            返回原版 Google Maps
        </button>
    `;


    document.body.appendChild(
        panel
    );


    document
        .getElementById(
            'avhzyV69Opacity'
        )
        .addEventListener(

            'input',

            function () {

                roadOpacity =
                    Number(
                        this.value
                    )
                    /
                    100;


                roads.setOpacity(
                    roadOpacity
                );
            }
        );


    document
        .getElementById(
            'avhzyV69Road'
        )
        .addEventListener(

            'click',

            function () {

                roadsVisible =
                    !roadsVisible;


                if (
                    roadsVisible
                ) {

                    if (
                        !map.hasLayer(
                            roads
                        )
                    ) {

                        roads.addTo(
                            map
                        );
                    }


                    this.textContent =
                        '隐藏道路';


                    requestAnimationFrame(

                        function () {

                            applyRoadOffset();
                        }
                    );

                } else {

                    if (
                        map.hasLayer(
                            roads
                        )
                    ) {

                        map.removeLayer(
                            roads
                        );
                    }


                    this.textContent =
                        '显示道路';
                }
            }
        );


    /************************************************************
     * Google搜索同步
     ************************************************************/

    function syncFromGoogle(
        force = false
    ) {

        if (
            !autoFollowGoogle &&
            !force
        ) {

            return;
        }


        /*
         * 用户正在手动拖动/缩放时,
         * Google不能把地图拉回去。
         */

        if (
            !force &&
            (
                isDraggingMap
                ||
                isZoomingMap
                ||
                performance.now() <
                suppressGoogleSyncUntil
            )
        ) {

            return;
        }


        const position =
            getGooglePosition();


        if (
            !position
        ) {

            return;
        }


        const pending =
            getPendingSearch();


        /*
         * 有地名搜索正在进行时,
         * 如果Google还没有真正解析出新地点,
         * 就继续等。
         */

        if (
            !force
            &&
            pending
            &&
            !shouldAcceptSearchPosition(

                position,

                pending,

                location.href,

                Date.now()
            )
        ) {

            showSearchState(

                'Google 正在解析:'

                +

                pending.query
            );


            return;
        }


        if (
            !force
            &&
            lastGoogleLat !==
            null
            &&
            Math.abs(

                position.lat -

                lastGoogleLat
            )
            <
            0.0000001
            &&
            Math.abs(

                position.lng -

                lastGoogleLng
            )
            <
            0.0000001
            &&
            !pending
        ) {

            return;
        }


        lastGoogleLat =
            position.lat;


        lastGoogleLng =
            position.lng;


        const zoom =
            Math.max(

                3,

                Math.min(

                    21,

                    Number.isFinite(
                        position.zoom
                    )

                    ?

                    Math.round(
                        position.zoom
                    )

                    :

                    map.getZoom()
                )
            );


        map.setView(

            [
                position.lat,
                position.lng
            ],

            zoom,

            {
                animate:
                    false
            }
        );


        applyRoadOffset();


        saveStoredView(

            map.getCenter(),

            map.getZoom()
        );


        /*
         * pending搜索成功完成。
         */

        if (
            pending
        ) {

            searchInput.value =
                pending.query;


            clearPendingSearch();


            showSearchState(

                '已定位:'

                +

                pending.query,

                1800
            );

        } else {

            const query =
                getQueryFromURL();


            if (
                query
                &&
                document.activeElement !==
                searchInput
            ) {

                searchInput.value =
                    query;
            }


            searchState.style.display =
                'none';
        }
    }


    function monitorGoogleURL() {

        const current =
            location.href;


        if (
            current ===
            lastGoogleURL
        ) {

            return;
        }


        lastGoogleURL =
            current;


        /*
         * Google解析地点时URL可能多次变化。
         */

        [
            180,
            600,
            1200,
            2500
        ]
        .forEach(

            function (
                delay
            ) {

                setTimeout(

                    function () {

                        syncFromGoogle(
                            false
                        );
                    },

                    delay
                );
            }
        );
    }


    /*
     * 关键:
     *
     * 即使Google内部状态变化但URL暂时没变,
     * pending搜索期间仍持续尝试读取新坐标。
     */

    function pollPendingSearch() {

        if (
            getPendingSearch()
        ) {

            syncFromGoogle(
                false
            );
        }
    }


    document
        .getElementById(
            'avhzyV69Sync'
        )
        .addEventListener(

            'click',

            function () {

                clearPendingSearch();


                lastGoogleLat =
                    null;


                lastGoogleLng =
                    null;


                suppressGoogleSyncUntil =
                    0;


                syncFromGoogle(
                    true
                );
            }
        );


    const followButton =
        document.getElementById(
            'avhzyV69Follow'
        );


    followButton.addEventListener(

        'click',

        function () {

            autoFollowGoogle =
                !autoFollowGoogle;


            followButton.textContent =

                '自动跟随搜索:'

                +

                (
                    autoFollowGoogle

                    ?

                    '开启'

                    :

                    '关闭'
                );


            if (
                autoFollowGoogle
            ) {

                lastGoogleLat =
                    null;


                lastGoogleLng =
                    null;


                suppressGoogleSyncUntil =
                    0;


                syncFromGoogle(
                    false
                );
            }
        }
    );


    /************************************************************
     * 返回原Google
     ************************************************************/

    const reopen =
        document.createElement(
            'button'
        );


    reopen.id =
        'avhzyV69Reopen';


    reopen.textContent =
        '🇨🇳 打开纠偏地图';


    reopen.style.display =
        'none';


    document.body.appendChild(
        reopen
    );


    document
        .getElementById(
            'avhzyV69Native'
        )
        .addEventListener(

            'click',

            function () {

                mapDiv.style.display =
                    'none';


                panel.style.display =
                    'none';


                searchBar.style.display =
                    'none';


                quickBar.style.display =
                    'none';


                searchState.style.display =
                    'none';


                reopen.style.display =
                    'block';
            }
        );


    reopen.addEventListener(

        'click',

        function () {

            mapDiv.style.display =
                'block';


            panel.style.display =
                'block';


            searchBar.style.display =
                'flex';


            quickBar.style.display =
                'flex';


            reopen.style.display =
                'none';


            setTimeout(

                function () {

                    map.invalidateSize(
                        true
                    );


                    lastGoogleLat =
                        null;


                    lastGoogleLng =
                        null;


                    syncFromGoogle(
                        false
                    );


                    applyRoadOffset();

                },

                80
            );
        }
    );


    /************************************************************
     * Resize
     ************************************************************/

    window.addEventListener(

        'resize',

        function () {

            setTimeout(

                function () {

                    map.invalidateSize(
                        false
                    );


                    applyRoadOffset();

                },

                80
            );
        }
    );


    /************************************************************
     * 监控
     ************************************************************/

    setInterval(
        monitorGoogleURL,
        700
    );


    setInterval(
        pollPendingSearch,
        900
    );


    /************************************************************
     * 初始化
     ************************************************************/

    setTimeout(

        function () {

            map.invalidateSize(
                true
            );


            lastGoogleLat =
                null;


            lastGoogleLng =
                null;


            /*
             * 如果这是刚搜索后重新加载的页面,
             * 不允许强行同步旧坐标。
             */

            if (
                getPendingSearch()
            ) {

                syncFromGoogle(
                    false
                );

            } else {

                syncFromGoogle(
                    true
                );
            }


            applyRoadOffset();

        },

        700
    );

})();