Overlay Gulf of Mexico label on Google Maps

Restore the Gulf of Mexico to its original name in Google Maps.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Overlay Gulf of Mexico label on Google Maps
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Restore the Gulf of Mexico to its original name in Google Maps.
// @author       You
// @match        https://www.google.com/maps/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=google.com
// @license      MIT
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
  'use strict';

  // ——— UNIVERSAL REPLACER ———
  function replaceLabels(root) {
    // 1) ARIA labels
    root.querySelectorAll('[aria-label]').forEach(el => {
      const aria = el.getAttribute('aria-label');
      if (aria.includes('Gulf of America')) {
        el.setAttribute(
          'aria-label',
          aria.replace(/Gulf of America/g, 'Gulf of Mexico')
        );
      }
    });

    // 2) Text nodes everywhere
    const walker = document.createTreeWalker(
      root,
      NodeFilter.SHOW_TEXT,
      null,
      false
    );
    let node;
    while (node = walker.nextNode()) {
      if (node.nodeValue.includes('Gulf of America')) {
        node.nodeValue = node.nodeValue.replace(
          /Gulf of America/g,
          'Gulf of Mexico'
        );
      }
    }
  }

  // run once on page load (in case you already have a popup open)
  replaceLabels(document);

    // === catch both new nodes AND text-only updates ===
  const observer = new MutationObserver(mutations => {
    for (const m of mutations) {
      // newly injected nodes
      for (const node of m.addedNodes) {
        if (node.nodeType === 1) replaceLabels(node);
      }
      // in-place text changes
      if (m.type === 'characterData') {
        const txt = m.target.nodeValue;
        if (txt && txt.includes('Gulf of America')) {
          m.target.nodeValue = txt.replace(/Gulf of America/g, 'Gulf of Mexico');
        }
      }
    }
  });
  observer.observe(document.body, {
    childList: true,
    subtree: true,
    characterData: true
  });

  // === also re-run on marker hover ===
  document.body.addEventListener('mouseover', e => {
    if (e.target.classList.contains('ET197e')) {
      // slight delay to let Google finish drawing
      setTimeout(() => replaceLabels(document), 50);
    }
  });


  // watch for any new DOM nodes (when you click somewhere, Google injects the popup)
  new MutationObserver(mutations => {
    for (let m of mutations) {
      for (let node of m.addedNodes) {
        if (node.nodeType === 1) {
          replaceLabels(node);
        }
      }
    }
  }).observe(document.body, {
    childList: true,
    subtree: true
  });

  const DEFAULT = {
    lat:            24.745124,
    lng:           -91.737921,
    backgroundColor: 'rgb(114, 212, 232)',
    fontSize:       '12px'
  };

  const coordsMap = [
    { zoom: 2, lat: 24.941090, lng: -90.102396, backgroundColor: 'rgb(109, 212, 232)', fontSize: '12px' },
    { zoom: 3, lat: 24.941090, lng: -90.102396, backgroundColor: 'rgb(109, 212, 232)', fontSize: '12px' },
    { zoom: 4, lat: 24.501996, lng: -90.058450, backgroundColor: 'rgb(109, 212, 232)', fontSize: '12px' },
    { zoom: 5, lat: 24.786898, lng: -90.065263, backgroundColor: 'rgb(114, 212, 232)', fontSize: '12px' },
    { zoom: 6, lat: 25.100375, lng: -90.014505, backgroundColor: 'rgb(121, 212, 232)', fontSize: '12px' },
    { zoom: 7, lat: 25.189882, lng: -90.047464, backgroundColor: 'rgb(127, 215, 235)', fontSize: '12px' },
    { zoom: 8, lat: 25.239890, lng: -90.065263, backgroundColor: 'rgb(131, 214, 235)', fontSize: '12px' },
    { zoom: 9, lat: 25.272183, lng: -90.061959, backgroundColor: 'rgb(138, 217, 237)', fontSize: '12px' },
    { zoom: 10, lat: 25.290499, lng: -90.063943, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 11, lat: 25.296707, lng: -90.066003, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 12, lat: 25.300121, lng: -90.066003, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 13, lat: 25.301983, lng: -90.065832, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 14, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 15, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 16, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 17, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 18, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 19, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 20, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
    { zoom: 21, lat: 25.303225, lng: -90.06583, backgroundColor: 'rgb(145, 217, 237)', fontSize: '12px' },
  ];

  function getEntryForZoom(zoom) {
    const e = coordsMap.find(o => o.zoom === zoom);
    return e || DEFAULT;
  }

  function latLngToPoint(lat, lng, centerLat, centerLng, zoom, width, height) {
    const tileSize = 256;
    const scale = tileSize * Math.pow(2, zoom);

    const sinC = Math.min(Math.max(Math.sin(centerLat * Math.PI/180), -0.9999), 0.9999);
    const worldCX = (centerLng + 180)/360 * scale;
    const worldCY = (0.5 - Math.log((1 + sinC)/(1 - sinC))/(4*Math.PI)) * scale;

    const sinT = Math.min(Math.max(Math.sin(lat * Math.PI/180), -0.9999), 0.9999);
    const worldX = (lng + 180)/360 * scale;
    const worldY = (0.5 - Math.log((1 + sinT)/(1 - sinT))/(4*Math.PI)) * scale;

    return {
      x: worldX - worldCX + width/2,
      y: worldY - worldCY + height/2
    };
  }

  let overlay;
  function createOverlay() {
    overlay = document.createElement('div');
    overlay.innerHTML = 'Gulf<br>of Mexico';
    Object.assign(overlay.style, {
      position: 'fixed',
      pointerEvents: 'none',
      whiteSpace: 'pre-line',
      textAlign: 'center',
      borderRadius: '4px',
      padding: '2px',
      transform: 'translate(-50%, -100%)',
      zIndex: '9999',
      display: 'inline-block',
      color: 'rgb(12, 125, 148)'
    });
    document.body.appendChild(overlay);
  }

  function updateOverlay() {
    const container = document.querySelector('.widget-scene');
    if (!container) return;
    if (!overlay) createOverlay();

    const m = window.location.pathname.match(/@(-?\d+\.\d+),(-?\d+\.\d+),([\d.]+)z/);
    if (!m) return;
    const centerLat = parseFloat(m[1]);
    const centerLng = parseFloat(m[2]);
    const zoom = Math.round(parseFloat(m[3]));

    const { lat, lng, backgroundColor, fontSize } = getEntryForZoom(zoom);

    const rect = container.getBoundingClientRect();
    const { x, y } = latLngToPoint(lat, lng, centerLat, centerLng, zoom, rect.width, rect.height);

    overlay.style.left = `${rect.left + x}px`;
    overlay.style.top = `${rect.top + y}px`;

    overlay.style.backgroundColor = backgroundColor;
    overlay.style.fontSize = fontSize;
  }

  setInterval(updateOverlay, 200);
})();