wplace - simple map browser

use openstreetmap to navigate the world faster (press M)

От 23.08.2025. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==UserScript==
// @name         wplace - simple map browser
// @namespace    http://pawing.cv
// @version      v1.1
// @description  use openstreetmap to navigate the world faster (press M)
// @author       cv
// @match        https://wplace.live/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    function openmap() {
        let popupcode = `<!DOCTYPE html>
                        <html lang="en">
                        <head>
                            <title>OpenStreetMap</title>
                            <meta charset="utf-8">
                            <style>
                                @import url("https://fonts.googleapis.com/css2?family=Geist:[email protected]&display=swap");
                                html, body, .map {
                                    height: 100%; line-height: 1;
                                    margin: 0; padding: 0;
                                    font-family: "Geist", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
                                }
                                a, span, p, li, input, ul {
                                    font-family: Geist, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !important;
                                    user-select: none;
                                }
                                .coords, .small {user-select: all !important}

                                .info {
                                    display: flex; flex-direction: row; position: fixed;
                                    justify-content: center; align-items: center;
                                    top: 1em; left: 50%; transform: translateX(-50%);
                                    background: #f2f7fe;
                                    padding: 10px; border-radius: 32px;
                                    font-size: 13.5px; font-weight: 700; z-index: 1000;
                                    box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
                                    pointer-events: none; user-select: none;
                                }
                                .info svg {
                                    padding-left: 3px; padding-right: 2px; width: 15px;
                                }
                                .coords {
                                    display: flex; flex-direction: column; position: fixed; width: 300px;
                                    bottom: 1em; left: 50%; transform: translateX(-50%);
                                    background: #f2f7fe; display: none;
                                    padding: 20px; border-radius: 32px;
                                    font-size: 18px; z-index: 1000; font-weight: 500;
                                    box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
                                }
                                .small {
                                    font-size: 13px; color: #394e6a;
                                    opacity: 0.5;
                                }

                                div.leaflet-control-container > div.leaflet-top.leaflet-left > div {
                                    border: none;
                                }
                                div.leaflet-control-container > div.leaflet-top.leaflet-left > div > a {
                                    display: flex; flex-direction: column; background: #f2f7fe;
                                    border-radius: 32px; margin-bottom: 3px; font-size: 13px;
                                    box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1); color: #394e6a;
                                }

                                div.leaflet-top.leaflet-right * {
                                    border: none;
                                }
                                div.leaflet-top.leaflet-right > div > button,
                                div.leaflet-top.leaflet-right > div {
                                    display: flex; flex-direction: column; background: #f2f7fe;
                                    border-radius: 32px; margin-bottom: 3px;
                                    color: #394e6a;
                                }
                                div.leaflet-top.leaflet-right > div > button {
                                    display: none;
                                }
                                div.leaflet-top.leaflet-right > div > div.leaflet-control-geocoder-form {
                                    display: inline-block;
                                    width: 200px; padding: 10px;
                                    box-shadow: 0 5px 16px 0 rgba(0,0,0,0.1);
                                }
                                div.leaflet-top.leaflet-right > div > .leaflet-control-geocoder-alternatives {
                                    border-radius: 1px; padding: 10px;
                                }
                                div.leaflet-top.leaflet-right > div:has(.leaflet-control-geocoder-alternatives:not([style*="display: none"])) > .leaflet-control-geocoder-form {
                                    box-shadow: none !important;
                                }
                                body > div.map.leaflet-container.leaflet-touch.leaflet-fade-anim.leaflet-grab.leaflet-touch-drag.leaflet-touch-zoom > div.leaflet-control-container > div.leaflet-bottom.leaflet-right {
                                    display: none !important;
                                }

                                .leaflet-control-geocoder-form-no-error {
                                    text-align: center; color: rgb(179, 97, 97) !important;
                                    padding-bottom: 10px;
                                }

                            </style>
                            <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css" />
                            <link rel="stylesheet" href="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.css" />

                        </head>
                        <body>
                            <div class="info">Choose a location with <svg alt="Right Click" fill="#394e6a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52"><path d="M38 0h4v6h-4V0Zm9 1 4 2c-2 4-2 4-7 5 1-5 1-5 3-7ZM36 5c8 6 9 12 9 22v6c-2 10-5 14-14 19-10 1-16 1-23-6-4-7-5-11-5-19v-6C5 4 21-3 36 5ZM9 17l-1 9h14V7c-7 0-10 5-13 10ZM8 30c2 9 3 13 11 17 11 0 14-1 20-10l1-7H8Zm38-20h6v4h-6v-4Z"/></svg>!</div>
                            <div class="coords"></div>
                            <div class="map" style="width:100%;height:100%;"></div>
                            <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js"></script>
                            <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.js"></script>
                            <script src="https://unpkg.com/leaflet-control-geocoder/dist/Control.Geocoder.Photon.js"></script>
                            <script>
                                document.addEventListener("contextmenu", function(e) {e.preventDefault()});
                                var map = L.map(document.querySelector(".map"), {
                                    worldCopyJump: false,
                                    continuousWorld: false
                                }).setView([0, 0], 1);
                                map.setMaxBounds([[-85.05112878, -180], [85.05112878, 180]]);
                                L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {noWrap: true}).addTo(map);

                                map.on('drag', function() {
                                    if (!map.getBounds().intersects(map.options.maxBounds)) {
                                        map.panInsideBounds(map.options.maxBounds, { animate: false });
                                    }
                                });
                                L.Control.geocoder({
                                  geocoder: new L.Control.Geocoder.Photon()
                                }).addTo(map);
                                var lastlatlng = null;
                                map.on("mousemove", function(e) {
                                    lastlatlng = e.latlng; var lat = e.latlng.lat; var lng = e.latlng.lng;
                                    var zoom = map.getZoom();

                                    var tile_count = 2048; var tile_size = 1000;
                                    var pixel_count = tile_count * tile_size;

                                    var x = (lng + 180) / 360; var sinlat = Math.sin(lat * Math.PI / 180);
                                    var y = 0.5 - Math.log((1 + sinlat) / (1 - sinlat)) / (4 * Math.PI);

                                    x = Math.min(1, Math.max(0, x)); y = Math.min(1, Math.max(0, y));

                                    var px = x * (pixel_count - 1); var py = y * (pixel_count - 1);
                                    var t_x = Math.floor(px / tile_size); var t_y = Math.floor(py / tile_size);
                                    var p_x = Math.floor(px % tile_size); var p_y = Math.floor(py % tile_size);

                                    document.querySelector(".coords").innerHTML =
                                        lat.toFixed(6) + ", " + lng.toFixed(6) + " [x" + zoom + "] " +
                                        "<br><span class='small'>" + t_x + ", " + t_y + " // " + p_x + ", " + p_y + "</span>";

                                    var coords = document.querySelector(".coords");
                                    if (coords.innerHTML.trim() !== "") {coords.style.display = "flex"}
                                    else {coords.style.display = "none"}
                                });
                                document.querySelector(".map").addEventListener("contextmenu", function(ev) {
                                    ev.preventDefault();
                                    if (lastlatlng) {
                                        var osmzoom = map.getZoom(); var wplacezoom = Math.max(1, Math.min(19, osmzoom));
                                        window.opener.postMessage({ lat: lastlatlng.lat, lng: lastlatlng.lng, zoom: wplacezoom, source: "wplace-map-popup" }, "*");
                                        window.close();
                                    }
                                });
                            </script>
                        </body>
                        </html>`;
        const left = (window.screen.width - 800) / 2; const top = (window.screen.height - 600) / 2;
        const popup = window.open(
          '',
          'wplace_map_popup',
          `width=800,height=600,left=${left},top=${top},resizable=yes,scrollbars=no,status=no,location=no,toolbar=no,menubar=no`
        );
        if (popup) {
            const writepopup = () => {
                if (popup.document && popup.document.readyState === "complete") {
                    popup.document.open(); popup.document.write(popupcode); popup.document.close();
                } else {setTimeout(writepopup, 10)}
            }; writepopup();
        } else {alert("Popup failed to open! (do you have popups disabled?)")}
    }

    function cleanlink() {
        const cleanlink = new URL(window.location.href);
        cleanlink.searchParams.delete('lat');
        cleanlink.searchParams.delete('lng');
        cleanlink.searchParams.delete('zoom');
        window.history.replaceState({}, '', cleanlink.pathname + cleanlink.search + cleanlink.hash);
    }

    window.addEventListener('message', function(event) {
        if (event.data && event.data.source === 'wplace-map-popup' &&
            typeof event.data.lat === 'number' && typeof event.data.lng === 'number') {
            const currentlink = new URL(window.location.href);
            currentlink.searchParams.set('lat', event.data.lat);
            currentlink.searchParams.set('lng', event.data.lng);
            if (typeof event.data.zoom === 'number') {
                currentlink.searchParams.set('zoom', event.data.zoom);
            }
            window.location.replace(currentlink.toString());
            window.addEventListener('pageshow', cleanlink);
        }
    });

    window.addEventListener('DOMContentLoaded', function() {
        const url = new URL(window.location.href);
        if (url.searchParams.has('lat') || url.searchParams.has('lng') || url.searchParams.has('zoom')) {
            cleanlink();
        }
    });
    document.addEventListener('keydown', function(e) {
        if (e.key === 'M' || e.key === 'm') {openmap()}
    });
})();