├── .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 |
--------------------------------------------------------------------------------