eBird Alerts Map

Adds a Google Map with eBird alert locations as markers.

当前为 2024-04-19 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         eBird Alerts Map
// @namespace    http://tampermonkey.net/
// @version      2024-04-19_1.4
// @description  Adds a Google Map with eBird alert locations as markers.
// @author       Ruslan Balagansky
// @license      MIT
// @match        https://ebird.org/alert/needs/*
// @match        https://ebird.org/alert/rba/*
// @match        https://ebird.org/alert/summary?sid=*
// @require      http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==

(function() {
    'use strict';

    const mapDivId = 'userscript-map';

    // load api key from storage or use default
    const defaultApiKey = "AIzaSyCNhkdcs7rdwXoaSpqDzNLBnA-4Tu_7v-4"  // restricted to ebird.org
    var apiKey = GM_getValue("apiKey", defaultApiKey);

    // handle case when stored value is an empty string
    if (!apiKey) {
        apiKey = defaultApiKey;
    }

    // allow user to set a custom key via script's menu (via Tampermonkey extension icon)
    function promptForApiKey() {
        apiKey = prompt("Enter a Google Maps API key or accept the author-provided one: ", defaultApiKey);
        GM_setValue("apiKey", apiKey);
    }

    GM_registerMenuCommand("Change Google Maps API Key", promptForApiKey);

    // initializes the map (called by legacy API callback)
    function initMap() {
        // Collect locations data
        var locations = {};
        const mapRegex = /Map: (.+), (.+)/;
        const observations = document.getElementsByClassName("Observation");
        for (const obs of observations) {
            const species = obs.getElementsByClassName("Observation-species")[0];
            const specRef = species.getElementsByTagName("a")[0];
            const speciesCode = specRef.getAttribute("data-species-code");
            const fourLetterSpecies = speciesCode.slice(0, 2) + speciesCode.slice(3, 5)

            const meta = obs.getElementsByClassName("Observation-meta")[0];
            let coords;
            let key;
            for (const a of meta.getElementsByTagName("a")) {
                const title = a.getAttribute("title");
                const mapMatch = title.match(mapRegex);
                if (mapMatch) {
                    key = title;
                    coords = { lat: Number(mapMatch[1]), lng: Number(mapMatch[2]) };
                }
            }

            if (!(key in locations)) {
                locations[key] = {
                    labels:new Set(),
                    html:[],
                    speciesHtml:{}
                };
            }
            var loc = locations[key];
            loc.coords = coords;
            loc.labels.add(fourLetterSpecies);
            loc.html.push(obs.innerHTML);
            loc.speciesHtml[speciesCode] = species.innerHTML;
        }

        // compute the center based on alert locations
        var mapCenter = { lat: 32.92, lng: -116.85 }; // default to San Diego

        let minLat = Infinity, minLng = Infinity, maxLat = -Infinity, maxLng = -Infinity;
        for (const loc of Object.values(locations)) {
            const lat = loc.coords.lat;
            const lng = loc.coords.lng;
            minLat = Math.min(minLat, lat);
            maxLat = Math.max(maxLat, lat);
            minLng = Math.min(minLng, lng);
            maxLng = Math.max(maxLng, lng);
        }

        mapCenter = { lat: (maxLat + minLat) / 2, lng: (maxLng + minLng) / 2 };

        // Create the map object
        var mapOptions = {
            center: mapCenter,
            zoom: 9 // Set the initial zoom level
        };

        var map = new google.maps.Map(document.getElementById(mapDivId), mapOptions);

        // Create an InfoWindow for markers
        const infoWindow = new google.maps.InfoWindow();

        // Create location markers
        for (const location of Object.values(locations)) {
            var label = location.labels.values().next().value;
            if (location.labels.size > 1) {
                label = (location.labels.size).toString();
            }
            const marker = new google.maps.Marker({
                map: map,
                position: location.coords,
                label: {
                    text: label,
                    fontFamily: 'Arial Narrow',
                    color: 'white',
                    fontSize: '12px'
                }
            });

            marker.addListener("click", () => {
                infoWindow.close();
                infoWindow.setContent(Object.values(location.speciesHtml).join("") + "<hr>" + location.html.join("<hr>"));
                infoWindow.open(marker.getMap(), marker);
            });
        }
    }

    // adds map div and sets up the callback to initialize the map
    function embedGoogleMap() {
        // Create a div element to hold the map
        var mapDiv = document.createElement('div');
        mapDiv.id = mapDivId; // Set the ID for the div

        // Set the size and position of the map div
        mapDiv.style.width = '800px';
        mapDiv.style.height = '400px';
        mapDiv.style.margin = 'auto';

        // add map div above the list section
        var pageSectionInner = document.getElementsByClassName("Page-section--primaryLight")[0];
        pageSectionInner.appendChild(mapDiv);

        // Load the Google Maps JavaScript API
        var script = document.createElement('script');
        script.src = 'https://maps.googleapis.com/maps/api/js?key=' + apiKey;
        script.defer = true;
        document.head.appendChild(script);

        // Call the initMap function once the API script is loaded
        script.onload = function() {
            initMap();
        };
    }

    // do it!
    embedGoogleMap();
})();