├── .gitignore ├── README.md ├── index.html ├── latlon.js ├── FunctionButton.js └── leaflet-hash.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Copy latitude and longitude 2 | 3 | Displays a map with a marker which you can drag. After settling on a location, 4 | click one of the "Copy ..." buttons to copy the location to the clipboard. 5 | Supports URL hashes to set the initial location. 6 | 7 | ## Author and License 8 | 9 | Written by Ilya Zverev, published under MIT license. 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Copy latitude and longitude 6 | 7 | 8 | 9 | 10 | 11 | 12 | 30 | 31 | 32 |
33 |
Copied.
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /latlon.js: -------------------------------------------------------------------------------- 1 | var map; 2 | 3 | function copyText(str) { 4 | let c = map.getCenter(); 5 | let d = 0; 6 | if (map.getZoom() <= 5) 7 | d = 1; 8 | else if (map.getZoom() <= 8) 9 | d = 2; 10 | else if (map.getZoom() <= 11) 11 | d = 3; 12 | else if (map.getZoom() <= 14) 13 | d = 4; 14 | else if (map.getZoom() <= 17) 15 | d = 5 16 | else 17 | d = 6; 18 | 19 | str = str.replace('{lat}', c.lat.toFixed(d)).replace('{lon}', c.lng.toFixed(d)); 20 | navigator.clipboard.writeText(str) 21 | .then(function() { 22 | document.getElementById('note').style.display = 'block'; 23 | window.setTimeout(function() { 24 | document.getElementById('note').style.display = 'none'; 25 | }, 1000); 26 | }) 27 | .catch(function(err) { alert('Error when copying: ', err); }); 28 | } 29 | 30 | window.onload = function() { 31 | map = L.map('map', { doubleClickZoom: false }).setView([45, 20], 4); 32 | L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { 33 | attribution: '© OpenStreetMap', 34 | maxZoom: 19 35 | }).addTo(map); 36 | var m = L.marker(map.getCenter()).addTo(map); 37 | map.on('drag zoom', function(e) { 38 | m.setLatLng(map.getCenter()); 39 | }); 40 | map.addHash(); 41 | 42 | L.functionButtons([ 43 | { content: 'Copy lat, lon', callback: function() { 44 | copyText('{lat}, {lon}') 45 | }}, 46 | { content: 'Copy WKT', callback: function() { 47 | copyText('POINT({lon} {lat})') 48 | }}, 49 | { content: 'Copy wikipedia', callback: function() { 50 | copyText('{{Coord|{lat}|{lon}|display=title}}') 51 | }}, 52 | { content: 'Copy URL', callback: function() { 53 | copyText('' + window.location) 54 | }} 55 | ], {position: 'topleft'}).addTo(map); 56 | } 57 | -------------------------------------------------------------------------------- /FunctionButton.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A leaflet button with icon or text and click listener. 3 | */ 4 | L.FunctionButtons = L.Control.extend({ 5 | includes: L.Evented.prototype, 6 | 7 | initialize: function( buttons, options ) { 8 | if( !('push' in buttons && 'splice' in buttons) ) 9 | buttons = [buttons]; 10 | this._buttons = buttons; 11 | if( !options && buttons.length > 0 && 'position' in buttons[0] ) 12 | options = { position: buttons[0].position }; 13 | L.Control.prototype.initialize.call(this, options); 14 | }, 15 | 16 | onAdd: function( map ) { 17 | this._map = map; 18 | this._links = []; 19 | 20 | var container = L.DomUtil.create('div', 'leaflet-bar'); 21 | for( var i = 0; i < this._buttons.length; i++ ) { 22 | var button = this._buttons[i], 23 | link = L.DomUtil.create('a', '', container); 24 | link._buttonIndex = i; // todo: remove? 25 | link.href = button.href || '#'; 26 | if( button.href ) 27 | link.target = 'funcbtn'; 28 | link.style.padding = '0 4px'; 29 | link.style.width = 'auto'; 30 | link.style.minWidth = '20px'; 31 | link.style.textAlign = 'left'; 32 | if( button.bgColor ) 33 | link.style.backgroundColor = button.bgColor; 34 | if( button.title ) 35 | link.title = button.title; 36 | button.link = link; 37 | this._updateContent(i); 38 | 39 | var stop = L.DomEvent.stopPropagation; 40 | L.DomEvent 41 | .on(link, 'click', stop) 42 | .on(link, 'mousedown', stop) 43 | .on(link, 'dblclick', stop); 44 | if( !button.href ) 45 | L.DomEvent 46 | .on(link, 'click', L.DomEvent.preventDefault) 47 | .on(link, 'click', this.clicked, this); 48 | } 49 | 50 | return container; 51 | }, 52 | 53 | _updateContent: function( n ) { 54 | if( n >= this._buttons.length ) 55 | return; 56 | var button = this._buttons[n], 57 | link = button.link, 58 | content = button.content; 59 | if( !link ) 60 | return; 61 | if( content === undefined || content === false || content === null || content === '' ) 62 | link.innerHTML = button.alt || ' '; 63 | else if( typeof content === 'string' ) { 64 | var ext = content.length < 4 ? '' : content.substring(content.length - 4), 65 | isData = content.substring(0, 11) === 'data:image/'; 66 | if( ext === '.png' || ext === '.gif' || ext === '.jpg' || isData ) { 67 | link.style.width = '' + (button.imageSize || 26) + 'px'; 68 | link.style.height = '' + (button.imageSize || 26) + 'px'; 69 | link.style.padding = '0'; 70 | link.style.backgroundImage = 'url(' + content + ')'; 71 | link.style.backgroundRepeat = 'no-repeat'; 72 | link.style.backgroundPosition = button.bgPos ? (-button.bgPos[0]) + 'px ' + (-button.bgPos[1]) + 'px' : '0px 0px'; 73 | } else 74 | link.innerHTML = content; 75 | } else { 76 | while( link.firstChild ) 77 | link.removeChild(link.firstChild); 78 | link.appendChild(content); 79 | } 80 | }, 81 | 82 | setContent: function( n, content ) { 83 | if( content === undefined ) { 84 | content = n; 85 | n = 0; 86 | } 87 | if( n < this._buttons.length ) { 88 | this._buttons[n].content = content; 89 | this._updateContent(n); 90 | } 91 | }, 92 | 93 | setTitle: function( n, title ) { 94 | if( title === undefined ) { 95 | title = n; 96 | n = 0; 97 | } 98 | if( n < this._buttons.length ) { 99 | var button = this._buttons[n]; 100 | button.title = title; 101 | if( button.link ) 102 | button.link.title = title; 103 | } 104 | }, 105 | 106 | setBgPos: function( n, bgPos ) { 107 | if( bgPos === undefined ) { 108 | bgPos = n; 109 | n = 0; 110 | } 111 | if( n < this._buttons.length ) { 112 | var button = this._buttons[n]; 113 | button.bgPos = bgPos; 114 | if( button.link ) 115 | button.link.style.backgroundPosition = bgPos ? (-bgPos[0]) + 'px ' + (-bgPos[1]) + 'px' : '0px 0px'; 116 | } 117 | }, 118 | 119 | setHref: function( n, href ) { 120 | if( href === undefined ) { 121 | href = n; 122 | n = 0; 123 | } 124 | if( n < this._buttons.length ) { 125 | var button = this._buttons[n]; 126 | button.href = href; 127 | if( button.link ) 128 | button.link.href = href; 129 | } 130 | }, 131 | 132 | clicked: function(e) { 133 | var link = (window.event && window.event.srcElement) || e.target || e.srcElement; 134 | while( link && 'tagName' in link && link.tagName !== 'A' && !('_buttonIndex' in link ) ) 135 | link = link.parentNode; 136 | if( '_buttonIndex' in link ) { 137 | var button = this._buttons[link._buttonIndex]; 138 | if( button ) { 139 | if( 'callback' in button ) 140 | button.callback.call(button.context); 141 | this.fire('clicked', { idx: link._buttonIndex }); 142 | } 143 | } 144 | } 145 | }); 146 | 147 | L.functionButtons = function( buttons, options ) { 148 | return new L.FunctionButtons(buttons, options); 149 | }; 150 | 151 | /* 152 | * Helper method from the old class. It is not recommended to use it, please use L.functionButtons(). 153 | */ 154 | L.functionButton = function( content, button, options ) { 155 | if( button ) 156 | button.content = content; 157 | else 158 | button = { content: content }; 159 | return L.functionButtons([button], options); 160 | }; 161 | -------------------------------------------------------------------------------- /leaflet-hash.js: -------------------------------------------------------------------------------- 1 | // https://github.com/calvinmetcalf/leaflet-hash 2 | (function() { 3 | 4 | L.Hash = L.Class.extend({ 5 | initialize: function(map, options) { 6 | this.map = map; 7 | this.options = options || {}; 8 | if (!this.options.path) { 9 | if (this.options.lc) { 10 | this.options.path = '{base}/{z}/{lat}/{lng}'; 11 | } else { 12 | this.options.path = '{z}/{lat}/{lng}'; 13 | } 14 | } 15 | if (this.options.lc && !this.options.formatBase) { 16 | this.options.formatBase = [ 17 | /[\s\:A-Z]/g, function(match) { 18 | if (match.match(/\s/)) { 19 | return "_"; 20 | } else if (match.match(/\:/)) { 21 | return ""; 22 | } 23 | if (match.match(/[A-Z]/)) { 24 | return match.toLowerCase(); 25 | } 26 | } 27 | ]; 28 | }if (location.hash) { 29 | this.updateFromState(this.parseHash(location.hash)); 30 | } 31 | if (this.map._loaded) { 32 | return this.startListning(); 33 | } else { 34 | return this.map.on("load", this.startListning,this); 35 | } 36 | }, 37 | startListning: function() { 38 | var onHashChange, 39 | _this = this; 40 | if (location.hash) { 41 | this.updateFromState(this.parseHash(location.hash)); 42 | } 43 | if (history.pushState) { 44 | if (!location.hash) { 45 | history.replaceState.apply(history, this.formatState()); 46 | } 47 | window.onpopstate = function(event) { 48 | 49 | if (event.state) { 50 | return _this.updateFromState(event.state); 51 | } 52 | }; 53 | this.map.on("moveend", function() { 54 | var pstate; 55 | pstate = _this.formatState(); 56 | if (location.hash !== pstate[2] && !_this.moving) { 57 | return history.pushState.apply(history, pstate); 58 | } 59 | }); 60 | } else { 61 | if (!location.hash) { 62 | location.hash = this.formatState()[2]; 63 | } 64 | onHashChange = function() { 65 | 66 | var pstate; 67 | pstate = _this.formatState(); 68 | if (location.hash !== pstate[2] && !_this.moving) { 69 | return location.hash = pstate[2]; 70 | } 71 | }; 72 | this.map.on("moveend", onHashChange); 73 | if (('onhashchange' in window) && (window.documentMode === void 0 || window.documentMode > 7)) { 74 | window.onhashchange = function() { 75 | if (location.hash) { 76 | 77 | return _this.updateFromState(_this.parseHash(location.hash)); 78 | } 79 | }; 80 | } else { 81 | this.hashChangeInterval = setInterval(onHashChange, 50); 82 | } 83 | } 84 | return this.map.on("baselayerchange", function(e) { 85 | var pstate, _ref; 86 | _this.base = (_ref = _this.options.lc._layers[e.layer._leaflet_id].name).replace.apply(_ref, _this.options.formatBase); 87 | pstate = _this.formatState(); 88 | if (history.pushState) { 89 | if (location.hash !== pstate[2] && !_this.moving) { 90 | return history.pushState.apply(history, pstate); 91 | } 92 | } else { 93 | if (location.hash !== pstate[2] && !_this.moving) { 94 | return location.hash = pstate[2]; 95 | } 96 | } 97 | }); 98 | }, 99 | parseHash: function(hash) { 100 | 101 | var args, lat, latIndex, lngIndex, lon, out, path, zIndex, zoom; 102 | path = this.options.path.split("/"); 103 | zIndex = path.indexOf("{z}"); 104 | latIndex = path.indexOf("{lat}"); 105 | lngIndex = path.indexOf("{lng}"); 106 | if (hash.indexOf("#") === 0) { 107 | hash = hash.substr(1); 108 | } 109 | args = hash.split("/"); 110 | 111 | if (args.length > 2) { 112 | zoom = parseInt(args[zIndex], 10); 113 | lat = parseFloat(args[latIndex]); 114 | lon = parseFloat(args[lngIndex]); 115 | if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { 116 | return false; 117 | } else { 118 | out = { 119 | center: new L.LatLng(lat, lon), 120 | zoom: zoom 121 | }; 122 | if (args.length > 3) { 123 | out.base = args[path.indexOf("{base}")]; 124 | return out; 125 | } else { 126 | return out; 127 | } 128 | } 129 | } else { 130 | 131 | return false; 132 | } 133 | }, 134 | updateFromState: function(state) { 135 | if (this.moving || !state) { 136 | return; 137 | } 138 | this.moving = true; 139 | this.map.setView(state.center, state.zoom); 140 | if (state.base) { 141 | this.setBase(state.base); 142 | } 143 | this.moving = false; 144 | return true; 145 | }, 146 | formatState: function() { 147 | var center, precision, state, template, zoom; 148 | center = this.map.getCenter(); 149 | zoom = this.map.getZoom(); 150 | precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); 151 | state = { 152 | center: center, 153 | zoom: zoom 154 | }; 155 | template = { 156 | lat: center.lat.toFixed(precision), 157 | lng: center.lng.toFixed(precision), 158 | z: zoom 159 | }; 160 | if (this.options.path.indexOf("{base}") > -1) { 161 | state.base = this.getBase(); 162 | template.base = state.base; 163 | } 164 | return [state, "a", '#' + L.Util.template(this.options.path, template)]; 165 | }, 166 | setBase: function(base) { 167 | var i, inputs, len, _ref; 168 | this.base = base; 169 | inputs = this.options.lc._form.getElementsByTagName('input'); 170 | len = inputs.length; 171 | i = 0; 172 | while (i < len) { 173 | if (inputs[i].name === 'leaflet-base-layers' && (_ref = this.options.lc._layers[inputs[i].layerId].name).replace.apply(_ref, this.options.formatBase) === base) { 174 | inputs[i].checked = true; 175 | this.options.lc._onInputClick(); 176 | return true; 177 | } 178 | i++; 179 | } 180 | }, 181 | getBase: function() { 182 | var i, inputs, len, _ref; 183 | if (this.base) { 184 | return this.base; 185 | } 186 | inputs = this.options.lc._form.getElementsByTagName('input'); 187 | len = inputs.length; 188 | i = 0; 189 | while (i < len) { 190 | if (inputs[i].name === 'leaflet-base-layers' && inputs[i].checked) { 191 | this.base = (_ref = this.options.lc._layers[inputs[i].layerId].name).replace.apply(_ref, this.options.formatBase); 192 | return this.base; 193 | } 194 | } 195 | return false; 196 | }, 197 | remove: function() { 198 | this.map.off("moveend"); 199 | if (window.onpopstate) { 200 | window.onpopstate = null; 201 | } 202 | location.hash = ""; 203 | return clearInterval(this.hashChangeInterval); 204 | } 205 | }); 206 | 207 | L.hash = function(map, options) { 208 | return new L.Hash(map, options); 209 | }; 210 | 211 | L.Map.include({ 212 | addHash: function(options) { 213 | 214 | this._hash = L.hash(this, options); 215 | 216 | return this; 217 | }, 218 | removeHash: function() { 219 | this._hash.remove(); 220 | return this; 221 | } 222 | }); 223 | 224 | }).call(this); 225 | --------------------------------------------------------------------------------