NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Geocaching Map Enhancements // @version 0.9.0 // @author JRI // @oujs:author JRI // @namespace inge.org.uk/userscripts // @description Adds extra maps and grid reference search to Geocaching.com, along with several other enhancements. // @include https://www.geocaching.com/* // @license MIT; http://www.opensource.org/licenses/mit-license.php // @copyright 2011-18, James Inge (http://geo.inge.org.uk/) // @attribution GeoNames (http://www.geonames.org/) // @attribution Postcodes.io (https://postcodes.io/) // @attribution Chris Veness (http://www.movable-type.co.uk/scripts/latlong-gridref.html) // @grant GM_xmlhttpRequest // @grant GM.xmlHttpRequest // @connect geograph.org.uk // @connect channel-islands.geographs.org // @connect geo-en.hlipp.de // @connect api.geonames.org // @connect api.postcodes.io // @connect www.geocaching.com // @icon http://gcgpx.cz/gme/GeocachingMap48.png // @icon64 http://gcgpx.cz/gme/GeocachingMap64.png // @updateURL http://gcgpx.cz/gme/GeocachingMapEnhancements.meta.js // @downloadURL https://openuserjs.org/install/Vylda/Geocaching_Map_Enhancements.user.js // ==/UserScript== /* jshint multistr: true */ /* global $, amplify, DMM, FileReader, GM, GM_xmlhttpRequest, Groundspeak, L, LatLon, mapLatLng, MapSettings */ (function () { "use strict"; var gmeResources = { parameters: { // Defaults version: "0.9.0", versionMsg: "Icons fix, API key, Default hill shader", brightness: 1, // Default brightness for maps (0-1), can be overridden by custom map parameters. filterFinds: false, // True filters finds out of list searches. follow: false, // Locator widget follows current location (moving map mode) labels: "codes", // Label caches on the map with their GC code. Or "names" to use long name. measure: "metric", // Or "imperial" - used for the scale indicators osgbSearch: true, // Enhance search box with OSGB grid references, zooming, etc. (may interfere with postal code searches) defaultMap: "OpenStreetMap", defaultHillShading: "Hillshading", maps: [ // {alt:"Readable Name", tileUrl: "URL template including {s} (subdomain) and either {q} (quadkey) or {x},{y},{z} (Google/TMS tile coordinates + zoom)", subdomains: "0123", minZoom: 0, maxZoom: 24, attribution: "Copyright message (HTML allowed)", name: "shortname", overlay:false } { alt: "OpenStreetMap", tileUrl: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", name: "osm", subdomains: "abc" }, { alt: "OpenCycleMap", tileUrl: "https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png", name: "ocm", apiKey: 'OpenCycleMap', apiKeyQuery: "apikey={apikey}" }, { alt: "Bing Maps", tileUrl: "https://ecn.t{s}.tiles.virtualearth.net/tiles/r{q}?g=864&mkt=en-gb&lbl=l1&stl=h&shading=hill&n=z", subdomains: "0123", minZoom: 1, maxZoom: 20, attribution: "<a href=\'https://www.bing.com/maps/\'>Bing</a> map data copyright Microsoft and its suppliers", name: "bingmap", ignore: true }, { alt: "Bing Aerial View", tileUrl: "https://ecn.t{s}.tiles.virtualearth.net/tiles/a{q}?g=737&n=z", subdomains: "0123", minZoom: 1, maxZoom: 20, attribution: "<a href=\'https://www.bing.com/maps/\'>Bing</a> map data copyright Microsoft and its suppliers", name: "bingaerial" }, { alt: "Google Maps", tileUrl: "https://mt.google.com/vt?&x={x}&y={y}&z={z}", name: "googlemaps", attribution: "<a href=\'https://maps.google.com/\'>Google</a> Maps", subdomains: "1234", tileSize: 256, maxZoom: 22 }, { alt: "Google Satellite", tileUrl: "https://mt.google.com/vt?lyrs=s&x={x}&y={y}&z={z}", name: "googlemapssat", attribution: "<a href=\'https://maps.google.com/\'>Google</a> Maps Satellite", subdomains: "1234", tileSize: 256, maxZoom: 22 }, { alt: "Freemap Slovakia Hiking", tileUrl: "http://t{s}.freemap.sk/T/{z}/{x}/{y}.jpeg", attribution: "Map © <a href='http://www.freemap.sk/'>Freemap Slovakia</a>, data © <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors", subdomains: "1234", minZoom: 8, maxZoom: 16, ignore: true }, { alt: "Freemap Slovakia Bicycle", tileUrl: "http://t{s}.freemap.sk/C/{z}/{x}/{y}.jpeg", attribution: "Map © <a href='http://www.freemap.sk/'>Freemap Slovakia</a>, data © <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors", subdomains: "1234", minZoom: 8, maxZoom: 16, ignore: true }, { alt: "Freemap Slovakia Car", tileUrl: "http://t{s}.freemap.sk/A/{z}/{x}/{y}.jpeg", attribution: "Map © <a href='http://www.freemap.sk/'>Freemap Slovakia</a>, data © <a href='http://openstreetmap.org'>OpenStreetMap</a> contributors", subdomains: "1234", minZoom: 8, maxZoom: 16, ignore: true }, { alt: "Mapy.cz - Turistická", tileUrl: "https://m{s}.mapserver.mapy.cz/turist-m/{z}-{x}-{y}", minZoom: 5, maxZoom: 18, subdomains: "1234", attribution: "© <a href='//www.seznam.cz' target='_blank'>Seznam.cz, a.s.</a>, © <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>, © NASA" }, { alt: "Hillshading", tileUrl: "http://{s}.tiles.wmflabs.org/hillshading/{z}/{x}/{y}.png", subdomains: "abc", attribution: "Hillshading by <a href=\'https://wiki.openstreetmap.org/wiki/Hike_%26_Bike_Map\'>Colin Marquardt</a> from NASA SRTM data", overlay: true } ], apiKeys: {}, }, css: { main: '.leaflet-control-gme,.leaflet-control-zoomwarning {border-radius:7px; filter: progid:DXImageTransform.Microsoft.gradient(startColorStr="#3F000000",EndColorStr="#3F000000"); padding:5px;z-index:8;}\ .leaflet-control-gme {display: inline-block; padding: 0; background: rgba(0, 0, 0, 0.2); box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);}\ .gme-control-scale {bottom:5em !important;margin-left:13px !important; left: 385px;}\ .gme-left {left: 385px; margin-left:13px !important;}\ div.gme-identify-layer {margin-top:-1em;margin-left:1em;padding-left:0.1em;font-weight:bold;background:rgba(255,255,255,0.57);}\ #gme_caches table { margin-top: 0.5em; }\ .GME_search_list { border: 1px solid #679300; border-radius: 7px; padding: 0.5em; }\ div.GME_search_results { margin-right: -65px; }\ .GME_search_results.hidden { display: none; }\ .groundspeak-control-findmylocation { border: 1px solid #888; border-radius: 5px; box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); padding:0; background:rgba(255,255,255,0.8);}\ .groundspeak-control-findmylocation a { padding: 3px; }\ .gme-button { display: inline-block; box-sizing: content-box; -moz-box-sizing: content-box; padding:3px; vertical-align:middle; background:url(https://www.geocaching.com/map/css/themes/images/icons-18-black.png) no-repeat #eee; background-color: rgba(255,255,255,0.8); border: 1px solid #888; border-right:0; height:19px; width:19px; text-decoration: none; }\ .gme-button-l { border-bottom-left-radius:5px; border-top-left-radius:5px; }\ .gme-button-r { border-right: 1px solid #888; border-bottom-right-radius: 5px; border-top-right-radius:5px; margin-right:0.5em;}\ .gme-button:hover { background-color: #fff; }\ .gme-button-active {border:solid 3px #02b; padding:1px 0 1px 1px; background-color:#fff;}\ .gme-button-active:hover {border-color:#63f;filter:alpha(opacity=100);}\ span.gme-button, .gme-button-wide { padding-left:5px; padding-right:5px; font-size:12px; font-weight:bold; width:auto; background-image:none; color: #424242; }\ .GME_home { background-size: 18px 18px; background-position: center;background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="50" height="50" viewBox="0 0 30 30" style="fill:%23000000;"%3E%3Cpath d="M 15 2 A 1 1 0 0 0 14.300781 2.2851562 L 3.3925781 11.207031 A 1 1 0 0 0 3.3554688 11.236328 L 3.3183594 11.267578 L 3.3183594 11.269531 A 1 1 0 0 0 3 12 A 1 1 0 0 0 4 13 L 5 13 L 5 24 C 5 25.105 5.895 26 7 26 L 23 26 C 24.105 26 25 25.105 25 24 L 25 13 L 26 13 A 1 1 0 0 0 27 12 A 1 1 0 0 0 26.681641 11.267578 L 26.666016 11.255859 A 1 1 0 0 0 26.597656 11.199219 L 25 9.8925781 L 25 6 C 25 5.448 24.552 5 24 5 L 23 5 C 22.448 5 22 5.448 22 6 L 22 7.4394531 L 15.677734 2.2675781 A 1 1 0 0 0 15 2 z M 18 15 L 22 15 L 22 23 L 18 23 L 18 15 z"%3E%3C/path%3E%3C/svg%3E\')}\ .GME_config { background-size: 18px 18px; background-position: center;background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="50" height="50" viewBox="0 0 32 32" style="fill:%23000000;"%3E%3Cg id="surface1"%3E%3Cpath style=" " d="M 21.5 2.5 L 21.5 3.90625 C 20.664063 4.054688 19.886719 4.371094 19.21875 4.84375 L 18.1875 3.875 L 16.8125 5.34375 L 17.8125 6.28125 C 17.363281 6.9375 17.050781 7.6875 16.90625 8.5 L 15.5 8.5 L 15.5 10.5 L 16.90625 10.5 C 17.050781 11.328125 17.378906 12.085938 17.84375 12.75 L 16.78125 13.78125 L 18.21875 15.21875 L 19.25 14.15625 C 19.914063 14.621094 20.671875 14.949219 21.5 15.09375 L 21.5 16.5 L 23.5 16.5 L 23.5 15.09375 C 24.3125 14.949219 25.0625 14.636719 25.71875 14.1875 L 26.65625 15.1875 L 28.125 13.8125 L 27.15625 12.78125 C 27.628906 12.113281 27.945313 11.335938 28.09375 10.5 L 29.5 10.5 L 29.5 8.5 L 28.09375 8.5 C 27.949219 7.671875 27.621094 6.914063 27.15625 6.25 L 28.09375 5.3125 L 26.6875 3.90625 L 25.75 4.84375 C 25.085938 4.378906 24.328125 4.050781 23.5 3.90625 L 23.5 2.5 Z M 22.5 5.8125 C 24.554688 5.8125 26.1875 7.445313 26.1875 9.5 C 26.1875 11.554688 24.554688 13.1875 22.5 13.1875 C 20.445313 13.1875 18.8125 11.554688 18.8125 9.5 C 18.8125 7.445313 20.445313 5.8125 22.5 5.8125 Z M 9.53125 11.71875 L 7.6875 12.46875 L 8.40625 14.28125 C 7.453125 14.851563 6.640625 15.648438 6.0625 16.59375 L 4.28125 15.875 L 3.53125 17.71875 L 5.3125 18.4375 C 5.179688 18.964844 5.09375 19.523438 5.09375 20.09375 C 5.09375 20.664063 5.179688 21.21875 5.3125 21.75 L 3.53125 22.46875 L 4.28125 24.3125 L 6.0625 23.59375 C 6.640625 24.554688 7.445313 25.359375 8.40625 25.9375 L 7.6875 27.71875 L 9.53125 28.46875 L 10.25 26.6875 C 10.78125 26.820313 11.332031 26.90625 11.90625 26.90625 C 12.476563 26.90625 13.035156 26.820313 13.5625 26.6875 L 14.28125 28.46875 L 16.125 27.71875 L 15.40625 25.9375 C 16.351563 25.359375 17.148438 24.546875 17.71875 23.59375 L 19.53125 24.3125 L 20.28125 22.46875 L 18.46875 21.75 C 18.601563 21.21875 18.6875 20.664063 18.6875 20.09375 C 18.6875 19.523438 18.601563 18.964844 18.46875 18.4375 L 20.28125 17.71875 L 19.53125 15.875 L 17.71875 16.59375 C 17.148438 15.652344 16.351563 14.851563 15.40625 14.28125 L 16.125 12.46875 L 14.28125 11.71875 L 13.5625 13.53125 C 13.03125 13.398438 12.476563 13.3125 11.90625 13.3125 C 11.335938 13.3125 10.78125 13.398438 10.25 13.53125 Z M 11.90625 15.3125 C 14.570313 15.3125 16.6875 17.429688 16.6875 20.09375 C 16.6875 22.757813 14.570313 24.90625 11.90625 24.90625 C 9.242188 24.90625 7.09375 22.757813 7.09375 20.09375 C 7.09375 17.429688 9.242188 15.3125 11.90625 15.3125 Z "%3E%3C/path%3E%3C/g%3E%3C/svg%3E\')}\ .GME_route { background-size: 18px 18px; background-position: center;background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"%3E%3Cpath d="M12 0c-4.198 0-8 3.403-8 7.602 0 4.198 3.469 9.21 8 16.398 4.531-7.188 8-12.2 8-16.398 0-4.199-3.801-7.602-8-7.602zm0 11c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z"/%3E%3C/svg%3E\');background-repeat: no-repeat; background-color: #eee;}\ .GME_hide { background-size: 18px 18px; background-position: center;background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"%3E%3Cpath d="M19.604 2.562l-3.346 3.137c-1.27-.428-2.686-.699-4.243-.699-7.569 0-12.015 6.551-12.015 6.551s1.928 2.951 5.146 5.138l-2.911 2.909 1.414 1.414 17.37-17.035-1.415-1.415zm-6.016 5.779c-3.288-1.453-6.681 1.908-5.265 5.206l-1.726 1.707c-1.814-1.16-3.225-2.65-4.06-3.66 1.493-1.648 4.817-4.594 9.478-4.594.927 0 1.796.119 2.61.315l-1.037 1.026zm-2.883 7.431l5.09-4.993c1.017 3.111-2.003 6.067-5.09 4.993zm13.295-4.221s-4.252 7.449-11.985 7.449c-1.379 0-2.662-.291-3.851-.737l1.614-1.583c.715.193 1.458.32 2.237.32 4.791 0 8.104-3.527 9.504-5.364-.729-.822-1.956-1.99-3.587-2.952l1.489-1.46c2.982 1.9 4.579 4.327 4.579 4.327z"/%3E%3C/svg%3E\');background-repeat: no-repeat; background-color: #eee;}\ .GME_route.gme-button-active {background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"%3E%3Cpath d="M11.6,11C10.1,10.8,9,9.5,9,8c0-1.7,1.3-3,3-3c1.6,0,2.9,1.2,3,2.8L19,4c-1.4-2.4-4.1-4-7-4C7.8,0,4,3.4,4,7.6c0,2.3,1.1,4.9,2.8,7.9L11.6,11z"/%3E%3Cpath d="M7.8,17.3c1.2,2,2.6,4.2,4.2,6.7c4.5-7.2,8-12.2,8-16.4c0-0.5-0.1-1-0.2-1.5L7.8,17.3z"/%3E%3Cline stroke-linecap="round" stroke="black" x1="2.1" y1="21.3" x2="21.9" y2="2.7"/%3E%3C/svg%3E\'); }\ .GME_hide.gme-button-active { background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"%3E%3Cpath d="M12.015 7c4.751 0 8.063 3.012 9.504 4.636-1.401 1.837-4.713 5.364-9.504 5.364-4.42 0-7.93-3.536-9.478-5.407 1.493-1.647 4.817-4.593 9.478-4.593zm0-2c-7.569 0-12.015 6.551-12.015 6.551s4.835 7.449 12.015 7.449c7.733 0 11.985-7.449 11.985-7.449s-4.291-6.551-11.985-6.551zm-.015 3c-2.209 0-4 1.792-4 4 0 2.209 1.791 4 4 4s4-1.791 4-4c0-2.208-1.791-4-4-4z"/%3E%3C/svg%3E\'); }\ .gme-button-refresh-labels { background-position: -320px 4px;}\ .gme-button-clear-labels { background-position: -69px 4px;}\ span.gme-distance-container { display: none; }\ span.gme-distance-container.show { display: inline-block; }\ .GME_info { background-size: 16px 16px; background-position: center;background-image: url(\'data:image/svg+xml,%3C%3Fxml version="1.0" encoding="iso-8859-1"%3F%3E%3Csvg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 32 32" %3E%3Cpath style="fill:%23030104;" d="M10,16c1.105,0,2,0.895,2,2v8c0,1.105-0.895,2-2,2H8v4h16v-4h-1.992c-1.102,0-2-0.895-2-2L20,12H8 v4H10z"/%3E%3Ccircle style="fill:%23030104;" cx="16" cy="4" r="4"/%3E%3C/svg%3E\')}\ .GME_info.gme-button-active {}\ #GME_loc, a.gme-button.leaflet-active {outline: none;}\ .leaflet-control-zoomwarning { top: 94px; }\ .leaflet-control-zoomwarning a { filter: progid:DXImageTransform.Microsoft.gradient(startColorStr="#BFC80000",EndColorStr="#BFC80000"); background-color:rgba(200,0,0,0.75); margin-left: -4px; background-position: -502px 2px;height:14px;width:14px; border-color: #b00; box-shadow: 0 0 8px rgba(0, 0, 0, 0.4); }\ .leaflet-control-zoomwarning a:hover { background-color:rgba(230,0,0,0.75); }\ .gme-event { cursor: pointer; }\ .gme-modalDialog {position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: rgba(0,0,0,0.5); z-index: 1000; opacity:.5; -webkit-transition: opacity 400ms ease-in; -moz-transition: opacity 400ms ease-in; transition: opacity 400ms ease-in; pointer-events: none; display:none; }\ .gme-modalDialog:target, .gme-modalDialog.gme-targetted { opacity:1; display:block; pointer-events: auto; }\ .gme-modalDialog > div { position: relative; margin: 4% 12.5%; height: 30em; max-height: 75%; padding: 0 0 13px 0; border: 1px solid #000; border-radius: 10px; background: #fff; background: -moz-linear-gradient(#fff, #999); background: -webkit-linear-gradient(#fff, #999); background: -o-linear-gradient(#fff, #999); }\ .gme-modalDialog header { color: #eee; background: none #454545; font-size: 15px; text-align: center; border-top-left-radius: 10px; padding: 0.5em 0; font-weight: bold; text-shadow: none; height: auto; min-height: auto; min-width: auto; }\ .gme-modal-content { position: absolute; top: 3.5em; left: 0.75em; right: 0.75em; bottom: 0.5em; overflow: auto; }\ .gme-modal-content > .leaflet-control-gme { position: absolute; left: 0.5em; bottom: 0.5em; top: auto; }\ .gme-close-dialog { background: #606061; color: #fff; line-height: 25px; position: absolute; right: -12px; text-align: center; top: -10px; width: 24px; text-decoration: none; font-weight: bold; -webkit-border-radius: 12px; -moz-border-radius: 12px; border-radius: 12px; -moz-box-shadow: 1px 1px 3px #000; -webkit-box-shadow: 1px 1px 3px #000; box-shadow: 1px 1px 3px #000; }\ .gme-close-dialog:hover { background: #00d9ff; }\ #searchtabs li a { padding: 1em 0.5em; }\ @media print { #search { display: none !important}}\ .tab-switcher { position: relative; font-family: Arial, sans-serif; font-size: 14px; }\ .gme-tab { float: left; }\ .gme-tab-label { border-radius: 8px 8px 0 0; border: 1px solid #ccc; color: #454545; background: #ddd; display: block; position: relative; margin-left: 15px; padding: 3px 0; font-weight: bold; z-index: 0; }\ .gme-tab-label:after { border-bottom: 1px solid #ccc; border-bottom-left-radius: 8px; border-left: 1px solid #ccc; box-shadow: -2px 2px 0 #ddd; bottom: -8px; content: ""; display: inline-block; height: 8px; left: 9px; position: relative; width: 8px; z-index: 3;}\ .gme-tab-label:before { border-bottom: 1px solid #ccc; border-bottom-right-radius: 8px; border-right: 1px solid #ccc; box-shadow: 2px 2px 0 #ddd; bottom: -8px; content: ""; display: inline-block; height: 8px; left: -9px; position: relative; width: 8px; z-index: 3; }\ .gme-tab-label:hover { cursor: pointer; }\ .gme-tab-content { position: absolute; top: 25px; bottom: 3.5em; left: 0; right: 0; padding: 0.5em; background: #000; border: 1px solid #ccc; border-radius: 8px; color: #555; z-index: 1; opacity: 0; overflow: auto; }\ .gme-tab-content ul { margin: 0.5em 0; }\ .gme-tab input[type=radio] { display: none; }\ .gme-tab input[type=radio]:checked ~ .gme-tab-content { z-index: 2; opacity: 1; background: #fff; color: #454545; }\ .gme-tab input[type=radio]:checked ~ .gme-tab-label { background: #fff; color: #454545; border-bottom: 1px solid #fff; z-index: 3; }\ .gme-tab input[type=radio]:checked ~ .gme-tab-label:after { box-shadow: -2px 2px 0 #fff; }\ .gme-tab input[type=radio]:checked ~ .gme-tab-label:before { box-shadow: 2px 2px 0 #fff; }\ .gme-fieldgroup { position: relative; border: 1px solid #ccc; border-radius: 6px; background: #eee; margin: 0.5em 0 1.5em; padding: 0.5em; }\ .gme-fieldgroup h3 { position:absolute; top: -0.7em; left: 0.5em; padding: 0 0.5em; background: #eee; border-top: 1px solid #ccc; border-radius: 6px; z-index:1; display:inline-block; font-weight: bold; font-size: 12px; }\ .gme-fieldgroup ul { margin: 0.5em 0; padding: 0; }\ .gme-fieldgroup li { display: inline-block; margin: 0 -1px -1px 0; background: #ddd; border: 1px solid #ccc; border-radius: 6px; padding: 0 0.5em; }\ .gme-xhair { cursor: crosshair; }\ .map-button-container { margin-right: 5em; }\ .leaflet-control-layers-toggle { background-size: 75%; background-image: url(\'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 26 26"%3E%3Cdefs%3E%3Cstyle%3E.diamond%7Bopacity:0.8;fill:url(%23radialGradient);%7D.diamnondPath%7Bfill:url(%23linearGradient);%7D%3C/style%3E%3CradialGradient id="radialGradient" cx="170.89" cy="960.99" r="10.16" gradientTransform="translate(-161.12 -1073.84) scale(1.02 1.13)" gradientUnits="userSpaceOnUse"%3E%3Cstop offset="0.16" stop-color="gray"/%3E%3Cstop offset="1" stop-color="%23fff"/%3E%3C/radialGradient%3E%3ClinearGradient id="linearGradient" x1="13" x2="13" y2="15" gradientUnits="userSpaceOnUse"%3E%3Cstop offset="0" stop-color="%23b3b3b3"/%3E%3Cstop offset="1" stop-color="%234d4d4d"/%3E%3C/linearGradient%3E%3Csymbol id="layer" data-name="Nový symbol…" viewBox="0 0 26 15"%3E%3Cpolygon class="diamond" points="13.52 0 0 6.92 13.52 15 26 6.92 13.52 0"/%3E%3Cpath class="diamnondPath" d="M13.5,1.13,24.06,7,13.5,13.82,2.06,7,13.5,1.13m0-1.13L0,6.92,13.52,15,26,6.92,13.52,0Z"/%3E%3C/symbol%3E%3C/defs%3E%3Cuse width="26" height="15" transform="translate(0 11)" xlink:href="%23layer"/%3E%3Cuse width="26" height="15" transform="translate(0 5.57) scale(1 0.98)" xlink:href="%23layer"/%3E%3Cuse width="26" height="15" transform="translate(0) scale(1 0.98)" xlink:href="%23layer"/%3E%3C/svg%3E\'); }\ #GME_apikeys_field label { display: block; }\ #GME_apikeys_field label input { width: 75%; }', drag: '#cacheDetails .cacheImage { border: solid 1px #ccc; border-radius: 7px; padding-left: 5px; }\ .moveable { cursor: move; box-shadow: 0 1px 4px rgba(102, 51, 255, 0.3); }' }, env: { dragdrop: (document.createElement('span').draggable !== undefined), geolocation: !!navigator.geolocation, init: [], loggedin: (!!document.getElementById("ctl00_uxLoginStatus_divSignedIn") || !!document.getElementById("uxLoginStatus_divSignedIn")), page: "default", storage: false, xhr: (typeof GM_xmlhttpRequest === 'function') ? 'GM' : ((typeof GM === 'object' && typeof GM.xmlHttpRequest === 'function') ? 'GM4' : '') }, html: { config: '<section class="gme-tab">\ <input type="radio" name="gme-tab-row" id="gme-tab-maps" checked />\ <label class="gme-tab-label" for="gme-tab-maps">Map display</label>\ <div class="gme-tab-content">\ <div class="gme-fieldgroup">\ <h3>Maps to show in selector widget</h3>\ <ul id="GME_mapfields"></ul>\ <label>Default map source: <select name="GME_map_default" id="GME_map_default"></select></label>\ <label>Default hill shading source: <select name="GME_hill_shade_default" id="GME_hill_shade_default"></select></label>\ </div>\ <div class="gme-fieldgroup">\ <h3>Map API key</h3>\ <div id="GME_apikeys_field"></dov>\ </div>\ </div>\ </section>\ <section class="gme-tab">\ <input type="radio" name="gme-tab-row" id="gme-tab-manage" />\ <label class="gme-tab-label" for="gme-tab-manage">Manage maps</label>\ <div class="gme-tab-content">\ <div class="gme-fieldgroup">\ <h3>Add map sources</h3>\ <label>Mapsource: <input type="text" name="GME_map_custom" id="GME_map_custom"> </label>\ <div class="leaflet-control-gme"><button type="button" id="GME_custom_add" class="gme-button gme-button-wide gme-button-l gme-button-r" title="Add custom map source">Add</button> <a href="#GME_format" title="Map source format info" class="gme-button gme-button-wide gme-button-l">Mapsource format info</a><button type="button" id="GME_custom_export" title="Export custom map source JSON" class="gme-button gme-button-wide gme-button-r">Export custom maps</button></div>\ </div>\ <div class="gme-fieldgroup">\ <h3>Remove map sources</h3>\ <ul id="GME_mapfields_del"></ul>\ </div>\ </div>\ </section>\ <section class="gme-tab">\ <input type="radio" name="gme-tab-row" id="gme-tab-other" />\ <label class="gme-tab-label" for="gme-tab-other">Other</label>\ <div class="gme-tab-content">\ <div class="gme-fieldgroup">\ <h3>Miscellaneous settings</h3>\ <ul>\ <li><label title="Only list unfound caches in search"><input type="checkbox" name="GME_filterFinds" id="GME_filterFinds" /> Filter finds</label></li>\ <li><label><input type="checkbox" checked="checked" name="GME_osgbSearch" id="GME_osgbSearch" /> Enhance search</label></li>\ <li><label title="Location widget constantly updates position"><input type="checkbox" name="GME_follow" id="GME_follow" /> FollowMe Mode</label></li>\ </ul>\ <label>Labels:\ <select name="GME_labelStyle" id="GME_labelStyle">\ <option value="names">Names</option>\ <option value="codes" selected="selected">Codes</option>\ </select>\ </label>\ <label>Scale:\ <select name="GME_measure" id="GME_measure">\ <option value="metric" selected="selected">Metric</option>\ <option value="imperial">Imperial</option>\ </select>\ </label>\ <label>Map brightness:\ <input type="range" name="GME_brightness" id="GME_brightness" value="100" min="0" max="100" />\ </label>\ </div>\ </div>\ </section>\ <section class="gme-tab">\ <input type="radio" name="gme-tab-row" id="gme-tab-about" />\ <label class="gme-tab-label" for="gme-tab-about">About</label>\ <div class="gme-tab-content">\ <div class="gme-fieldgroup">\ <h3>Geocaching Map Enhancements</h3><br />\ <p>v<span id="GME_version"></span> © 2011-2018 James Inge. Geocaching Map Enhancements is licensed for reuse under the <a target="_blank" rel="noopener noreferrer" href="http://www.opensource.org/licenses/mit-license.php">MIT License</a>. For documentation, see <a target="_blank" rel="noopener noreferrer" href="http://geo.inge.org.uk/gme.htm">http://geo.inge.org.uk/gme.htm</a></p>\ <p>Elevation and reverse geocoding data provided by <a target="_blank" rel="noopener noreferrer" href="http://www.geonames.org/">GeoNames</a> and used under a <a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0</a> (CC-BY) License.</p>\ <p>Grid reference manipulation is adapted from code © 2005-2014 Chris Veness (<a target="_blank" rel="noopener noreferrer" href="http://www.movable-type.co.uk/scripts/latlong-gridref.html">www.movable-type.co.uk/scripts/latlong-gridref.html</a>, used under a <a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0</a> (CC-BY) License.</p>\ <p>Photos provided by Geograph are copyright their respective owners - hover mouse over thumbnails or click through for attribution details. They may be re-used under a <a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-sa/2.0/">Creative Commons Attribution-ShareAlike 2.0</a> (CC-BY-SA) License.</p>\ </div>\ </div>\ </section>\ <div class="leaflet-control-gme">\ <a href="#" class="gme-button gme-button-wide gme-button-l" rel="back" title="Cancel">Cancel</a><button type="button" class="gme-button gme-button-wide" id="GME_default" title="Reset to defaults">Defaults</button><button type="button" class="gme-button gme-button-wide gme-button-r" id="GME_set" title="Confirm settings">Save</button>\ </div>', customInfo: '<p>Custom mapsources can be added by supplying entering a <a rel="external" href="http://www.json.org/">JSON</a> configuration string that tells GME what to call the map, where to find it, and how it is set up. e.g.</p>\ <p><code>{"alt":"OS NPE (GB only)","tileUrl":"https://ooc.openstreetmap.org/npe/{z}/{x}/{y}.png", "minZoom":6, "maxZoom": 15, "attribution": "OpenStreetMap NPE" }</code></p>\ <p>The <code>"alt"</code> and <code>"tileUrl"</code> parameters are mandatory. <code>"tileUrl"</code> can contain {x}, {y} and {z} for Google-style coordinate systems (also works with TMS systems like Eniro, but needs the <code>"scheme":"tms"</code> parameter), or {q} for Bing-style quadkeys. GME can also connect with WMS servers, in which case a <code>"layers"</code> parameter is required.</p>\ <p>The other parameters are the same as those used by the <a rel="external" href="http://leafletjs.com/reference-versions.html">Leaflet API</a>, with the addition of a <code>"overlay":true</code> option, that makes the mapsource appear as a selectable overlay.</p>\ <ul><li><a rel="external" href="http://geo.inge.org.uk/gme_config.htm">Detailed documentation</a></li><li><a rel="external" href="http://geo.inge.org.uk/gme_maps.htm">More mapsource examples</a></li></ul>', search: '<input type="text" placeholder="Address, coordinates, GC-code, keyword, etc." id="SearchBox_Text" title="Jump to a specific zoom level by typing zoom then a number. Zoom 1 shows the whole world, maxiumum zoom is normally 18-22. To search using a British National Grid reference, just type it in the search box and hit the button! You can use 2, 4, 6, 8 or 10-digit grid refs with the 2-letter prefix but no spaces in the number (e.g. SU12344225) or absolute grid refs with a comma but no prefix (e.g. 439668,1175316)." />\ <button id="SearchBox_OS" title="Search">Search</button>\ <div class="GME_search_results hidden">\ <h3 class="GME_search_heading">GeoNames search results</h3>\ <ul class="GME_search_list"></ul>\ <p>Or try the <a href="#" class="GME_link_GSSearch">Geocaching.com search</a>\ </div>' }, script: { common: function () { var that = this, callbackCount = 0, load_count = 0, JSONP; function setEnv() { // The script waits for the Leaflet API to load, and will abort if it does not find it after a minute. var maxTries = 60, wait = 1000; switch (gmeConfig.env.page) { case "seek": if (typeof $ === "function") { gmeInit(gmeConfig.env.init); load(); return; } break; case "hide": maxTries = Infinity; wait = 3000; if (window.map !== null && window.map !== undefined && typeof L === "object" && typeof $ === "function") { gmeInit(gmeConfig.env.init); window.setTimeout(load, 500); return; } break; case "type": if (window.map !== null && window.map !== undefined && typeof L === "object" && typeof $ === "function") { gmeInit(gmeConfig.env.init); load(); reload(); $(".cache-type-selector button").click(reload); return; } break; case "maps": // Wait for the map to load and the default map selector to be added. if (typeof L === "object" && typeof $ === "function" && window.MapSettings && window.MapSettings.Map && window.MapSettings.Map._loaded && $(".leaflet-control-layers").length > 0) { gmeInit(gmeConfig.env.init); window.setTimeout(load, 500); return; } break; default: if (typeof L === "object" && typeof $ === "function") { gmeInit(gmeConfig.env.init); window.setTimeout(load, 500); return; } break; } if (load_count < maxTries) { window.setTimeout(setEnv, wait); load_count++; console.log("GME: Waiting for map API to load: " + load_count + "..."); } } function gmeInit(scriptArray) { // Init routines that need either JQuery or Leaflet API, so must be run from load() rather than on script insertion. var initScripts = { "config": function () { if (gmeConfig.env.storage) { setConfig(); $("#GME_set").bind("click", storeSettings); $("#GME_default").bind("click", setDefault); $("#GME_custom_add").bind("click", addCustom); $("#GME_custom_export").bind("click", exportCustom); $("li.li-user ul").append("<li class='li-settings'><a class='icon-settings' id='gme-config-link' href='#GME_config' title='Configure Geocaching Map Enhancements extension'>Geocaching Map Enhancements</a></li>"); } }, "drop": function () { $.fn.filterNode = function (name) { return this.find("*").filter(function () { return this.nodeName === name; }); }; L.GME_dropHandler = L.Control.extend(dropHandlerObj); }, "map": function () { bounds_GB = new L.LatLngBounds(new L.LatLng(49, -9.5), new L.LatLng(62, 2.3)); bounds_IE = new L.LatLngBounds(new L.LatLng(51.2, -12.2), new L.LatLng(55.73, -5.366)); bounds_NI = new L.LatLngBounds(new L.LatLng(54, -8.25), new L.LatLng(55.73, -5.25)); bounds_CI = new L.LatLngBounds(new L.LatLng(49.1, -2.8), new L.LatLng(49.8, -1.8)); bounds_DE = new L.LatLngBounds(new L.LatLng(47.24941, 5.95459), new L.LatLng(55.14121, 14.89746)); L.GME_DistLine = L.Polyline.extend(polylineObj); L.GME_QuadkeyLayer = L.TileLayer.extend(quadkeyLayerObj); L.GME_complexLayer = L.TileLayer.extend(complexLayerObj); L.GME_genericLayer = genericLayerFn; }, "widget": function () { L.GME_Widget = L.Control.extend(widgetControlObj); if (window.Groundspeak && Groundspeak.Map && Groundspeak.Map.Control && Groundspeak.Map.Control.FindMyLocation) { L.GME_FollowMyLocationControl = Groundspeak.Map.Control.FindMyLocation.extend(locationControlObj); } L.GME_ZoomWarning = L.Control.extend(zoomWarningObj); if (L.LatLng.prototype.toUrl === undefined) { L.LatLng.prototype.toUrl = function () { return this.lat.toFixed(6) + "," + this.lng.toFixed(6); }; } if ($.fancybox === undefined) { console.info("GME: Fetching Fancybox"); $("head").append("<link rel='stylesheet' type='text/css' href='https://cdnjs.cloudflare.com/ajax/libs/fancybox/2.1.5/jquery.fancybox.min.css'><script type='text/javascript' src='https://cdnjs.cloudflare.com/ajax/libs/fancybox/2.1.5/jquery.fancybox.min.js'></script>"); } } }, j; for (j = 0; j < scriptArray.length; j++) { if (initScripts.hasOwnProperty(scriptArray[j]) && typeof initScripts[scriptArray[j]] === "function") { initScripts[scriptArray[j]](); } } console.log("GME init: " + scriptArray.join()); } function b64encode(str) { if (typeof window.btoa === "function") { return btoa(encodeURIComponent(str)); } else { return encodeURIComponent(str); } } function b64decode(str) { if (typeof window.atob === "function") { return decodeURIComponent(window.atob(str)); } else { return decodeURIComponent(str); } } function DMM(ll) { var latDeg = ll.lat < 0 ? Math.ceil(ll.lat) : Math.floor(ll.lat), lngDeg = ll.lng < 0 ? Math.ceil(ll.lng) : Math.floor(ll.lng); return (ll.lat < 0 ? "S" : "N") + Math.abs(latDeg) + " " + (60 * Math.abs((ll.lat - latDeg))).toFixed(3) + (ll.lng < 0 ? " W" : " E") + Math.abs(lngDeg) + " " + (60 * Math.abs((ll.lng - lngDeg))).toFixed(3); } function formatDistance(dist) { var formatted = 0; if (that.parameters.measure === "metric") { if (dist > 10000) { formatted = Math.round(dist / 1000) + " km"; } else { if (dist > 1000) { formatted = (dist / 1000).toFixed(1) + " km"; } else { formatted = Math.round(dist) + " m"; } } } else { if (dist > 16093.44) { formatted = Math.round(dist / 1609.344) + " mi"; } else { if (dist > 1609.344) { formatted = (dist / 1609.344).toFixed(1) + " mi"; } else { formatted = Math.round(dist * 3.2808) + " ft"; } } } return formatted; } function htmlEntities(text) { return text .replace(/&/g, "&") .replace(/\"/g, """) .replace(/'/g, "'") .replace(/</g, "<") .replace(/>/g, ">"); } function validCoords(c1, c2) { var lat, lng; if (c1 === undefined) { return false; } if (c1.hasOwnProperty("lat") && c1.hasOwnProperty("lng")) { lat = c1.lat; lng = c1.lng; } else { if (c2 !== undefined) { lat = c1; lng = c2; } } if (lat !== null && lng !== null && !isNaN(+lat) && !isNaN(+lng) && lat >= -90 && lat <= 90) { return true; } return false; } function parseCoords(text) { var lat = 0, lng = 0, num = 0, c = text.replace(/[^\-SsWw0-9\.\s]/g, " ").trim().match(/^([S\-])?\s*(\d{1,2}(\.\d*){0,1}|\.\d*)(\s+(\d{0,2}(\.\d*){0,1})){0,1}(\s+(\d{0,2}(\.\d*){0,1})){0,1}\s*([S\-])?\s+([W\-])?\s*(\d{1,3}(\.\d*){0,1}|\.\d*)(\s+(\d{0,2}(\.\d*){0,1})){0,1}(\s+(\d{0,2}(\.\d*){0,1})){0,1}\s*([W\-])?$/i); if (c) { num = (c[2] ? 1 : 0) + (c[5] ? 1 : 0) + (c[8] ? 1 : 0) + (c[12] ? 1 : 0) + (c[15] ? 1 : 0) + (c[18] ? 1 : 0); switch (num) { case 6: break; case 4: if (c[15] === undefined) { c[15] = c[12]; c[12] = c[8]; c[8] = undefined; } break; case 2: if (c[12] === undefined && c[5]) { c[12] = c[5]; c[5] = undefined; } break; default: alert("Couldnt understand coordinates"); return false; } if (c[2] !== undefined) { lat = +c[2]; } if (c[5] !== undefined) { lat += c[5] / 60; } if (c[8] !== undefined) { lat += c[8] / 3600; } if (c[1] !== undefined || c[10] !== undefined) { lat *= -1; } if (c[12] !== undefined) { lng = +c[12]; } if (c[15] !== undefined) { lng += c[15] / 60; } if (c[18] !== undefined) { lng += c[18] / 3600; } if (c[11] !== undefined || c[20] !== undefined) { lng *= -1; } } if (validCoords(lat, lng)) { return { lat: lat, lng: lng }; } alert("Invalid coordinates"); return false; } function getHomeCoords() { var c, h = document.getElementById("ctl00_ContentBody_lnkPrintDirectionsSimple"); if (window.MapSettings && MapSettings.User && validCoords(MapSettings.User.Home)) { return new L.LatLng(MapSettings.User.Home.lat, MapSettings.User.Home.lng); } if (validCoords(window.homeLat, window.homeLon)) { return new L.LatLng(window.homeLat, window.homeLon); } if (h && h.href) { c = h.href.match(/(?:saddr=)(-?\d{1,2}\.\d*),(-?\d{1,3}\.\d*)/); if (c !== null && c.length === 3 && validCoords(c[1], c[2])) { return new L.LatLng(c[1], c[2]); } } return false; } function validURL(url) { return (/^(http|https|ftp)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\?'\\\+&%\$#\=~_\-]+))*$/).test(url); } if (window.console === undefined) { var logFn = function (text) { }; window.console = { error: logFn, log: logFn, info: logFn, warn: logFn }; } if (gmeConfig.env.xhr) { JSONP = function (url, id) { console.log("GME: Using GM_xhr to fetch " + url); var s = document.getElementById("gme_jsonp_node"); if (!s) { s = document.createElement("script"); s.id = "gme_jsonp_node"; document.documentElement.firstChild.appendChild(s); } s.type = "text/x-gme-jsonp"; s.text = url; s.setAttribute("data-gme-callback", id); document.dispatchEvent(new Event("GME_XHR_event")); }; document.addEventListener("GME_XHR_callback", function (e) { var s = document.getElementById("gme_jsonp_node"), callback = s.getAttribute("data-gme-callback"); if (typeof window[callback] === "function") { try { window[callback](JSON.parse(s.text)); } catch (e) { console.error("Error processing JSON callback " + callback + ": " + e); } } else { console.warn("Unexpected request to JSON callback handler: Couldn't find callback function " + callback); } return false; }); } else { JSONP = function (url, id) { console.log("GME: Using JSONP to fetch " + url); if (validURL(url)) { var s = document.createElement("script"); s.type = "text/javascript"; if (id) { s.id = id; } s.src = url; document.documentElement.firstChild.appendChild(s); } }; } gmeConfig.env.home = getHomeCoords(); this.parameters = gmeConfig.parameters; this.getVersion = function () { return gmeConfig.parameters.version; }; this.getGeograph = function (coords) { var callprefix = "GME_geograph_callback", call, host = ""; function searchLink(coords) { // URIs for website search pages. if (coords === undefined) { return false; } var host = ""; if (bounds_GB.contains(coords) || bounds_IE.contains(coords)) { host = "https://geograph.org.uk/"; } if (bounds_CI.contains(coords)) { host = "https://www.geograph.org.gg/"; } if (bounds_DE.contains(coords)) { host = "https://geo-en.hlipp.de/"; } return host ? [host, "search.php?location=", coords.toUrl()].join("") : false; } function makeCallback(callname) { callbackCount++; return function (json) { var html, i, p; if (json.items && json.items.length > 0) { html = ["<h3>Geograph images near ", DMM(coords), "</h3><p>"].join(""); for (i = json.items.length - 1; i >= 0; i--) { p = json.items[i]; html += ["<a target='_blank' rel='noopener noreferrer' href='", encodeURI(p.link), "' style='margin-right:0.5em;' title='", htmlEntities(p.title) + " by " + htmlEntities(p.author), "'>", p.thumbTag, "</a>"].join(""); } html += ["</p><p><a target='_blank' rel='noopener noreferrer' href='", searchLink(coords), "'>Search for more photos nearby on Geograph</a></p><p style='font-size:90%;'>Geograph photos are copyrighted by their owners and available under a <a href='https://creativecommons.org/licenses/by-sa/2.0/'>Creative Commons licence</a>. Hover mouse over thumbnails for more details, or click through for full images.</p>"].join(""); $.fancybox(html); } else { $.fancybox(["<p>No photos found nearby. <a target='_blank' rel='noopener noreferrer' href='", searchLink(coords), "'>Search on Geograph</a></p>"].join("")); } $("#" + callname).remove(); if (window[callname] !== undefined) { delete window[callname]; } }; } if (validCoords(coords) && this.isGeographAvailable(coords)) { if (!bounds_CI.contains(coords) && (bounds_GB.contains(coords) || bounds_IE.contains(coords))) { host = "https://api.geograph.org.uk/"; call = callprefix + callbackCount; window[call] = makeCallback(call); JSONP(host + "syndicator.php?key=geo.inge.org.uk&location=" + coords.toUrl() + "&format=JSON&callback=" + call, call); } else { window.open(searchLink(coords), "_blank"); } } else { console.error("GME: Bad coordinates to getGeograph"); } }; this.getHeight = function (coords) { var callprefix = "GME_height_callback", call; function makeCallback(callname) { callbackCount++; return function (json) { if (typeof json.astergdem === "number" && typeof json.lat === "number" && typeof json.lng === "number") { var h, m; if (json.astergdem === -9999) { m = "<p><strong>Spot Height</strong><br/>(Ocean)</p>"; } else { h = that.parameters.measure === "metric" ? json.astergdem + " m" : Math.round(json.astergdem * 3.2808) + " ft"; m = ["<p><strong>Spot Height</strong><br/>Approx ", h, " above sea level</p>"].join(""); } $.fancybox(m); } $("#" + callname).remove(); if (window[callname] !== undefined) { delete window[callname]; } }; } if (validCoords(coords)) { call = callprefix + callbackCount; window[call] = makeCallback(call); JSONP(["http://api.geonames.org/astergdemJSON?lat=", coords.lat, "&lng=", coords.lng, "&username=gme&callback=", call].join(""), call); } else { console.error("GME: Bad coordinates to getHeight"); } }; this.isGeographAvailable = function (coords) { return bounds_GB.contains(coords) || bounds_DE.contains(coords) || bounds_IE.contains(coords) || bounds_CI.contains(coords); }; this.isInUK = function (coords) { if (bounds_GB.contains(coords)) { if (bounds_IE.contains(coords)) { if (bounds_NI.contains(coords)) { return true; } return false; } return true; } return false; }; if (gmeConfig.env.geolocation) { this.seekHere = function () { function hereCallback(pos) { that.seekByLatLng({ lat: pos.coords.latitude, lng: pos.coords.longitude }); $("#GME_hereSub").val("Go"); } function hereError(err) { if (err.code === 2) { alert("Current location not available"); } if (err.code === 3) { alert("Timed out finding current location"); } $("#GME_hereSub").val("Go"); } $("#GME_hereSub").val("Waiting for location..."); navigator.geolocation.getCurrentPosition(hereCallback, hereError, { timeout: 60000, maximumAge: 30000 }); return false; }; } this.seekByLatLng = function (latlng) { if (validCoords(latlng)) { var url = ["https://www.geocaching.com/seek/nearest.aspx?origin_lat=", latlng.lat, "&origin_long=", latlng.lng, that.parameters.filterFinds ? "&f=1" : ""].join(""); window.open(url, "_blank"); } else { console.error("GME: Invalid coordinates for search"); } }; }, config: function () { function addSources(json) { function setSrc(src) { if (src.alt && src.tileUrl) { var m = that.parameters.maps.concat(src); that.parameters.maps = m; return 1; } alert("Map source must include at least \"alt\" and \"tileUrl\" parameters"); return 0; } var i, updated = 0; if (json.length === undefined) { updated += setSrc(json); } else { for (i = 0; i < json.length; i++) { updated += setSrc(json[i]); } } if (updated > 0) { setConfig(); $("#gme-tab-maps")[0].checked = true; } } function addCustom() { try { var n = JSON.parse(document.getElementById("GME_map_custom").value); addSources(n); } catch (e) { alert("Map source string must be valid JSON."); return; } } function exportCustom() { $.fancybox($("<p/>").text(JSON.stringify(that.parameters.maps)).html()); } function setDefault() { if (localStorage.GME_custom) { delete localStorage.GME_custom; } if (localStorage.GME_parameters) { delete localStorage.GME_parameters; } if (localStorage.GME_cache) { delete localStorage.GME_cache; } refresh(); } function refresh(config) { var dest = "https://www.geocaching.com/map/#", mapLink = document.getElementById("map_linkto"), uri; if (config) { dest += "GME_config"; } if (mapLink) { uri = mapLink.value; if (uri) { dest += uri.replace(/^http:\/\/coord.info\/map/, ""); } document.location.href = dest; } else { document.location.hash = ""; } window.location.reload(false); return false; } function setConfig() { var i, mapfields = "", shadingSelect = "<option value=''>none</option>", mapfields_del = "", mapselect = "", alt = "", overlay, sel, overSel, allMaps = that.parameters.maps, allApiKeys = that.parameters.apiKeys; for (i = 0; i < allMaps.length; i++) { alt = allMaps[i].alt; overlay = allMaps[i].overlay; if (!overlay) { mapselect += "<option value='" + htmlEntities(alt) + "'>" + htmlEntities(alt) + "</option>"; } else { shadingSelect += "<option value='" + htmlEntities(alt) + "'>" + htmlEntities(alt) + "</option>"; } mapfields += "<li><label><input type='checkbox' " + (allMaps[i].ignore ? "" : "checked='checked' ") + "name='" + htmlEntities(alt) + "' id='checkbox-" + i + "' /> " + htmlEntities(alt) + (overlay ? " (Overlay)" : "") + "</label></li>"; } // API keys const mapsWithApiKey = allMaps.filter((map) => map.apiKey); mapsWithApiKey.forEach((map) => { const existingApiKey = allApiKeys && allApiKeys[map.apiKey] ? allApiKeys[map.apiKey] : ""; const label = document.createElement("label"); label.innerHTML = `${map.apiKey} `; const input = document.createElement("input"); input.type = "text"; input.name = map.apiKey; input.value = existingApiKey; label.appendChild(input); $("#GME_apikeys_field").append(label); }); if (allMaps.length > 0) { for (i = 0; i < allMaps.length; i++) { alt = allMaps[i].alt; mapfields_del += "<li><label><input type='checkbox' name='" + htmlEntities(alt) + "' id='checkbox-del-" + i + "' /> " + htmlEntities(alt) + (allMaps[i].overlay ? " (Overlay)" : "") + "</label></li>"; } } else { mapfields_del = "< No custom maps installed >"; } $("#GME_mapfields").html(mapfields); $("#GME_mapfields_del").html(mapfields_del); $("#GME_map_default").html(mapselect); $("#GME_hill_shade_default").html(shadingSelect); sel = $("#GME_map_default").children(); for (i = sel.length - 1; i > -1; i--) { if (sel[i].value === that.parameters.defaultMap) { sel[i].selected = "selected"; } } overSel = $("#GME_hill_shade_default").children(); for (i = overSel.length - 1; i > -1; i--) { if (overSel[i].value === that.parameters.defaultHillShade) { overSel[i].selected = "selected"; } } $("#GME_filterFinds").attr("checked", that.parameters.filterFinds); $("#GME_osgbSearch").attr("checked", that.parameters.osgbSearch); $("#GME_follow").attr("checked", that.parameters.follow); $("#GME_labelStyle").val(that.parameters.labels); $("#GME_measure").val(that.parameters.measure); $("#GME_brightness").val(that.parameters.brightness * 100); $("#GME_version").html(that.parameters.version); } function storeSettings() { var i, j, list, apiKeys; that.parameters.defaultMap = $("#GME_map_default")[0].value; that.parameters.defaultHillShade = $("#GME_hill_shade_default")[0].value; list = $("#GME_mapfields input"); for (i = list.length - 1; i >= 0; i--) { for (j = that.parameters.maps.length - 1; j >= 0; j--) { if (that.parameters.maps[j].alt === list[i].name) { that.parameters.maps[j].ignore = !list[i].checked; } } } for (j = that.parameters.maps.length - 1; j >= 0; j--) { if (that.parameters.maps[j].alt === that.parameters.defaultMap) { that.parameters.maps[j].ignore = false; } } list = $("#GME_mapfields_del input"); for (i = list.length - 1; i >= 0; i--) { if (list[i].checked === true) { for (j = that.parameters.maps.length - 1; j >= 0; j--) { if (that.parameters.maps[j].alt === list[i].name) { that.parameters.maps.splice(j, 1); break; } } } } if (!that.parameters.apiKeys) { that.parameters.apiKeys = {}; } apiKeys = $("#GME_apikeys_field label input"); for (var ak = 0; ak < apiKeys.length; ak++) { const field = apiKeys[ak]; if (field.value) { that.parameters.apiKeys[field.name] = field.value; } else if (that.parameters.apiKeys[field.name]) { delete that.parameters.apiKeys[field.name]; } } that.parameters.brightness = $("#GME_brightness").val() / 100; that.parameters.filterFinds = $("#GME_filterFinds")[0].checked ? true : false; that.parameters.follow = $("#GME_follow")[0].checked ? true : false; that.parameters.labels = $("#GME_labelStyle")[0].value; that.parameters.measure = $("#GME_measure")[0].value; that.parameters.osgbSearch = $("#GME_osgbSearch")[0].checked ? true : false; localStorage.setItem("GME_parameters", JSON.stringify(that.parameters)); refresh(); } }, cssTransitionsFix: function () { // <bugfix> // Work around bug that breaks JQuery Mobile dialog boxes in Opera 12. if (window.$ && $.support) { $.support.cssTransitions = false; } // </bugfix> }, dist: function () { $("#lblDistFromHome").parent().append("<br/><span id='gme-dist'><a href='#' id='gme-dist-link'>Check distance from here</a></span>"); $("#gme-dist-link").click(function () { var there = new LatLon(mapLatLng.lat, mapLatLng.lng), rose = [[22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5, 337.5], ["N", "NE", "E", "SE", "S", "SW", "W", "NW"]], watcher; function found(pos) { var here = new LatLon(pos.coords.latitude, pos.coords.longitude), bearing = here.bearingTo(there), dir = "N", i; for (i = 0; i < 8; i++) { if (bearing < rose[0][i]) { dir = rose[1][i]; break; } } $("#gme-dist").html("<img style='vertical-align:text-bottom' alt='" + dir + "' src='/images/icons/compass/" + dir + ".gif'> " + dir + " " + formatDistance(here.distanceTo(there) * 1000) + " from here at bearing " + Math.round(bearing) + "°"); } function lost() { if (watcher) { navigator.geolocation.clearWatch(watcher); } alert("GME couldn't detect your location.\nDisable FollowMe mode in Geocaching Map Enhancements if this error pops up repeatedly."); } if (that.parameters.follow) { watcher = navigator.geolocation.watchPosition(found, lost, { timeout: 60000, maximumAge: 30000 }); } else { navigator.geolocation.getCurrentPosition(found, lost, { timeout: 60000, maximumAge: 30000 }); } return false; }); }, drag: function () { that.dragStart = function (event) { function GME_formatLOC(wpts) { return wpts ? ['<?xml version="1.0" encoding="UTF-8"?>\n<loc version="1.0" src="Geocaching Map Enhancements v' + that.getVersion() + '">' + wpts.join('\n') + '</loc>'].join('\n') : null; } function GME_formatLOC_wpt(id, desc, coords, type, link) { if (id && desc && coords) { var t = "Geocache", l = link ? ('\n\t<link text="' + link.desc + '">' + link.href + '</link>') : ""; switch (type) { case "Original Coordinates": t = type; break; case 217: t = "Parking Area"; break; case 218: t = "Question to Answer"; break; case 219: t = "Stages of a Multicache"; break; case 220: t = "Final Location"; break; case 221: t = "Trailhead"; break; case 452: t = "Reference Point"; break; } return ('<waypoint>\n\t<name id="' + id + '"><![CDATA[' + desc + ']]></name>\n\t<coord lat="' + coords.lat + '" lon="' + coords.lng + '"/>\n\t<type>' + t + '</type>' + l + '\n</waypoint>'); } console.warn("GME: Missing cache data - id:", id, "desc:", desc, "coords:", coords); return null; } var c, dataURI, dt, i, locfmt, id = $("#ctl00_ContentBody_CoordInfoLinkControl1_uxCoordInfoCode")[0].innerHTML, loc = [GME_formatLOC_wpt(id, cache_coords.primary[0].name, cache_coords.primary[0], cache_coords.primary[0].type, { desc: "Cache Details", href: "https://coord.info/" + id })]; for (i = cache_coords.additional.length - 1; i >= 0; i--) { c = cache_coords.additional[i]; loc.push(GME_formatLOC_wpt(c.pf + id.slice(2), [c.name, $("#awpt_" + c.pf).parent().parent().next().children()[2].innerHTML.trim()].join(" "), c, c.type)); } if (cache_coords.primary[0].isUserDefined) { loc.push(GME_formatLOC_wpt("GO" + id.slice(2), cache_coords.primary[0].name, { lat: cache_coords.primary[0].oldLatLng[0], lng: cache_coords.primary[0].oldLatLng[1] }, "Original Coordinates", { desc: "Cache Details", href: "https://coord.info/" + id })); } locfmt = GME_formatLOC(loc); dataURI = "data:application/xml-loc," + encodeURIComponent(locfmt); dt = event.originalEvent.dataTransfer; if (window.DataTransfer !== undefined && dt.constructor === window.DataTransfer) { dt.setData("application/gme-cache-coords", JSON.stringify(cache_coords)); dt.setData("application/xml-loc", locfmt); dt.setData("text/x-moz-url", dataURI + "\nGME_waypoints.loc"); dt.setData("DownloadURL", "application/xml-loc:GME_waypoints.loc:" + dataURI); } dt.setData("text/uri-list", dataURI); dt.setData("Text", locfmt); dt.effectAllowed = "copy"; dt.setDragImage($('a[title="About Cache Types"] img')[0], 0, 0); }; }, drop: function () { var dropHandlerObj = { onAdd: function (map) { var container = $(map.getContainer()); this._map = map; container.on("drop", this.drop(map)); container.on("dragover", this.dragOver); return document.createElement("div"); }, onRemove: function (map) { var container = $(map.getContainer()); container.off("drop", this.drop(map)); container.off("dragover", this.dragOver); }, drop: function (map) { return function (e) { function typeToIcon(t) { var j, type = t; for (j = wptTypes.length - 1; j >= 0; j--) { type = type.replace(wptTypes[j][0], wptTypes[j][1]); } return type; } function parseLOC(text) { var i, l, w, t, len, lat, lng, name, points = { primary: [], additional: [] }, wpts = $($.parseXML(text)).find("waypoint"); for (i = 0, len = wpts.length; i < len; i++) { w = $(wpts[i]); lat = w.find("coord").attr("lat"); lng = w.find("coord").attr("lon"); name = w.find("name").attr("id") + ": " + w.find("name").text().trim(); if (isNaN(+lat) || isNaN(+lng) || lat < -90 || lat > 90) { return false; } t = w.find("type").text(); if (/Geocache/i.test(t)) { points.primary.push({ lat: lat, lng: lng, name: name, type: 2 }); } else { l = points.primary.length; if (l && /Original Coordinates/i.test(t)) { points.primary[l - 1].oldLatLng = [lat, lng]; points.primary[l - 1].isUserDefined = true; } else { points.additional.push({ lat: lat, lng: lng, name: name, type: typeToIcon(t) }); } } } return (points.additional.length + points.primary.length > 0) ? points : false; } function readLOC(e) { var data = e.target.result, pts = parseLOC(data); if (pts) { console.info("GME: Received LOC file"); GME_displayPoints(pts, map, "dragdrop"); } } function parseGPX(text) { var d, i, j, k, w, r, t, lat, lng, len, n, name = "", poly, type, points = { primary: [], additional: [], routes: [] }, gpx = $($.parseXML(text)), wpts = gpx.find("wpt"), tracks = gpx.find("trk"), segs, routes = gpx.find("rte"); for (i = 0, len = wpts.length; i < len; i++) { w = $(wpts[i]); lat = w.attr("lat"); lng = w.attr("lon"); n = w.filterNode("name"); d = w.filterNode("desc"); name = n.length > 0 ? n[0].textContent : "Point " + i; name += (n.length > 0 && d.length > 0) ? " : " : ""; name += d.length > 0 ? d[0].textContent : ""; if (isNaN(+lat) || isNaN(+lng) || lat < -90 || lat > 90) { return false; } t = w.find("sym").text(); if (/Geocache/i.test(t)) { t = w.filterNode("groundspeak:type"); if (t.length > 0) { type = t[0].textContent; } else { type = "Geocache"; } } else { type = t; } points[/Geocache/i.test(t) ? "primary" : "additional"].push({ lat: lat, lng: lng, name: name, type: typeToIcon(type) }); } for (i = routes.length - 1; i >= 0; i--) { poly = []; r = $(routes[i]); n = r.filterNode("name"); name = n.length > 0 ? n[0].textContent : "Route " + i; wpts = r.find("rtept"); for (j = wpts.length - 1; j >= 0; j--) { w = $(wpts[j]); poly.push(new L.LatLng(w.attr("lat"), w.attr("lon"))); } points.routes.push({ name: name, points: poly }); } for (i = tracks.length - 1; i >= 0; i--) { poly = []; r = $(tracks[i]); segs = r.find("trkseg"); for (j = segs.length - 1; j >= 0; j--) { n = r.filterNode("name"); name = [(n.length > 0) ? n[0].textContent : "Track " + i, " segment ", j].join(""); wpts = $(segs[j]).find("trkpt"); for (k = wpts.length - 1; k >= 0; k--) { w = $(wpts[k]); poly.push(new L.LatLng(w.attr("lat"), w.attr("lon"))); } points.routes.push({ name: name, points: poly }); } } return (points.additional.length + points.primary.length + points.routes.length > 0) ? points : false; } function readGPX(e) { var data = e.target.result, pts = parseGPX(data); if (pts) { console.info("GME: Received GPX file"); GME_displayPoints(pts, map, "dragdrop"); } } e.stopPropagation(); e.preventDefault(); var i, data, dt = e.originalEvent.dataTransfer, file, files = dt.files, pts, reader; try { data = dt.getData("application/gme-cache-coords"); if (data) { console.info("GME: Received GME data"); GME_displayPoints(JSON.parse(data), map, "dragdrop"); return; } data = dt.getData("text/plain"); if (data) { pts = parseLOC(data); if (pts) { console.info("GME: Received LOC text"); GME_displayPoints(pts, map, "dragdrop"); return; } pts = parseGPX(data); if (pts) { console.info("GME: Received GPX text"); GME_displayPoints(pts, map, "dragdrop"); return; } } } catch (E) { console.warn("GME: Drop: " + E); } for (i = files.length - 1; i >= 0; i--) { file = files[i]; if (/application\/xml-loc/.test(file.type) || /\.loc$/i.test(file.name)) { reader = new FileReader(); reader.onload = readLOC; reader.readAsText(file); } else { if (/application\/xml-gpx/.test(file.type) || /\.gpx$/i.test(file.name)) { reader = new FileReader(); reader.onload = readGPX; reader.readAsText(file); } else { console.warn("GME: Dropped file not recognised: " + file.name + ", (type: " + file.type + ")"); } } } }; }, dragOver: function (e) { var dt = e.originalEvent.dataTransfer; function contains(array, value) { if (array.indexOf) { return array.indexOf(value) >= 0; } if (array.contains) { return array.contains(value); } console.warn("GME: couldn\'t determine type of dragged data. Accepting anyway."); return true; } if (dt && dt.types) { try { if (contains(dt.types, "application/gme-cache-coords") || contains(dt.types, "application/xml-gpx") || contains(dt.types, "application/xml-loc") || contains(dt.types, "text/plain") || contains(dt.types, "Files")) { e.preventDefault(); return false; } } catch (e) { console.error("GME: dragOver:", e); } } return false; } }; }, loadDefault: function () { if (typeof window.$ === "function") { gmeInit(gmeConfig.env.init); } }, labels: function () { function GME_load_labels(control, div) { function labelHandler() { var action = this.getAttribute("data-gme-action"), cache = this.getAttribute("data-gme-cache"); switch (action) { case "panTo": if (control.labels.labels[cache]) { control._map.panTo(control.labels.labels[cache][2]); } break; case "refresh": control.labels.refresh(); break; case "clear": control.labels.removeLabels(); break; case "auto": control.labels.toggleAuto(); break; case "show": control.labels.toggleShow(); break; } return false; } L.GME_identifyLayer = L.Class.extend({ initialize: function (latlng, options) { L.Util.setOptions(this, options); this._latlng = latlng; }, onAdd: function (map) { this._map = map; this._el = L.DomUtil.create("div", "gme-identify-layer leaflet-zoom-hide"); this._el.innerHTML = this.options.label; this._el.title = this.options.desc; this._el.style.position = "absolute"; map.getPanes().overlayPane.appendChild(this._el); map.on("viewreset", this._reset, this); this._reset(); }, onRemove: function (map) { map.getPanes().overlayPane.removeChild(this._el); map.off("viewreset", this._reset, this); }, options: { label: "Cache", desc: "Long cache name" }, setPosition: function (ll) { this._latlng = ll; this._reset(); }, _reset: function () { if (this._map) { var pos = this._map.latLngToLayerPoint(this._latlng); L.DomUtil.setPosition(this._el, pos); } } }); control.labels = { showLabels: false, autoUpdate: false, labels: {}, labelLayer: new L.LayerGroup(), clearLabels: function () { control._map.removeLayer(control.labels.labelLayer); }, displayLabels: function () { control._map.addLayer(control.labels.labelLayer); }, refresh: function () { if (!(window.MapSettings && MapSettings.MapLayers && MapSettings.MapLayers.UTFGrid)) { return; } var i, coords, tiles = $(".leaflet-tile-pane .leaflet-layer img[src*='geocaching.com/map.png']"); for (i = tiles.length - 1; i >= 0; i--) { coords = tiles[i].src.match(/x=(\d+)&y=(\d+)&z=(\d+)/); if (coords && !MapSettings.MapLayers.UTFGrid._cache.hasOwnProperty(([coords[3], coords[1], coords[2]].join("_")))) { MapSettings.MapLayers.UTFGrid._loadTile(coords[3], coords[1], coords[2]); } } setTimeout(control.labels.refreshLabels, 500); }, refreshLabels: function () { var c, p, q, r, tile, tilepos, tileref, gridref, zoom = control._map.getZoom(); if (!(window.MapSettings && MapSettings.MapLayers && MapSettings.MapLayers.UTFGrid)) { return; } for (p in MapSettings.MapLayers.UTFGrid._cache) { if (MapSettings.MapLayers.UTFGrid._cache.hasOwnProperty(p)) { tileref = p.split("_"); if (tileref.length === 3) { tile = $("img[src*='x=" + tileref[1] + "&y=" + tileref[2] + "&z=" + tileref[0] + "']")[0]; if (tile) { tilepos = L.DomUtil.getPosition(tile); if (MapSettings.MapLayers.UTFGrid._cache[p]) { for (q in MapSettings.MapLayers.UTFGrid._cache[p].data) { if (MapSettings.MapLayers.UTFGrid._cache[p].data.hasOwnProperty(q)) { for (r in MapSettings.MapLayers.UTFGrid._cache[p].data[q]) { if (MapSettings.MapLayers.UTFGrid._cache[p].data[q].hasOwnProperty(r)) { c = MapSettings.MapLayers.UTFGrid._cache[p].data[q][r]; if (!control.labels.labels[c.i]) { gridref = q.match(/\((\d+), (\d+)\)/); if (gridref) { control.labels.labels[c.i] = [c.i, c.n, control._map.layerPointToLatLng(tilepos.add(new L.Point(4 * gridref[1], 4 * gridref[2]))), zoom]; control.labels.labels[c.i][4] = new L.GME_identifyLayer(control.labels.labels[c.i][2], (that.parameters.labels === "names") ? { label: c.n, desc: c.i } : { label: c.i, desc: c.n }); } } else { if (zoom > control.labels.labels[c.i][3]) { gridref = q.match(/\((\d+), (\d+)\)/); if (gridref) { control.labels.labels[c.i][2] = control._map.layerPointToLatLng(tilepos.add(new L.Point(4 * gridref[1], 4 * gridref[2]))); control.labels.labels[c.i][3] = zoom; control.labels.labels[c.i][4].setPosition(control.labels.labels[c.i][2]); } } } control.labels.labelLayer.addLayer(control.labels.labels[c.i][4]); } } } } } } } } } control.labels.updateCachePanel(); if (control.labels.showLabels) { control.labels.clearLabels(); control.labels.displayLabels(); } }, removeLabels: function () { $("#gme_cachelist").html(""); control.labels.labelLayer.clearLayers(); control.labels.labels = {}; }, toggleAuto: function () { if (control.labels.autoUpdate) { control.labels.autoUpdate = false; control._map.off("moveend", control.labels.refresh); $(".gme-button-labels-auto").removeClass("gme-button-active"); } else { control.labels.autoUpdate = true; $(".gme-button-labels-auto").addClass("gme-button-active"); control._map.on("moveend", control.labels.refresh); control.labels.refresh(); } }, toggleShow: function () { if (control.labels.showLabels) { control.labels.showLabels = false; $(".gme-button-labels-show").removeClass("gme-button-active"); control.labels.clearLabels(); } else { control.labels.showLabels = true; $(".gme-button-labels-show").addClass("gme-button-active"); control.labels.refresh(); } }, updateCachePanel: function () { var i, j, sortorder = [], html = ""; for (i in control.labels.labels) { if (control.labels.labels.hasOwnProperty(i)) { sortorder.push(i); } } sortorder.sort(); j = sortorder.length; for (i = 0; i < j; i++) { html += "<tr><td><a href='https://coord.info/" + sortorder[i] + "' target='_blank' rel='noopener noreferrer'>" + control.labels.labels[sortorder[i]][1] + "</a></td><td class='gme-cache-code'> " + sortorder[i] + "</td><td><a class='gme-event' title='Pan map to cache location' data-gme-action='panTo' data-gme-cache='" + sortorder[i] + "'><img src='../images/silk/map.png' width='16' height='16' alt='Pan' /></a></td></tr>"; } $("#gme_cachelist").html(html); } }; $("#searchtabs ul").append("<li id='gme_caches_button'><a href='#gme_caches' title='GME Cache Label List' id='gme_caches_link'>GME</a></li>"); $("#searchtabs li").css("width", 100 / $("#searchtabs li").length + "%"); $("#pqlink").html("PQs"); $("#clistButton").html("GCVote"); document.getElementById("pqlink").innerHTML = "PQs"; $(div).append("<div id='gme_caches'>\ <div class='leaflet-control-gme'>\ <a title='Refresh cache labels' class='gme-event gme-button-refresh-labels gme-button gme-button-l' data-gme-action='refresh'></a><a title='Empty cache list and remove labels from map' class='gme-event gme-button gme-button-clear-labels' data-gme-action='clear'></a><a class='gme-event gme-button gme-button-wide gme-button-labels-show' data-gme-action='show'>Show labels</a><a class='gme-event gme-button gme-button-r gme-button-wide gme-button-labels-auto' data-gme-action='auto'>Auto update</a>\ </div>\ <table><tbody id='gme_cachelist'><tr><td colspan='3'>Hit the refresh button above to populate the list.</td></tr></tbody></table></div>"); $("#gme_caches").css("display", "none").on("click", ".gme-event", labelHandler); $("#gme-labels-show").on("change", control.labels.toggleShow); $("#gme-labels-auto").on("change", control.labels.toggleAuto); } }, loadHide: function () { function load() { window.GME_control = new L.GME_Widget().addTo(map); GME_control._layerControl = GME_load_map(map); if (gmeConfig.env.dragdrop) { map.addControl(new L.GME_dropHandler()); } } window.setTimeout(setEnv, 3000); }, loadListing: function () { var cache_coords = {}, mapLink = document.getElementById("ctl00_ContentBody_uxViewLargerMap"); function load() { var parkUrl = "", label = "", i, parking, uri = "&pop="; if (L.LatLng.prototype.toUrl === undefined) { L.LatLng.prototype.toUrl = function () { var obj = this; if (!(obj instanceof L.LatLng)) { return false; } return [L.Util.formatNum(obj.lat, 5), L.Util.formatNum(obj.lng, 5)].join(","); }; } $("#map_canvas").replaceWith("<div style=\'width: 325px; height: 325px; position: relative;\' id=\'map_canvas2\'></div>"); if (gmeConfig.env.dragdrop) { $("#cacheDetails .cacheImage").hover(function (e) { $("#cacheDetails .cacheImage").addClass("moveable"); }, function (e) { $("#cacheDetails .cacheImage").removeClass("moveable"); }); $("#cacheDetails .cacheImage").attr("draggable", "true").on("dragstart", that.dragStart); $("#cacheDetails .cacheImage a").removeAttr("href"); } window.GME_Map = new L.Map("map_canvas2", { center: new L.LatLng(mapLatLng.lat, mapLatLng.lng), zoom: 14 }); GME_Map.addControl(new L.control.scale()); GME_load_map(GME_Map); cache_coords = { primary: [mapLatLng], additional: [] }; if (cmapAdditionalWaypoints && cmapAdditionalWaypoints.length > 0) { cache_coords.additional = cmapAdditionalWaypoints; if (gmeConfig.env.home) { for (i = cmapAdditionalWaypoints.length - 1; i >= 0; i--) { if (cmapAdditionalWaypoints[i].hasOwnProperty("editurl")) { delete cache_coords.additional[i].editurl; } parking = cmapAdditionalWaypoints[i]; if (parking.type === 217 || parking.type === 221) { label = parking.type === 217 ? "Parking Area" : "Trailhead"; parkUrl = `https://www.google.com/maps/dir/${gmeConfig.env.home.toUrl()}/${parking.lat},${parking.lng}/`; $("#awpt_" + parking.pf)[0].parentNode.parentNode.children[1].innerHTML += `<a target="_blank" rel="noopener noreferrer" href="${parkUrl}"><img width="16" height="16" title="[GME] Directions to ${label}" alt="${label}" src="https://www.geocaching.com/images/icons/16/directions.png" /></a>`; } } } } if (gmeConfig.env.dragdrop) { GME_Map.addControl(new L.GME_dropHandler()); } if (cache_coords.primary[0].oldLatLng || cache_coords.primary.length + cache_coords.additional.length > 1) { uri += b64encode(JSON.stringify(cache_coords)); mapLink.href = mapLink.href.replace("http:", "https:") + uri; $('#ctl00_ContentBody_MapLinks_MapLinks a[href*="geocaching.com"]').attr("href", function (i, val) { return val + uri; }); } GME_displayPoints(cache_coords, GME_Map, "listing"); } setEnv(); }, loadMap: function () { function load() { function goSearch(e) { if (e.type === "click" || (e.which || e.keyCode) === 13) { e.preventDefault(); e.stopImmediatePropagation(); return GME_control.search($.trim($("#SearchBox_Text").val() || "")); } } var jsonURI; GME_load_map(MapSettings.Map); if (gmeConfig.env.dragdrop) { MapSettings.Map.addControl(new L.GME_dropHandler()); } window.GME_control = GME_load_widget(MapSettings.Map); GME_load_labels(GME_control, "#scroller"); if (gmeConfig.parameters.osgbSearch) { $("#SearchBox_OS").on("click keypress", goSearch); $(".SearchBox").on("keydown", goSearch); $("#search p")[0].innerHTML = "Search by <span style='cursor:help;' title='Enhanced by Geonames'>Address</span>, Coordinates, GC-code,<br/><span style='cursor:help;' title='Jump to a specific zoom level by typing zoom then a number. Zoom 1 shows the whole world, maxiumum zoom is normally 18-22.'>zoom</span> or <span style='cursor:help;' title='To search using a British National Grid reference, just type it in the search box and hit the button! You can use 2, 4, 6, 8 or 10-digit grid refs with the 2-letter prefix but no spaces in the number (e.g. SU12344225) or absolute grid refs with a comma but no prefix (e.g. 439668,1175316).'>Grid Ref</span>"; } if (window.pnlOpen === false) { $(".leaflet-control-toolbar,.groundspeak-control-findmylocation,.leaflet-control-scale,.gme-left").css("left", "30px"); } if (gmeConfig.env.storage) { if (localStorage.GME_cache) { try { GME_displayPoints(JSON.parse(b64decode(localStorage.GME_cache)), GME_control._map, "clickthru"); delete localStorage.GME_cache; } catch (e) { console.error("GME Can't pop cache: " + e); } } } } setEnv(); }, loadSeek: function () { function load() { function goGR(e) { if (e.type === "click" || (e.type === "keypress" && (e.which || e.keyCode) === 13)) { e.preventDefault(); e.stopImmediatePropagation(); that.seekGR($.trim($("#grRef").val())); return false; } } function goCoords(e) { var c, coords; if (e.type === "click" || (e.type === "keypress" && (e.which || e.keyCode) === 13)) { e.preventDefault(); e.stopImmediatePropagation(); c = document.getElementById("gme_coords").value; if (c) { coords = parseCoords(c); if (coords) { that.seekByLatLng(coords); return false; } } } } function goGoogle(e) { var q = document.getElementById("gme_google").value.trim(), url; e.preventDefault(); e.stopImmediatePropagation(); if (q) { url = "https://www.google.co.uk/search?q=allintitle%3A" + encodeURIComponent(q) + "+site%3Awww.geocaching.com%2Fgeocache%2F+OR+site%3Awww.geocaching.com%2Fseek%2Fcache_details.aspx"; window.open(url, "_blank"); } return false; } $("#grRef").keypress(goGR); $("#grSub").click(goGR); $("#gme_coords").keypress(goCoords); $("#gme_coords_sub").click(goCoords); $("#GME_hereSub").click(that.seekHere); $("#GME_googleSub").click(goGoogle); } setEnv(); }, loadType: function () { function load() { window.GME_control = new L.GME_Widget(); GME_control._layerControl = GME_get_layerControl(); } function reload() { $($(".leaflet-control-layers")[0]).remove(); try { GME_control.removeFrom(map); } catch (e) { } GME_control.addTo(map); GME_control._layerControl.addTo(map); if (gmeConfig.env.dragdrop) { map.addControl(new L.GME_dropHandler()); } setTimeout(function () { map.eachLayer(function (layer) { if (layer instanceof L.TileLayer) { map.removeLayer(layer); } }); GME_control._layerControl.setDefault(); }, 1000); } window.setTimeout(setEnv, 3000); }, loadTrack: function () { function load() { var caches, coords, i, name; function getLogPoints(layer) { if (layer._latlng && layer._popup && layer._popup._content && ~layer._popup._content.indexOf(name)) { coords = layer._latlng; $(caches[i].parentNode.parentNode.children[0]).append("<br/><a href='#GME_map_anchor'><img src='https://www.geocaching.com/images/silk/map.png' width='16' height='16' alt='Map' class='gme-action' data-gme-ref='" + coords.lat + "," + coords.lng + "' title='Centre map on this geocache'/></a> #" + layer._icon.innerHTML); return; } } GME_load_map(map); map.addControl(new L.GME_Widget()); $("#ctl00_ContentBody_lbHeading").append("<a id=\'GME_map_anchor\'></a>"); caches = $(".TrackableLogTable a[href*=cache_details]"); for (i = caches.length - 1; i >= 0; i--) { name = caches[i].textContent.trim(); map.eachLayer(getLogPoints, this); } $(".TrackableLogTable").on("click", ".gme-action", function (e) { map.panTo(L.latLng(this.getAttribute("data-gme-ref").split(","))); }); } setEnv(); }, map: function () { var bounds_CI, bounds_DE, bounds_GB, bounds_IE, bounds_NI, icons = { marker: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAZCAYAAADuWXTMAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAOQSURBVDiNhdRPSCNXHAfw7xs1mc5MjP9itE5ISjVGQrGxKgu6FIzx1CCskKunsj1YKN68tRdPBQ89hdKLl8JmLRR6sGzAPXRB0VVML2Y3kpDBJkHNv3EmTZjx14tZdM3aHzx4vPf78OP9Hu8xIsJNMABfxuPx7xKJxMT5%2BXn3xcWF0NfXpzscjtL4%2BPjrYDD4I4BXTQAiAhF1xWKxg%2BHhYRoYGCCfz0d%2Bv58CgQD5%2FX7y%2BXw0ODhIY2NjFIvFdonITkRgRMQ2NjZO19fXPxkdHUVPTw8AQFVVqKoKm80Gm80GACgWi0gmk1hbW0uvrq5%2BynZ2dlaWlpZ%2BmpychNVqRalUwv7%2B%2FhUR%2FQPgbwCfMcY%2Bnpqakrq7u1Gv13FwcICtra1v2fLy8sHe3t4XIyMjKBQKdHh4eG4YxhMienc2xthMe3v7bxMTEw6n08lSqRSmp6dfc7lcThZFEYZh4Pj4%2BMowjMe34U1fXhmG8fhmH4IgIJfLyVw%2Bn%2B8SBAGKopBhGDEieoMWQURvDMOIKYpCgiCgUCjYuUKhYBEEAaqq1k3TfNEKNsM0zReqqtZvsJUTBKHRaDSgqqoJ4O1DGMBbVVXNRqMBnucbnNvtzpbLZYii2AZA%2Fh8si6LYVqlU4Ha7FW5oaGi3VqtBkiQrx3GPHpIcxz2SJMlaq9Ugy%2FIu19nZua3rOmRZZhaLZYUx1tcKMsb6LBbLiizLTNd1SJL0J5fJZLaq1eo1Ywxer1fkef4lY2zoPTjE8%2FxLr9crchyHSqVynUwmnzMiwsLCwmE%2Bnw94PB4oikInJye1tra2PzRN%2B0sUxVnTNL%2Fy%2BXwfuVwulslk0N%2FffxyPxz9vBwCXy%2FV9IpH43ePxwOVyMYfDIVxeXkY0TYuIooje3l7wPA8AuLi4QCAQ%2BAEAWPNJut3ufz0ej9Vut3%2BwYdVqFaenp3VFUXgA4Jobs7Ozz3K53IP3lMvlMDMz8%2Fxd95sTSZK%2BKZfLpqZpLaGu6ygWi6bFYnl6D0ejUX1%2Bfv7XbDbbEmezWczNzT3b3NzU7mEAsNlsTyuVinl1dXUHapqGUqlkWq3Wr2%2Bv38HRaFQPh8M%2Fp1KpOziVSiEcDv9yuypwq9u3IxAIlBljdlmWcXZ2huvr68rR0VHX%2B3ncPQkgFAotptNpVKtVpNNphEKhxVZ5zd%2Fz3ohEItsdHR0UiUS2P5TTsjIAOJ3OxWAwmHQ6na2rAvgPIb3JdHxMgbEAAAAASUVORK5CYII%3D', tick: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAXCAYAAAAYyi9XAAAALHRFWHRDcmVhdGlvbiBUaW1lAFRodSAxNyBNYXkgMjAxMiAyMjoxMjo1MiAtMDAwMP%2F5zBkAAAAHdElNRQfcBREVNTVnAq5wAAAACXBIWXMAAAsSAAALEgHS3X78AAAABGdBTUEAALGPC%2FxhBQAAAnRJREFUeNq9lMlrFEEUh19VdU3P1j0koujFmYzE4B8gCaIXTx6SgODFBUTw4lkv4kHxKuLFs4JHEZQkF0GM%2B4KIATHE4DATBMmiSetktq7NVxM7DB4n03nQdHdtv%2FreRmAbrFAo%2BM4e55477Oad7RAkaTKmq%2FqYrMgE3Q464pJL7kGXAQMRu6ClAwEHUodSjlpUzVhdGtElR5IU6QC%2FF2IltHSEkP3%2BOZ833zQFSZKnsRFGdJnxjOPsciD8GoZA4X5shG06Rga9k14inAtxANbkgvwci%2BAm3WiG830c6k%2Fqkqboq0qlUo3FpZ10%2Bo%2BG1kxLgoRJO9dzwn90F%2F2zvsMHuY0d6HXdkEvyWSyClo56tJA9kXUxbtB43tCEk3nTMMt2vmuXIkkaXz4%2BSxgb00nnnfZSbDcDFIHmBywHTqZwjexaEA9m2Ixv6po%2BjkRvi35xAmP1kqTI4TbdeDZp14mSAPldShR%2BHO3tShBvq4peccY0zXm2k42xPDuCBwuMFfPObNBZw2SxrxVs3HNbErSGh9xl%2FWxEr%2BlT%2Fdf6d%2FA8B13Xmu%2Fl7bwwytj4CZqmL0qVUjXa13XSIGWoVtVlY8xscCOwbQsSQwmKbm3Pqx%2FKEiq82GTnvi1lKYoumqq5IMqiGtwOlBFmcy6cbXeXmvqlXvdM0Fr5W%2FkdGLi6%2FmBd1x7VNgZR13YXkiCf8G%2Bxp4LWMFnu0CSdCG4FYbvQA%2BwuH1sS4zgZlUxkrBeCQRCInJt7DxxGxbzIsT7GalO1lvltrgSrwXLnWtILwcgGhgaOYpE%2FpH3Uw5L5olbUMBLWe%2B7SyEzLTOOJ19VP1cLmPf2%2FmLW%2FylgqETpxl%2BsAAAAASUVORK5CYII%3D' }, wptTypes = [[/Geocache/i, "2"], [/Traditional Cache/i, "2"], [/Multi-cache/i, "3"], [/Virtual Cache/i, "4"], [/Letterbox Hybrid/i, "5"], [/Event Cache/i, "6"], [/Unknown cache/i, "8"], [/Webcam Cache/i, "11"], [/Cache In Trash Out Event/i, "13"], [/Wherigo Cache/i, "1858"], [/Locationless \(Reverse\) Cache/i, "12"], [/Mega-Event Cache/i, "453"], [/GPS Adventures Exhibit/i, "1304"], [/Groundspeak Block Party/i, "4738"], [/Groundspeak HQ/i, "3773"], [/Groundspeak Lost and Found Celebration/i, "3774"], [/Lost and Found Event Cache/i, "3653"], [/Project APE Cache/i, "9"], [/Earthcache/i, "137"], [/Question to Answer/i, "218"], [/Parking Area/i, "217"], [/Stages of a Multicache/i, "219"], [/Final Location/i, "220"], [/Trailhead/i, "221"], [/Reference Point/i, "452"]], polylineObj = { initialize: function (pts, ops) { L.Polyline.prototype.initialize.call(this, pts, ops); this._length = 0; this._markers = L.layerGroup(); this._updateMarkers(); }, addLatLng: function (pt) { L.Polyline.prototype.addLatLng.call(this, pt); var len = this._latlngs.length; this._addMarker(pt, len); if (len > 1) { this._length += this._latlngs[len - 2].distanceTo(this._latlngs[len - 1]); this.fire("gme-length", { length: this._length }); } return this; }, getData: function () { return ((typeof window.btoa === "function") ? "data:application/xml-gpx;base64," : "data:application/xml-gpx,") + b64encode(this.getGPX()); }, getGPX: function () { var name = $(".CommonUsername").attr("title"), author = name ? (name + '</name>\r\n\t\t\t<link href="https://www.geocaching.com/profile/?u=' + name + '"><text>' + name + '\'s profile</text></link>\r\n') : "Geocaching.com user</name>\r\n", date = !!Date.prototype.toISOString ? ["\t\t<time>", new Date().toISOString(), "</time>\r\n"].join("") : "", i, l, gpx = ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<gpx creator=\"Geocaching Map Enhancements v", that.getVersion(), "\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" version=\"1.1\" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\" xmlns=\"http://www.topografix.com/GPX/1/1\">\r\n\t<metadata>\r\n\t\t<name>GME Export</name>\r\n\t\t<desc>Route file exported from Geocaching.com using Geocaching Map Enhancements.</desc>\r\n\t\t<author>\r\n\t\t\t<name>", author, "\t\t</author>\r\n", date, "\t</metadata>\r\n"].join(""); for (i = 0, l = this._latlngs.length; i < l; i++) { gpx += ["\t<wpt lat=\"", this._latlngs[i].lat, "\" lon=\"", this._latlngs[i].lng, "\">\r\n\t\t<name>P", i, "</name>\r\n\t\t<type>Waypoint</type>\r\n\t</wpt>\r\n"].join(""); } gpx += "\t<rte>\r\n\t\t<name>GME exported route</name>\r\n\t\t<src>Manual entry</src>\r\n\t\t<number>1</number>\r\n"; for (i = 0, l = this._latlngs.length; i < l; i++) { gpx += ["\t\t<rtept lat=\"", this._latlngs[i].lat, "\" lon=\"", this._latlngs[i].lng, "\">\r\n\t\t\t<name>P", i, "</name>\r\n\t\t\t<type>Waypoint</type>\r\n\t\t</rtept>\r\n"].join(""); } gpx += "\t</rte>\r\n</gpx>"; return gpx; }, getLength: function () { return this._length; }, onAdd: function (map) { map.addLayer(this._markers); this._markers.eachLayer(function (a) { a.dragging.enable(); }); return L.Polyline.prototype.onAdd.call(this, map); }, onRemove: function (map) { map.removeLayer(this._markers); return L.Polyline.prototype.onRemove.call(this, map); }, removePt: function (num) { this.spliceLatLngs(num - 1, 1); this._updateMarkers(); }, setLatLngs: function (pts) { L.Polyline.prototype.setLatLngs.call(this, pts); this._updateMarkers(); return this; }, _addMarker: function (pt, num) { var mark = new L.Marker(pt, { icon: new L.Icon({ draggable: "true", iconUrl: icons.marker, iconSize: new L.Point(15, 25), iconAnchor: new L.Point(8, 25) }), zIndexOffset: 99, title: "Route Point #" + num }); mark._routeNum = num; mark.bindPopup(["<p><strong>Route Point #", num, "</strong><br/>Centre: ", pt.toUrl(), "<br/><strong>", DMM(pt), "</strong><br/><span style='float:right;'><a class='gme-event' data-gme-action='removeDistMarker' data-gme-layer='", this._leaflet_id, "' data-gme-ref='", num, "'>Clear</a>, <a class='gme-event' data-gme-layer='", this._leaflet_id, "' data-gme-action='clearDist'>Clear All</a>, <a class='gme-event gme-draggable-gpx' data-gme-action='exportDist' data-gme-layer='", this._leaflet_id, "' draggable='true'>GPX</a></span></p>"].join("")); mark.on("dragend", this._moveMarker, this); this._markers.addLayer(mark); if (mark.dragging) { mark.dragging.enable(); } }, _moveMarker: function (e) { this.spliceLatLngs(e.target._routeNum - 1, 1, e.target.getLatLng()); this._updateLength(); }, _updateLength: function () { var i; this._length = 0; for (i = 1; i < this._latlngs.length; i++) { this._length += this._latlngs[i - 1].distanceTo(this._latlngs[i]); } this.fire("gme-length", { length: this._length }); return this._length; }, _updateMarkers: function () { var i; this._markers.clearLayers(); if (this._latlngs.length > 0) { this._addMarker(this._latlngs[0], 1); for (i = 1; i < this._latlngs.length; i++) { this._addMarker(this._latlngs[i], i + 1); } } this._updateLength(); } }, quadkeyLayerObj = { tile2quad: function (x, y, z) { var i, digit, mask, quad = ""; for (i = z; i > 0; i--) { digit = 0; mask = 1 << (i - 1); if ((x & mask) !== 0) { digit += 1; } if ((y & mask) !== 0) { digit += 2; } quad = quad + digit; } return quad; }, getTileUrl: function (tilePoint) { return L.Util.template(this._url, L.extend({ s: this._getSubdomain(tilePoint), q: this.tile2quad(tilePoint.x, tilePoint.y, this._getZoomForUrl()), z: this._getZoomForUrl() }, this.options)); } }, complexLayerObj = { getTileUrl: function (tilePoint) { return L.Util.template(this._url, L.extend({ s4: tilePoint.x % 4 + 4 * (tilePoint.y % 4), x100: this._getZoomForUrl() <= 13 ? "" : Math.floor(tilePoint.x / 100) + "/", z: this._getZoomForUrl(), x: tilePoint.x, y: tilePoint.y }, this.options)); } }; function GME_displayPoints(plist, map, context) { var bounds = new L.LatLngBounds(), i, p, layers = L.featureGroup(), ll, op, PinIcon = L.Icon.extend({ iconSize: new L.Point(20, 23), iconAnchor: new L.Point(10, 23) }); function checkType(t) { var j; for (j = wptTypes.length - 1; j >= 0; j--) { if (t == wptTypes[j][1]) { return wptTypes[j][1]; } } return 452; } for (i = plist.primary.length - 1; i >= 0; i--) { p = plist.primary[i]; ll = L.latLng(p.lat, p.lng); if (context === "listing" || context === "dragdrop" || p.isUserDefined) { layers.addLayer(L.marker(ll, { icon: new PinIcon({ iconUrl: "/images/wpttypes/pins/" + checkType(p.type) + ".png", iconAnchor: L.point(10, 23) }), clickable: false, zIndexOffset: 98, title: p.name + (p.isUserDefined ? " (Corrected coordinates)" : "") })); if (p.isUserDefined) { layers.addLayer(L.marker(ll, { icon: new PinIcon({ iconSize: new L.Point(28, 23), iconAnchor: L.point(10, 23), iconUrl: icons.tick }), clickable: false, zIndexOffset: 99, title: p.name + " (Corrected coordinates)" })); } } else { bounds.extend(ll); } if (p.isUserDefined) { op = L.latLng(p.oldLatLng[0], p.oldLatLng[1]); layers.addLayer(L.polyline([op, ll], { clickable: false, weight: 3 })); if (context === "listing") { layers.addLayer(L.circleMarker(op, { clickable: false, weight: 3, radius: 6 })); } } } for (i = plist.additional.length - 1; i >= 0; i--) { p = plist.additional[i]; ll = L.latLng(p.lat, p.lng); layers.addLayer(L.marker(ll, { icon: new PinIcon({ iconUrl: "/images/wpttypes/pins/" + checkType(p.type) + ".png", iconAnchor: new L.Point(10, 23) }), title: p.name, clickable: false })); } if (plist.routes) { for (i = plist.routes.length - 1; i >= 0; i--) { if (plist.routes[i].points && plist.routes[i].points.length > 0) { layers.addLayer(L.polyline(plist.routes[i].points)); } } } bounds.extend(layers.getBounds()); if (bounds.isValid()) { switch (context) { case "listing": map.fitBounds(bounds, { padding: [10, 10], maxZoom: 15 }); break; case "clickthru": if (map.getBoundsZoom(bounds) > 15) { map.panTo(bounds.getCenter()).setZoom(15); } else { map.fitBounds(bounds); } break; default: map.panTo(bounds.getCenter()); } } map.addLayer(layers); return bounds; } function genericLayerFn(url, options) { function filterOpts(opts) { /* Remove GME's internal options, so they don't get passed to servers (WMS in particular). */ var opt, filtered = {}, exclude = ["tileUrl", "ignore", "alt"]; for (opt in opts) { if (exclude.indexOf(opt) === -1) { filtered[opt] = options[opt]; } } return filtered; } var filteredOpts = filterOpts(options); if (typeof url === "string") { return (/\{q\}/).test(url) ? (new L.GME_QuadkeyLayer(url, filteredOpts)) : ((/\{s4\}|\{x100\}/).test(url) ? (new L.GME_complexLayer(url, filteredOpts)) : ((/\{x\}/).test(url) ? (new L.TileLayer(url, filteredOpts)) : (new L.TileLayer.WMS(url, filteredOpts)))); } console.error("GME: Bad map source: " + JSON.stringify(options)); return undefined; } function setBrightness(e) { var brightness = this.brightness || that.parameters.brightness; if (brightness < 1) { $(".leaflet-container").css("backgroundColor", "#000"); } else { $(".leaflet-container").css("backgroundColor", "#ddd"); } if (e.layer._url && /^http/.test(e.layer._url) && e.layer.options && !e.layer.options.overlay) { e.layer.setOpacity(brightness); } } function switchLayer(e) { var layer = e.layer; if (layer.options && layer.options.tileUrl && !layer.options.overlay) { this.layersMaxZoom = layer.options.maxZoom; this.layersMinZoom = layer.options.minZoom; if (isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { this._zoomBoundLayers[L.stamp(layer)] = layer; this._updateZoomLevels(); } if (this.getZoom() > this.layersMaxZoom) { this.setZoom(this.layersMaxZoom); } if (this.getZoom() < this.layersMinZoom) { this.setZoom(this.layersMinZoom); } this.brightness = e.layer.options.brightness || that.parameters.brightness; } } function GME_get_layerControl(map) { var maps = {}, overlays = {}, allMaps = that.parameters.maps, apiKeys = that.parameters.apiKeys, baseMaps, control, i, layer, src; for (baseMaps = 0, i = 0; i < allMaps.length; i++) { src = allMaps[i]; if (!src.ignore) { let tileUrl = src.tileUrl; if (apiKeys && src.apiKey && src.apiKeyQuery) { const apiKey = apiKeys[src.apiKey]; if (apiKey) { var replacedTemplate = src.apiKeyQuery.replace(/\{apikey\}/, apiKey); const url = new URL(tileUrl); const search = url.search; url.search = search ? `${search}&${replacedTemplate}` : `?${replacedTemplate}`; tileUrl = decodeURI(url.href); } } layer = L.GME_genericLayer(tileUrl, src); if (layer) { if (src.overlay) { overlays[src.alt] = layer; if (src.alt === that.parameters.defaultHillShade) { layer.default = true; } } else { if (src.alt === that.parameters.defaultMap) { layer.default = true; } maps[src.alt] = layer; baseMaps++; } } } } if (baseMaps > 0) { // Only return a new control if we have some basemaps. control = L.control.layers(maps, overlays); control.setDefault = function () { var defLayer, defOverlay, j; for (j in this._layers) { if (this._layers.hasOwnProperty(j) && this._layers[j].layer.default) { if (!this._layers[j].overlay) { defLayer = j; } else { defOverlay = j; } } } if (!defLayer) { defLayer = Object.keys(this._layers)[0]; } if (this._map && defLayer !== undefined) { this._map.addLayer(this._layers[defLayer].layer, true); } if (this._map && defOverlay !== undefined) { this._map.addLayer(this._layers[defOverlay].layer, true); } return defLayer; }; } return control; } function GME_load_map(map) { var control = GME_get_layerControl(), layer; map.on("layeradd", switchLayer); if (document.createElement("div").style.opacity !== undefined) { map.on("layeradd", setBrightness); } /* If we're adding our own map selector control, we need to manually remove any pre-existing map layers. Otherwise, they persist in the background underneath * the layers provided by GME. We check for the _url or _google attribute to distinguish map layers from other Leaflet layers like controls or popups */ if (control) { if (gmeConfig.env.page === "maps" || gmeConfig.env.page === "track" || gmeConfig.env.page === "hide" || gmeConfig.env.page === "hide") { $($(".leaflet-control-layers")[0]).remove(); for (layer in map._layers) { if (map._layers[layer] instanceof L.TileLayer) { if (window.MapSettings !== undefined && MapSettings.MapLayers !== undefined && MapSettings.MapLayers.Geocache === map._layers[layer]) { // Leave geocache layer in place } else { map.removeLayer(map._layers[layer]); } } } } map.addControl(control); control.setDefault(); } } }, osgb: function () { function OSGridToLatLng(E, N) { var a = 6377563.396, b = 6356256.910, F0 = 0.9996012717, lat0 = 49 * Math.PI / 180, lon0 = -2 * Math.PI / 180, N0 = -100000, E0 = 400000, e2 = 1 - (b * b) / (a * a), n = (a - b) / (a + b), n2 = n * n, n3 = n * n2, lat = lat0, lon, M = 0, Ma, Mb, Mc, Md, cosLat, sinLat, nu, nu3, nu5, nu7, rho, eta2, tanLat, tan2lat, tan4lat, tan6lat, secLat, VII, VIII, IX, X, XI, XII, XIIA, dE, dE2, dE3, dE4, dE5, dE6, dE7, tx, ty, tz, rx, ry, rz, s1, sinPhi, cosPhi, sinLambda, cosLambda, eSq, nu2, x1, y1, z1, x2, y2, z2, p, phi, phiP, precision, lambda; do { lat = (N - N0 - M) / (a * F0) + lat; Ma = (1 + n + (5 / 4) * n2 + (5 / 4) * n3) * (lat - lat0); Mb = (3 * n + 3 * n * n + (21 / 8) * n3) * Math.sin(lat - lat0) * Math.cos(lat + lat0); Mc = ((15 / 8) * n2 + (15 / 8) * n3) * Math.sin(2 * (lat - lat0)) * Math.cos(2 * (lat + lat0)); Md = (35 / 24) * n3 * Math.sin(3 * (lat - lat0)) * Math.cos(3 * (lat + lat0)); M = b * F0 * (Ma - Mb + Mc - Md); } while (N - N0 - M >= 0.01); cosLat = Math.cos(lat); sinLat = Math.sin(lat); nu = a * F0 / Math.sqrt(1 - e2 * sinLat * sinLat); rho = a * F0 * (1 - e2) / Math.pow(1 - e2 * sinLat * sinLat, 1.5); eta2 = nu / rho - 1; tanLat = Math.tan(lat); tan2lat = tanLat * tanLat; tan4lat = tan2lat * tan2lat; tan6lat = tan4lat * tan2lat; secLat = 1 / cosLat; nu3 = nu * nu * nu; nu5 = nu3 * nu * nu; nu7 = nu5 * nu * nu; VII = tanLat / (2 * rho * nu); VIII = tanLat / (24 * rho * nu3) * (5 + 3 * tan2lat + eta2 - 9 * tan2lat * eta2); IX = tanLat / (720 * rho * nu5) * (61 + 90 * tan2lat + 45 * tan4lat); X = secLat / nu; XI = secLat / (6 * nu3) * (nu / rho + 2 * tan2lat); XII = secLat / (120 * nu5) * (5 + 28 * tan2lat + 24 * tan4lat); XIIA = secLat / (5040 * nu7) * (61 + 662 * tan2lat + 1320 * tan4lat + 720 * tan6lat); dE = (E - E0); dE2 = dE * dE; dE3 = dE2 * dE; dE4 = dE2 * dE2; dE5 = dE3 * dE2; dE6 = dE4 * dE2; dE7 = dE5 * dE2; lat = lat - VII * dE2 + VIII * dE4 - IX * dE6; lon = lon0 + X * dE - XI * dE3 + XII * dE5 - XIIA * dE7; tx = 446.448; ty = -125.157; tz = 542.060; rx = 7.2819014902652306237205098174164e-7; ry = 1.1974897923405539041670878328241e-6; rz = 4.0826160086234026020206666559563e-6; s1 = 0.9999795106; sinPhi = Math.sin(lat); cosPhi = Math.cos(lat); sinLambda = Math.sin(lon); cosLambda = Math.cos(lon); eSq = (a * a - b * b) / (a * a); nu2 = a / Math.sqrt(1 - eSq * sinPhi * sinPhi); x1 = nu2 * cosPhi * cosLambda; y1 = nu2 * cosPhi * sinLambda; z1 = (1 - eSq) * nu2 * sinPhi; x2 = tx + x1 * s1 - y1 * rz + z1 * ry; y2 = ty + x1 * rz + y1 * s1 - z1 * rx; z2 = tz - x1 * ry + y1 * rx + z1 * s1; a = 6378137; b = 6356752.3142; eSq = (a * a - b * b) / (a * a); p = Math.sqrt(x2 * x2 + y2 * y2); phi = Math.atan2(z2, p * (1 - eSq)); phiP = 2 * Math.PI; precision = 4 / a; while (Math.abs(phi - phiP) > precision) { nu = a / Math.sqrt(1 - eSq * Math.sin(phi) * Math.sin(phi)); phiP = phi; phi = Math.atan2(z2 + eSq * nu * Math.sin(phi), p); } lambda = Math.atan2(y2, x2); return { lat: phi * 180 / Math.PI, lng: lambda * 180 / Math.PI }; } function gridrefLetToNum(letters, numbers) { letters = letters.toUpperCase(); var e, n, l1 = letters.charCodeAt(0) - "A".charCodeAt(0), l2 = letters.charCodeAt(1) - "A".charCodeAt(0); if (l1 > 7) { l1--; } if (l2 > 7) { l2--; } e = ((l1 - 2) % 5) * 5 + (l2 % 5); n = (19 - Math.floor(l1 / 5) * 5) - Math.floor(l2 / 5); e += numbers.slice(0, numbers.length / 2); n += numbers.slice(numbers.length / 2); switch (numbers.length) { case 2: e += "5000"; n += "5000"; break; case 4: e += "500"; n += "500"; break; case 6: e += "50"; n += "50"; break; case 8: e += "5"; n += "5"; break; } return [e, n]; } function parseGR(searchVal) { var ngr, gr = searchVal.match(/^\s*([hnstHNST][A-Ha-hJ-Zj-z])\s*((?:\d\d){1,5})\s*$/); if (gr) { if (gr.length === 3) { if (2 * Math.floor(gr[2].length / 2) === gr[2].length) { ngr = gridrefLetToNum(gr[1], gr[2]); return OSGridToLatLng(ngr[0], ngr[1]); } } return null; } gr = searchVal.match(/^\s*(\d{3,6})\s*,\s*(\d{4,7})\s*$/); if (gr) { if (gr.length === 3) { return OSGridToLatLng(gr[1], gr[2]); } } return null; } }, seek: function () { this.seekGR = function (searchVal) { if (searchVal.length > 0) { var coords = parseGR(searchVal); if (coords !== null) { that.seekByLatLng(coords); } else { alert("Could not recognise grid reference."); } } }; }, widget: function () { var locationControlObj = { onAdd: function (map) { var el, tracking = false, container = L.DomUtil.create("div", "leaflet-control-toolbar groundspeak-control-findmylocation gme-left"); function located(l) { this.panTo(l.latlng); } function click(e) { L.DomEvent.stopPropagation(e); if (tracking) { map.stopLocate(); map.off("locationfound", located); tracking = false; $(".groundspeak-control-findmylocation-lnk").removeClass("gme-button-active"); $("#GME_loc").attr("title", "Follow My Location"); } else { map.on("locationfound", located); map.locate({ enableHighAccuracy: true, watch: true, timeout: 60000 }); tracking = true; $(".groundspeak-control-findmylocation-lnk").addClass("gme-button-active"); $("#GME_loc").attr("title", "Stop following"); } } function click_once(e) { L.DomEvent.stopPropagation(e); this.locate({ setView: true, maxZoom: this.getZoom(), minZoom: this.getZoom(), enableHighAccuracy: true, timeout: 60000 }); } el = document.createElement("a"); el.id = "GME_loc"; el.title = that.parameters.follow ? "Follow My Location" : "Find My Location"; el.className = "groundspeak-control-findmylocation-lnk"; if (that.parameters.follow) { L.DomEvent.addListener(el, "click", click, map); } else { L.DomEvent.addListener(el, "click", click_once, map); } container.appendChild(el); return container; } }, widgetControlObj = { options: { position: "bottomleft" }, onAdd: function (contextmap) { var elem, container = L.DomUtil.create("div", "leaflet-control-gme"), control = this, html = ""; function onPopup(e) { if (e.layer._container && /leaflet-popup/.test(e.layer._container.className)) { $(e.layer._contentNode).on("click", ".gme-event", contextmap, mapHandler); $(e.layer._contentNode).on("dragstart", ".gme-draggable-gpx", { line: control._dist_line }, dragGPXHandler); } } function offPopup(e) { if (e.layer._container && /leaflet-popup/.test(e.layer._container.className)) { $(e.layer._contentNode).off("click", ".gme-event", contextmap, mapHandler); $(e.layer._contentNode).off("dragstart", ".gme-draggable-gpx", { line: control._dist_line }, dragGPXHandler); } } function mapHandler(e) { var action = this.getAttribute("data-gme-action"), c1 = this.getAttribute("data-gme-coords"), c2, coords, data = this.getAttribute("data-gme-ref"), layer = this.getAttribute("data-gme-layer"); e.stopPropagation(); if (action !== "exportDist") { e.preventDefault(e); } if (c1) { c2 = c1.match(/(-?\d{1,2}(\.\d+)?),(-?\d{1,3}(\.\d+)?)/); if (c2 && c2.length === 5 && validCoords(c2[1], c2[3])) { coords = new L.LatLng(c2[1], c2[3]); } } if (action === "clearDist") { control.clearDist(); } if (action === "clearMarkers") { control.clearMarkers(); } if (action === "dropDist" && coords) { control.dropDist(coords); } if (action === "dropMarker" && coords) { control.dropMarker(coords); } if (action === "exportDist") { control.exportDist(this); } if (action === "getGeograph" && coords) { that.getGeograph(coords); } if (action === "getHeight" && coords) { that.getHeight(coords); } if (action === "getPostcode" && coords) { control.getPostcode(coords); } if (action === "panTo" && coords) { e.data.panTo(coords); } if (action === "removeMarker" && data) { control.removeMarker(data); } if (action === "removeDistMarker" && data) { control.removeDistMarker(data); } if (action === "toggleCaches") { control.toggleCaches(); } $(".leaflet-popup-close-button").each(function () { this.click(); }); } function dragGPXHandler(e) { e.originalEvent.dataTransfer.effectAllowed = "copy"; var plain = e.data.line.getGPX(), data = e.data.line.getData(); e.originalEvent.dataTransfer.setData("application/xml-gpx", plain); e.originalEvent.dataTransfer.setData("text/uri-list", data); e.originalEvent.dataTransfer.setData("DownloadURL", "application/xml-gpx:gme-export.gpx:" + data); e.originalEvent.dataTransfer.setData("text/plain", plain); } function widgetHandler(e) { var action = this.getAttribute("data-gme-action"); e.stopPropagation(); if (action === "panToHome") { control.panToHome(); } if (action === "toggleInfo") { control.toggleTool("info"); } if (action === "toggleRoute") { control.toggleTool("route"); } if (action === "toggleCaches") { control.toggleCaches(); } } this._map = contextmap; this._map.infoMode = false; this._map.routeMode = false; this._markers = L.layerGroup().addTo(contextmap); html = "<button type='button' class=\'GME_info gme-button gme-button-l\' title=\'Enable location info tool\' data-gme-action=\'toggleInfo\'></button>"; html += "<button type='button' class=\'GME_hide gme-button\' title=\'Hide caches\' data-gme-action=\'toggleCaches\'></button>"; html += "<button type='button' class=\'GME_route gme-button\' title=\'Enable route tool\' data-gme-action=\'toggleRoute\'></button>"; if (gmeConfig.env.home) { html += "<button type='button' title=\'Go to home location\' class=\'GME_home gme-button\' data-gme-action=\'panToHome\'></button>"; } if (gmeConfig.parameters.osgbSearch) { $(".GME_search_results").on("click", ".gme-event", contextmap, mapHandler); } if (gmeConfig.env.storage) { html += "<a class=\'GME_config gme-button\' title=\'Configure Geocaching Map Enhancements\' href=\'#GME_config\'></a>"; } container.innerHTML = html; $(container.lastChild).addClass("gme-button-r"); container.innerHTML += "<span class=\'gme-button gme-button-l gme-button-r gme-scale-container\' title=\'Approximate width of the full map view\' style=\'cursor:help;\'>Width: <span class=\'gme-scale\'>-</span></span><span class=\'gme-distance-container gme-button gme-button-r\' title=\'Measured distance\'>Route: <span class=\'gme-distance\'>" + formatDistance(0) + "</span></span>"; contextmap.addControl(new L.GME_ZoomWarning()).on("layeradd", onPopup).on("layerremove", offPopup).on("viewreset", this.updateScale, this); $(container).on("click", ".gme-button", this, widgetHandler); $(window).on("resize", this, (function (context) { var t = { timer: null }; return function () { context.updateScale(context._map, t); }; }(this))); return container; }, clearDist: function () { this._dist_line.off("gme-length"); this._map.removeLayer(this._dist_line); delete this._dist_line; $(".gme-distance-container").removeClass("show"); $(".gme-distance").html(formatDistance(0)); $(".gme-scale-container").addClass("gme-button-r"); }, clearMarkers: function () { this._markers.clearLayers(); }, dropDist: function (ll) { if (!validCoords(ll)) { return; } var dist, formatted; if (this._dist_line === undefined) { this._dist_line = new L.GME_DistLine([ll], { clickable: false }); this._dist_line.on("gme-length", function (e) { $(this._map._container).find(".gme-distance").html(formatDistance(e.length)); }); this._map.addLayer(this._dist_line); $(this._map._container).find(".gme-distance-container").addClass("show"); $(this._map._container).find(".gme-scale-container").removeClass("gme-button-r"); } else { this._dist_line.addLatLng(ll); } }, exportDist: function (e) { if (!this._dist_line) { return; } e.download = "ExportedRoute.gpx"; e.href = "data:application/xml-gpx," + encodeURIComponent(this._dist_line.getGPX()); return false; }, dropMarker: function (ll, rad) { if (!validCoords(ll)) { return; } var circle, defaultRadius = 0.161, group, label = "Marker", m = 1000, r, radius, raw, unit = "km"; if (that.parameters.measure !== "metric") { unit = "miles"; defaultRadius = 0.1; m = 1609.344; } radius = defaultRadius; if (!isNaN(rad)) { radius = rad * 1; } else { raw = window.prompt("Radius in " + unit + " [, label]", defaultRadius).match(/([\d]*\.?[\d]*)\s*,?\s*(.*)/); if (raw) { if (raw.length === 3) { radius = raw[1] * 1; label = raw[2] || label; } else { label = raw; } } } if (radius) { radius *= m; if (isNaN(radius)) { radius = 161; } } else { radius = 161; } circle = new L.Circle(ll, radius, { weight: 2 }); group = new L.LayerGroup([circle, new L.CircleMarker(ll, { weight: 2, radius: 3 })]); this._markers.addLayer(group); r = (radius / m).toFixed(3) + " " + unit; circle.bindPopup("<p><strong>" + label + "</strong><br/>Radius: " + r + "<br/>Centre: decimal " + ll.toUrl() + "<br/><strong>" + DMM(ll) + "</strong><br/><span style='float:right;'><a class='gme-event' data-gme-action='removeMarker' data-gme-ref='" + group._leaflet_id + "'>Clear</a>, <a class='gme-event' data-gme-action='clearMarkers'>Clear All</a></span></p>"); }, getPostcode: function (coords) { var that = this, callprefix = "GME_postcode_callback", call; function makeCallback(callname) { callbackCount++; return function (json) { var m; if (json !== undefined && json.status === 200) { if (json.result && json.result.length > 0) { m = "<p>" + json.result[0].postcode + (json.result[0].parish ? (", " + json.result[0].parish) : "") + (json.result[0].admin_ward ? (", " + json.result[0].admin_ward) : "") + "</p>"; } else { m = "<p>No postcode found for this location.<br />Is it within 500m of an occupied building?</p>"; } } else { m = "<p>Error fetching data from postcodes.io</p>"; } if (json.result && !isNaN(json.result[0].latitude) && !isNaN(json.result[0].longitude)) { L.popup().setLatLng({ lat: json.result[0].latitude, lng: json.result[0].longitude }).setContent(m).openOn(that._map); } else { $.fancybox(m); } $("#" + callname).remove(); if (window[callname] !== undefined) { delete window[callname]; } }; } if (validCoords(coords)) { call = callprefix + callbackCount; window[call] = makeCallback(call); JSONP("https://api.postcodes.io/postcodes/lon/" + coords.lng + "/lat/" + coords.lat + "?radius=500&limit=1&callback=" + call, call); } else { console.error("GME: Bad coordinates to getPostcode"); } }, panToHome: function () { if (gmeConfig.env.home) { this._map.panTo(gmeConfig.env.home); return true; } return false; }, removeDistMarker: function (mark) { if (this._dist_line) { this._dist_line.removePt(mark); $(this._map._container).find(".gme-distance").html(formatDistance(this._dist_line.getLength())); } }, removeMarker: function (mark) { this._markers.removeLayer(this._markers._layers[mark]); }, removeMarkers: function (mark) { this._markers.clearLayer(this._markers._layers[mark]); }, showInfo: function (e) { var control = this, popupContent = "<p>", popup = new L.Popup(), i; for (i = 0; i < this.tools.length; i++) { if (this.tools[i].isValid(e.latlng, control._map.getZoom())) { popupContent += this.tools[i].getHTML(e.latlng, control._map.getZoom(), control._map) + " "; } } popupContent += "</p>"; popup.setLatLng(e.latlng); popup.setContent(popupContent); control._map.addLayer(popup); }, tools: [ { name: "Coords", getHTML: function (coords, zoom, map) { var ll = coords.toUrl(); return "<strong>" + DMM(coords) + "</strong><br/>Dec: <a href='geo:" + ll + "?z=" + zoom + "'>" + ll + "</a></br>"; }, isValid: function (coords, zoom) { return true; } }, { name: "List caches", getHTML: function (coords, zoom, map) { return "<a title='List " + (that.parameters.filterFinds ? "unfound " : "") + "caches near point' href='https://www.geocaching.com/seek/nearest.aspx?lat=" + coords.lat + "&lng=" + coords.lng + (that.parameters.filterFinds ? "&f=1" : "") + "' target='_blank' rel='noopener noreferrer'>List caches</a>"; }, isValid: function (coords, zoom) { return true; } }, { name: "Geograph", action: "getGeograph", getHTML: function (coords, zoom, map) { return "<a href='#' title='Show Geograph images near this point' class='gme-event' data-gme-action='getGeograph' data-gme-coords='" + coords.toUrl() + "'>Geograph</a>"; }, isValid: function (coords, zoom) { return bounds_GB.contains(coords) || bounds_DE.contains(coords) || bounds_IE.contains(coords) || bounds_CI.contains(coords); } }, { name: "Directions", getHTML: function (coords, zoom, map) { return "<a title='Launch Google Directions from home to this point' target='_blank' rel='noopener noreferrer' href='https://www.google.com/maps/dir/?api=1&origin=" + gmeConfig.env.home.toUrl() + "&destination=" + coords.toUrl() + "'>Directions</a>"; }, isValid: function (coords, zoom) { return !!gmeConfig.env.home; } }, { name: "Wikimapia", getHTML: function (coords, zoom, map) { var centre = map.getCenter(); return "<a title='Go to wikimapia' target='_blank' rel='noopener noreferrer' href='http://wikimapia.org/#lat=" + centre.lat + "&lon=" + centre.lng + "&z=" + zoom + "'>Wikimapia</a>"; }, isValid: function (coords, zoom) { return true; } }, { name: "Marker", getHTML: function (coords, zoom, map) { return "<a title='Drop route marker onto map' href='#' class='gme-event' data-gme-action='dropMarker' data-gme-coords='" + coords.toUrl() + "'>Marker</a>"; }, isValid: function (coords, zoom) { return true; } }, { name: "MAGIC", getHTML: function (coords, zoom, map) { var b = map.getBounds(); return "<a title='Show MAGIC map of environmentally sensitive areas' target='_blank' rel='noopener noreferrer' href='http://magic.defra.gov.uk/MagicMap.aspx?srs=WGS84&startscale=" + (Math.cos(map.getCenter().lat * L.LatLng.DEG_TO_RAD) * 684090188 * Math.abs(b.getSouthWest().lng - b.getSouthEast().lng)) / map.getSize().x + "&layers=LandBasedSchemes,12,24:HabitatsAndSpecies,38:Designations,6,10,13,16,34,37,40,72,94&box=" + b.toBBoxString().replace(/,/g, ":") + "'>MAGIC</a>"; }, isValid: function (coords, zoom) { return that.isInUK(coords); } }, { name: "Postcode", getHTML: function (coords, zoom, map) { return "<a title='Fetch location data from postcodes.io' href='#' class='gme-event' data-gme-action='getPostcode' data-gme-coords='" + coords.toUrl() + "'>Postcode</a>"; }, isValid: function (coords, zoom) { return that.isInUK(coords); } }, { name: "Height", getHTML: function (coords, zoom, map) { return "<a title='Height of point above sea level' href='#' class='gme-event' data-gme-action='getHeight' data-gme-coords='" + coords.toUrl() + "'>Height</a>"; }, isValid: function (coords, zoom) { return (coords.lat > -65 && coords.lat < 83); } }, { name: "StreetView", getHTML: function (coords, zoom, map) { return "<a title='Launch Google Streetview' target='_blank' rel='noopener noreferrer' href='https://www.google.com/maps/@?api=1&map_action=pano&viewpoint=" + coords.toUrl() + "'>Streetview</a>"; }, isValid: function (coords, zoom) { return true; } }, { name: "MapApp", getHTML: function (coords, zoom, map) { /* Open Bing Maps app if available, otherwise use a cross-platform Google Maps URI */ return "<a title='Launch Bing Maps' href='bingmaps:?cp=" + coords.lat + "~" + coords.lng + "' target='_blank' rel='noopener noreferrer'><a title='Launch Google Maps' href='https://www.google.com/maps/@?api=1&map_action=map¢er=" + coords.toUrl() + "&zoom=" + zoom + "' target='_blank' rel='noopener noreferrer'>Maps</a></a>"; }, isValid: function (coords, zoom) { return true; } } ], showRoute: function (e) { L.DomEvent.stopPropagation(e); this.dropDist(e.latlng); }, toggleCaches: function () { if (window.MapSettings && MapSettings.MapLayers && MapSettings.MapLayers.AddGeocacheLayer && MapSettings.MapLayers.RemoveGeocacheLayer) { if (MapSettings.MapLayers.Geocache) { MapSettings.MapLayers.RemoveGeocacheLayer(); $(".GME_hide").addClass("gme-button-active").attr("title", "Show caches"); } else { MapSettings.MapLayers.AddGeocacheLayer(); $(".GME_hide").removeClass("gme-button-active").attr("title", "Hide caches"); } } }, toggleTool: function (mode) { var that = this, widgets = { info: { on: function () { that._map.on("click contextmenu", that.showInfo, that); $("#map_canvas").addClass("gme-xhair"); $(".GME_info").addClass("gme-button-active").attr("title", "Disable location info tool"); }, off: function () { that._map.off("click contextmenu", that.showInfo, that); $("#map_canvas").removeClass("gme-xhair"); $(".GME_info").removeClass("gme-button-active").attr("title", "Enable location info tool"); } }, none: { on: function () { }, off: function () { } }, route: { on: function () { that._map.on("click contextmenu", that.showRoute, that); $("#map_canvas").addClass("gme-xhair"); $(".GME_route").addClass("gme-button-active").attr("title", "Disable route tool"); }, off: function () { that._map.off("click contextmenu", that.showRoute, that); $("#map_canvas").removeClass("gme-xhair"); $(".GME_route").removeClass("gme-button-active").attr("title", "Enable route tool"); } } }; if (!widgets[mode]) { return; } widgets[this._clickMode].off(); if (mode == this._clickMode) { this._clickMode = "none"; } else { this._clickMode = mode; widgets[mode].on(); } }, search: function (searchVal) { var gr, m, call, callbackPrefix = "GME_search_callback", coords = false, marker, that = this; function searchGS(searchVal) { $(".GME_search_results").addClass("hidden"); $.getJSON("/api/geocode", { q: searchVal }, function (a) { if (a.status === "success") { that._map.panTo(new L.LatLng(a.data.lat, a.data.lng)); } else { alert("Sorry, no results found for " + searchVal); } }); } function makeCallback2(callname) { callbackCount++; return function (json) { var i, j; if (json.geonames && json.geonames.length > 0) { $(".GME_search_list").empty(); for (i = 0, j = json.geonames.length; i < j; i++) { $(".GME_search_list").append("<li><a class='gme-event' data-gme-action='panTo' data-gme-coords='" + json.geonames[i].lat + "," + json.geonames[i].lng + "'>" + json.geonames[i].name + ", " + json.geonames[i].adminName1 + ", " + json.geonames[i].countryCode + "</a></li>"); } $(".GME_search_results").removeClass("hidden"); $(".GME_search_results.ui-collapsible-collapsed a.ui-collapsible-heading-toggle").click(); $(".GME_link_GSSearch").off("click"); $(".GME_link_GSSearch").click(function () { searchGS(searchVal); }); that._map.panTo(new L.LatLng(json.geonames[0].lat, json.geonames[0].lng)); } else { searchGS(searchVal); } $("#" + callname).remove(); if (window[callname] !== undefined) { delete window[callname]; } }; } function makeCallback1(callname) { callbackCount++; return function (json) { var newCall = callbackPrefix + callbackCount; if (json.countryCode) { window[newCall] = makeCallback2(newCall); JSONP("http://api.geonames.org/searchJSON?q=" + encodeURIComponent(searchVal) + "&countryBias=" + json.countryCode + "&maxRows=10&username=gme&callback=" + newCall, newCall); } else { searchGS(searchVal); } $("#" + callname).remove(); if (window[callname] !== undefined) { delete window[callname]; } }; } if (searchVal.length > 0) { m = searchVal.match(/^\s*(?:z|zoom)\s*(\d\d?)\s*$/i); if (m && m.length === 2) { this._map.setZoom(m[1]); return false; } m = searchVal.match(/^\s*(?:p|plot)(?:\s+r\s?(\d*\.?\d*))?\s+(.*)/); if (m && m.length === 3) { coords = parseCoords(m[2]); if (coords) { if (!isNaN(m[1])) { this.dropMarker(L.latLng(coords.lat, coords.lng), m[1]); } else { L.marker(coords, { icon: L.divIcon() }).addTo(this._map).bindPopup(DMM(coords)); } this._map.panTo(coords); return false; } } m = searchVal.match(/^\s*(GC[0123456789ABCDEFGHJKMNOPQRSTVWXYZ]{1,7})\s*$/i); if (m && m.length === 2) { if (gmeConfig.env.loggedin) { this.panToGC(m[1]); $(".GME_search_results").addClass("hidden"); return false; } alert("You must be logged in to allow GME to get cache coordinates"); return false; } gr = parseGR(searchVal); if (gr) { this._map.panTo(new L.LatLng(gr.lat, gr.lng)); } else { call = callbackPrefix + callbackCount; window[call] = makeCallback1(call); JSONP("http://api.geonames.org/countryCodeJSON?lat=" + this._map.getCenter().lat + "&lng=" + this._map.getCenter().lng + "&username=gme&radius=100&callback=" + call, call); } } return false; }, panToGC: function (gc) { var req = new XMLHttpRequest(), map = this._map || e; req.addEventListener("load", function (e) { var r = req.responseText, k = r.indexOf("mapLatLng = {"), c; if (req.status < 400) { try { c = JSON.parse(r.substring(k + 12, r.indexOf("}", k) + 1)); map.panTo(new L.LatLng(c.lat, c.lng)); } catch (e) { console.warn("GME: Couldn't extract cache coordinates:" + e + "\nReceived " + r.length + " bytes, coords at " + k); } } else { if (req.status === 404) { alert("Sorry, cache " + gc + " doesn't seem to exist."); } console.warn("GME: error retrieving cache page to find coords for " + gc + ": " + req.statusText); } }); req.open("GET", "https://www.geocaching.com/geocache/" + gc); req.send(); }, updateScale: function (e, timer) { var map = this._map || e; if (!map.getBounds) { console.warn("updateScale didn't have working map"); return; } function updateMap() { var bound = map.getBounds(); var width = formatDistance(Math.cos(map.getCenter().lat * L.LatLng.DEG_TO_RAD) * 111319.49079327358 * Math.abs(bound.getSouthWest().lng - bound.getSouthEast().lng)); $(this._container).find(".gme-scale").html(width); } if (timer !== undefined) { window.clearTimeout(timer.timer); timer.timer = window.setTimeout(function () { map.whenReady(updateMap); return false; }, 200); } else { map.whenReady(updateMap); } }, _clickMode: "none" }, zoomWarningObj = { options: { position: "topleft" }, onAdd: function (map) { var c = L.DomUtil.create("div", "leaflet-control-zoomwarning gme-left"); function checkZoom() { if (map.getZoom() > map.layersMaxZoom) { map.setZoom(map.layersMaxZoom); } if (map.getZoom() < map.layersMinZoom) { map.setZoom(map.layersMinZoom); } if (this.getZoom() > 18) { c.style.display = "block"; if (typeof amplify === "object" && typeof amplify.store === "function" && amplify.store("ShowPanel") === false) { $(".leaflet-control-zoomwarning").css("left", "30px"); } } else { c.style.display = "none"; } } c.innerHTML = "<a class='gme-button gme-button-l gme-button-r' title='Caches not visible at this zoom level'></a>"; c.style.display = (map.getZoom() > 18) ? "block" : "none"; map.on("zoomend", checkZoom); return c; } }; function GME_load_widget(map) { var control = new L.GME_Widget().addTo(map); $(control._container).addClass("gme-left").css("top", "20px"); $(".groundspeak-control-findmylocation").remove(); if (L.GME_FollowMyLocationControl) { map.addControl(new L.GME_FollowMyLocationControl()); } $(".leaflet-control-scale").addClass("gme-control-scale"); $("a.ToggleSidebar").unbind(); $("a.ToggleSidebar").click(function (a) { a.preventDefault(); if (window.pnlOpen) { window.pnlOpen = false; $(".Sidebar").animate({ left: "-355px" }, 500); $(".leaflet-control-zoom,.leaflet-control-toolbar,.leaflet-control-scale,.gme-left").animate({ left: "30px" }, 500); $(this).removeClass("Open"); } else { window.pnlOpen = true; $(".Sidebar").animate({ left: "0" }, 500); $(".leaflet-control-zoom,.leaflet-control-toolbar,.leaflet-control-scale,.gme-left").animate({ left: "385px" }, 500); $(this).addClass("Open"); } if (typeof amplify === "object" && typeof amplify.store === "function") { amplify.store("ShowPanel", window.pnlOpen); } return false; }); // Trigger reset to update scale and width controls. map.fireEvent("viewreset"); return control; } }, xhr: function (e) { var node = document.getElementById("gme_jsonp_node"), callback = node.getAttribute("data-gme-callback"), url = node.text, details = { "method": "GET", "url": url, "onload": function (response) { var x = response.responseText, call = x.match(/([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/), s; if (call && call.length === 2 && call[1] === callback) { s = document.getElementById("gme_jsonp_node"); s.setAttribute("data-gme-callback", callback); s.text = x.substring(x.indexOf("(") + 1, x.lastIndexOf(")")); document.dispatchEvent(new Event("GME_XHR_callback")); } else { console.warn("Received: " + x); } } }; if (gmeResources.env.xhr === 'GM4') { // GreaseMonkey 4+ GM.xmlHttpRequest(details); } else { // Other userscript engines setTimeout(function () { GM_xmlhttpRequest(details); }, 0); } } } }, pageTests = [ ["listing", /\/geocache\/GC|\/seek\/cache_details\.aspx|\/seek\/cache_details2\.aspx/], ["maps", /\/map\//], ["hide", /\/hide\/planning\.aspx/], ["type", /\/hide\/typelocation\.aspx/], ["hide", /\/hide\/waypoints\.aspx/], ["seek", /\/seek\/$|\/seek\/default\.aspx/], ["track", /\/track\/map_gm\.aspx/] ], i, target, target2, targets; function buildScript() { var j, script = ""; for (j = 1; j < arguments.length; j++) { if (typeof arguments[j] === "string" && gmeResources.script.hasOwnProperty(arguments[j])) { script += unwrapFunction(gmeResources.script[arguments[j]]); gmeResources.env.init.push(arguments[j]); } } insertScript( 'var GME;\ (function () {\ "use strict";\ function GeocachingMapEnhancements() {\ var gmeConfig = ' + JSON.stringify({ env: gmeResources.env, parameters: gmeResources.parameters }) + ";" + script + '}\ GME = new GeocachingMapEnhancements();\ console.info("Geocaching Map Enhancements v' + gmeResources.parameters.version + ' loaded.");\ }());', arguments[0] ); } function insertCSS(css) { if (typeof css !== "string") { console.warn("GME: insertCSS not called with string: " + typeof css); return; } var style = document.createElement('style'); style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } document.documentElement.firstChild.appendChild(style); } function insertPage(div, src, title, back) { if (div && typeof src === 'string') { var d = document.createElement('div'); d.id = div; d.title = title || ''; d.innerHTML = ['<div><a href="#', back || '', '" title="Close" class="gme-close-dialog">X</a><header>', d.title, '</header><div class="gme-modal-content">', src, '</div></div>'].join(''); d.className = 'gme-modalDialog'; document.documentElement.lastChild.appendChild(d); } else { console.warn("GME: insertPage not called with correct parameters."); } } function insertScript(src, id) { console.log("GME: Inserting script: " + id); // console.debug(src); if (typeof src !== "string") { console.warn("GME: insertScript not called with string."); return; } var s = document.createElement("script"); s.type = "text/javascript"; s.text = src; if (id) { s.id = id; } document.documentElement.firstChild.appendChild(s); document.documentElement.firstChild.removeChild(s); } function unwrapFunction(fn) { var text; if (typeof fn === "function") { text = fn.toString(); return text.slice(text.indexOf("{") + 1, text.lastIndexOf("}")); } return fn; } function xhr(e) { var node = document.getElementById("gme_jsonp_node"), callback = node.getAttribute("data-gme-callback"), url = node.text, details = { "method": "GET", "url": url, "onload": function (response) { var x = response.responseText, call = x.match(/([a-zA-Z_$][0-9a-zA-Z_$]*)\s*\(/), s; if (call && call.length === 2 && call[1] === callback) { s = document.getElementById("gme_jsonp_node"); s.setAttribute("data-gme-callback", callback); s.text = x.substring(x.indexOf("(") + 1, x.lastIndexOf(")")); document.dispatchEvent(new Event("GME_XHR_callback")); } else { console.warn("Received: " + x); } } }; if (gmeResources.env.xhr === 'GM4') { GM.xmlHttpRequest(details); } else { setTimeout(function () { GM_xmlhttpRequest(details); }, 0); } } //don't run on frames or iframes if (window.top !== window.self) { return; } if (!(typeof JSON === 'object' && typeof JSON.parse === 'function')) { console.error("Geocaching Map Enhancements requires a browser with JSON support."); return; } if (document.querySelector("head[data-gme-version]")) { console.error("Aborting: GME already running on page: " + document.location); return; } document.documentElement.firstChild.setAttribute("data-gme-version", gmeResources.parameters.version); for (i = 0; i < pageTests.length; i++) { if (pageTests[i][1].test(document.location.pathname)) { gmeResources.env.page = pageTests[i][0]; break; } } try { if (window.localStorage !== undefined && window.localStorage !== null) { gmeResources.env.storage = true; } } catch (e) { /*Potential security exception*/ console.warn("No localStorage capability - GME cannot set configuration"); } if (gmeResources.env.storage) { var a, b, customJSON, GME_custom, paramsJSON, storedParams; /* List of defunct tileUrls to remove from settings */ var blacklist = [ "https://ecn.t{s}.tiles.virtualearth.net/tiles/r{q}?g=737&productSet=mmOS", "https://ecn.t{s}.tiles.virtualearth.net/tiles/r{q}?g=864&productSet=mmCB", "https://otile{s}-s.mqcdn.com/tiles/1.0.0/osm/{z}/{x}/{y}.jpg", "https://otile{s}-s.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.jpg" ]; try { paramsJSON = localStorage.getItem("GME_parameters"); if (paramsJSON) { try { storedParams = JSON.parse(paramsJSON); if (storedParams.version !== gmeResources.parameters.version) { for (a in gmeResources.parameters) { if (gmeResources.parameters.hasOwnProperty(a)) { if (storedParams[a] === undefined) { storedParams[a] = gmeResources.parameters[a]; } } } alert("Geocaching Map Enhancements has been updated to v" + gmeResources.parameters.version + ". " + gmeResources.parameters.versionMsg); storedParams.version = gmeResources.parameters.version; localStorage.setItem("GME_parameters", JSON.stringify(storedParams)); } if (typeof storedParams.maps === "string") { console.info("GME: Trying to fix corrupted map settings."); storedParams.maps = JSON.parse(storedParams.maps); } gmeResources.parameters = storedParams; } catch (e) { console.warn("GME: Could not parse stored configuration parameters."); } } /* Import old-style custom maps */ customJSON = localStorage.getItem("GME_custom"); if (customJSON) { console.info("GME: Found stored custom settings"); try { GME_custom = JSON.parse(customJSON); if (GME_custom.maps && GME_custom.maps.length > 0) { gmeResources.parameters.maps = gmeResources.parameters.maps.concat(GME_custom.maps); } delete localStorage.GME_custom; } catch (e) { console.warn("GME: Could not parse stored custom maps."); } } /* Remove old-style builtin maps */ if (gmeResources.parameters.includeMaps) { delete gmeResources.parameters.includeMaps; } if (gmeResources.parameters.excludeMaps) { for (a = gmeResources.parameters.excludeMaps.length - 1; a >= 0; a--) { for (b = gmeResources.parameters.maps.length - 1; b >= 0; b--) { if (gmeResources.parameters.maps[b].alt === gmeResources.parameters.excludeMaps[a]) { gmeResources.parameters.maps[b].ignore = true; } } } delete gmeResources.parameters.excludeMaps; } /* Remove broken map sources */ for (a = gmeResources.parameters.maps.length - 1; a >= 0; a--) { for (b = 0; b < blacklist.length; b++) { if (gmeResources.parameters.maps[a].tileUrl === blacklist[b]) { gmeResources.parameters.maps.splice(a, 1); } } } localStorage.setItem("GME_parameters", JSON.stringify(gmeResources.parameters)); } catch (e) { console.error("GME: Bad Exception: " + e); /* Potential security exception. Carry on with default parameters, but block localstorage */ gmeResources.env.storage = false; } } document.addEventListener("GME_XHR_event", xhr); if (!gmeResources.env.geolocation) { gmeResources.script.dist = function () { console.warn("GME: Geolocation not available"); }; } if (!gmeResources.env.dragdrop) { gmeResources.script.drag = function () { console.warn("GME: Drag and Drop not available"); }; gmeResources.script.drop = gmeResources.script.drag; } insertCSS(gmeResources.css.main); if (gmeResources.env.storage) { insertPage('GME_config', gmeResources.html.config, 'Configure GME v' + gmeResources.parameters.version); insertPage('GME_format', gmeResources.html.customInfo, 'Custom Mapsource Format', 'GME_config'); } // <bugfix> // Trixie treats jQuery Mobile dialogs as new page loads, resetting GME's functions if (window.GME !== undefined) { return; } // </bugfix> switch (gmeResources.env.page) { case "listing": // On a geocache listing if (!gmeResources.env.loggedin) { // Not logged in, so no maps or coordinates... console.log("GME: Couldn't detect log-in. Exiting..."); return; } if (gmeResources.env.dragdrop) { insertCSS(gmeResources.css.drag); } buildScript("GME_page_listing", "common", gmeResources.env.storage ? "config" : "", "map", "dist", "drag", "drop", "loadListing"); break; case "seek": // On the Hide & Seek page target2 = document.querySelector(".SeekCacheWidget h4"); targets = document.getElementsByTagName("h5"); for (i = 0; i < targets.length; i++) { if (targets[i].innerHTML.match(/WGS84/)) { target = targets[i]; break; } } if (target && target2) { var grDiv = document.createElement("div"), hereDiv = document.createElement("div"); grDiv.innerHTML = '<h5>Ordnance Survey Grid Reference :</h5><dl><dt>Grid reference : </dt><dd><form name="grForm" id="grForm"><input type="text" class="Text EqualWidthInput" maxlength="50" size="15" name="grRef" id="grRef" placeholder="SU122422"> <input type="submit" class="Button blockWithModalSpinner" name="submitGR" value="Go" id="grSub"></form></dd></dl><h5>Freeform coordinates</h5><dl><dt>Coordinates :</dt><dd><form name="coordForm" id="coordForm"><input type="text" class="Text EqualWidthInput" maxlength="50" size="15" name="gme_coords" id="gme_coords" placeholder="N 51° 10.683′ W 001° 49.604′"/> <input type="submit" class="Button blockWithModalSpinner" name="gme_coords_sub" value="Go" id="gme_coords_sub"/></form></dd></dl>'; hereDiv.innerHTML = '<h4>Where you are...</h4><dl><dt>Use GeoLocation :</dt><dd><form name="hereForm" id="hereForm"><input type="submit" class="Button blockWithModalSpinner" name="GME_hereSub" value="Go" id="GME_hereSub"></form></dd></dl><h4>By keyword...</h4><dl><dt>Google search :</dt><dd><form name="googleForm" id="googleForm"><input type="text" class="Text EqualWidthInput" maxlength="50" size="15" name="gme_google" id="gme_google"/><input type="submit" class="Button blockWithModalSpinner" name="GME_googleSub" value="Go" id="GME_googleSub"></form></dd></dl>'; target.parentNode.insertBefore(grDiv, target); target2.parentNode.insertBefore(hereDiv, target2); buildScript("GME_page_seek", "common", gmeResources.env.storage ? "config" : "", "osgb", "seek", "loadSeek"); } break; case "track": // On a TB tracking map if (!gmeResources.env.loggedin) { // Not logged in, so no maps or coordinates... return; } buildScript("GME_page_track", "common", gmeResources.env.storage ? "config" : "", "map", "widget", "loadTrack"); break; case "maps": // On a Geocaching Maps page // TODO: Detect if the Google Maps API is being used instead of Leaflet, and quit gracefully /* if (document.querySelector("script[src*='//maps.googleapis.com/']")){ console.warn("Geocaching Map Enhancements requires Leaflet Maps to be enabled."); return; f } */ // Check for click-thru cache data in URI var pop = location.search.match(/pop=([A-Za-z0-9+\/=]+)[\?&]?/); if (pop && pop.length === 2) { try { localStorage.setItem("GME_cache", pop[1]); location.search = location.search.replace(/&pop=[A-Za-z0-9+\/=]+[\?&]?/, ""); } catch (e) { console.error(e + "GME couldn't decode click-through data: " + pop[1]); } return; } if (gmeResources.parameters.osgbSearch) { targets = document.getElementsByClassName("SearchBox"); if (targets[0]) { targets[0].innerHTML = gmeResources.html.search; } } buildScript("GME_page_map", "common", gmeResources.env.storage ? "config" : "", "cssTransitionsFix", "map", "widget", "labels", "drop", gmeResources.parameters.osgbSearch ? "osgb" : "", "loadMap"); break; case "type": buildScript("GME_page_type", "common", gmeResources.env.storage ? "config" : "", "map", "widget", "drop", "loadType"); break; case "hide": buildScript("GME_page_hide", "common", gmeResources.env.storage ? "config" : "", "map", "widget", "drop", "loadHide"); break; default: // Somewhere random on the main website if (gmeResources.env.storage) { buildScript("Generic config", "common", "config", "loadDefault"); } } }());