]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/leaflet.share.js
Merge remote-tracking branch 'upstream/pull/6105'
[rails.git] / app / assets / javascripts / leaflet.share.js
1 L.OSM.share = function (options) {
2   const control = L.OSM.sidebarPane(options, "share", "javascripts.share.title", "javascripts.share.title"),
3         marker = L.marker([0, 0], { draggable: true }),
4         locationFilter = new L.LocationFilter({
5           enableButton: false,
6           adjustButton: false
7         });
8
9   function init(map, $ui) {
10     // Link / Embed
11
12     $ui.find("#link_marker").on("change", toggleMarker);
13
14     $ui.find(".btn-group .btn")
15       .on("click", function (e) {
16         e.preventDefault();
17         if (!$(this).hasClass("btn-primary")) return;
18         const id = "#" + $(this).attr("for");
19         $(this).siblings("a")
20           .removeClass("active");
21         $(this).addClass("active");
22         $ui.find(".share-tab")
23           .prop("hidden", true);
24         $ui.find(".share-tab:has(" + id + ")")
25           .prop("hidden", false)
26           .find("input, textarea")
27           .trigger("select");
28       });
29
30     $ui.find(".share-tab [id]").on("click", select);
31
32     // Image
33
34     $ui.find("#mapnik_scale").on("change", update);
35
36     $ui.find("#image_filter").bind("change", toggleFilter);
37
38     const csrfInput = $ui.find("#csrf_export")[0];
39     [[csrfInput.name, csrfInput.value]] = Object.entries(OSM.csrf);
40
41     locationFilter
42       .on("change", update)
43       .addTo(map);
44
45     marker.on("dragend", movedMarker);
46     map.on("move", movedMap);
47     map.on("moveend baselayerchange overlayadd overlayremove", update);
48
49     $ui
50       .on("show", shown)
51       .on("hide", hidden);
52
53     update();
54
55     function shown() {
56       $("#mapnik_scale").val(getScale());
57       update();
58     }
59
60     function hidden() {
61       map.removeLayer(marker);
62       map.options.scrollWheelZoom = map.options.doubleClickZoom = true;
63       locationFilter.disable();
64       update();
65     }
66
67     function toggleMarker() {
68       if ($(this).is(":checked")) {
69         marker.setLatLng(map.getCenter());
70         map.addLayer(marker);
71         map.options.scrollWheelZoom = map.options.doubleClickZoom = "center";
72       } else {
73         map.removeLayer(marker);
74         map.options.scrollWheelZoom = map.options.doubleClickZoom = true;
75       }
76       update();
77     }
78
79     function toggleFilter() {
80       if ($(this).is(":checked")) {
81         locationFilter.setBounds(map.getBounds().pad(-0.2));
82         locationFilter.enable();
83       } else {
84         locationFilter.disable();
85       }
86       update();
87     }
88
89     function movedMap() {
90       marker.setLatLng(map.getCenter());
91       update();
92     }
93
94     function movedMarker() {
95       if (map.hasLayer(marker)) {
96         map.off("move", movedMap);
97         map.on("moveend", updateOnce);
98         map.panTo(marker.getLatLng());
99       }
100     }
101
102     function updateOnce() {
103       map.off("moveend", updateOnce);
104       map.on("move", movedMap);
105       update();
106     }
107
108     function escapeHTML(string) {
109       const htmlEscapes = {
110         "&": "&",
111         "<": "&lt;",
112         ">": "&gt;",
113         "\"": "&quot;",
114         "'": "&#x27;"
115       };
116       return string === null ? "" : String(string).replace(/[&<>"']/g, function (match) {
117         return htmlEscapes[match];
118       });
119     }
120
121     function update() {
122       const layer = map.getMapBaseLayer();
123       const canEmbed = Boolean(layer && layer.options.canEmbed);
124       let bounds = map.getBounds();
125
126       $("#link_marker")
127         .prop("checked", map.hasLayer(marker));
128
129       $("#image_filter")
130         .prop("checked", locationFilter.isEnabled());
131
132       // Link / Embed
133
134       $("#short_input").val(map.getShortUrl(marker));
135       $("#long_input").val(map.getUrl(marker));
136       $("#short_link").attr("href", map.getShortUrl(marker));
137       $("#long_link").attr("href", map.getUrl(marker));
138
139       const params = new URLSearchParams({
140         bbox: bounds.toBBoxString(),
141         layer: map.getMapBaseLayerId()
142       });
143
144       if (map.hasLayer(marker)) {
145         const latLng = marker.getLatLng().wrap();
146         params.set("marker", latLng.lat + "," + latLng.lng);
147       }
148
149       $("#embed_link")
150         .toggleClass("btn-primary", canEmbed)
151         .toggleClass("btn-secondary", !canEmbed)
152         .tooltip(canEmbed ? "disable" : "enable");
153       if (!canEmbed && $("#embed_link").hasClass("active")) {
154         $("#long_link").trigger("click");
155       }
156
157       $("#embed_html").val(
158         "<iframe width=\"425\" height=\"350\" src=\"" +
159           escapeHTML(OSM.SERVER_PROTOCOL + "://" + OSM.SERVER_URL + "/export/embed.html?" + params) +
160           "\" style=\"border: 1px solid black\"></iframe><br/>" +
161           "<small><a href=\"" + escapeHTML(map.getUrl(marker)) + "\">" +
162           escapeHTML(OSM.i18n.t("javascripts.share.view_larger_map")) + "</a></small>");
163
164       // Geo URI
165
166       $("#geo_uri")
167         .attr("href", map.getGeoUri(marker))
168         .text(map.getGeoUri(marker));
169
170       // Image
171
172       if (locationFilter.isEnabled()) {
173         bounds = locationFilter.getBounds();
174       }
175
176       let scale = $("#mapnik_scale").val();
177       const size = L.bounds(L.CRS.EPSG3857.project(bounds.getSouthWest()),
178                             L.CRS.EPSG3857.project(bounds.getNorthEast())).getSize(),
179             maxScale = Math.floor(Math.sqrt(size.x * size.y / 0.3136));
180
181       $("#mapnik_minlon").val(bounds.getWest());
182       $("#mapnik_minlat").val(bounds.getSouth());
183       $("#mapnik_maxlon").val(bounds.getEast());
184       $("#mapnik_maxlat").val(bounds.getNorth());
185
186       if (scale < maxScale) {
187         scale = roundScale(maxScale);
188         $("#mapnik_scale").val(scale);
189       }
190
191       const mapWidth = Math.round(size.x / scale / 0.00028);
192       const mapHeight = Math.round(size.y / scale / 0.00028);
193       $("#mapnik_image_width").text(mapWidth);
194       $("#mapnik_image_height").text(mapHeight);
195
196       const canDownloadImage = Boolean(layer && layer.options.canDownloadImage);
197
198       $("#mapnik_image_layer").text(canDownloadImage ? layer.options.name : "");
199       $("#map_format").val(canDownloadImage ? layer.options.layerId : "");
200
201       $("#map_zoom").val(map.getZoom());
202       $("#mapnik_lon").val(map.getCenter().lng);
203       $("#mapnik_lat").val(map.getCenter().lat);
204       $("#map_width").val(mapWidth);
205       $("#map_height").val(mapHeight);
206
207       $("#export-image").toggle(canDownloadImage);
208       $("#export-warning").toggle(!canDownloadImage);
209       $("#mapnik_scale_row").toggle(canDownloadImage && layer.options.layerId === "mapnik");
210     }
211
212     function select() {
213       $(this).trigger("select");
214     }
215
216     function getScale() {
217       const bounds = map.getBounds(),
218             centerLat = bounds.getCenter().lat,
219             halfWorldMeters = 6378137 * Math.PI * Math.cos(centerLat * Math.PI / 180),
220             meters = halfWorldMeters * (bounds.getEast() - bounds.getWest()) / 180,
221             pixelsPerMeter = map.getSize().x / meters,
222             metersPerPixel = 1 / (92 * 39.3701);
223       return Math.round(1 / (pixelsPerMeter * metersPerPixel));
224     }
225
226     function roundScale(scale) {
227       const precision = 5 * Math.pow(10, Math.floor(Math.LOG10E * Math.log(scale)) - 2);
228       return precision * Math.ceil(scale / precision);
229     }
230   }
231
232   control.onAddPane = function (map, button, $ui) {
233     $("#content").addClass("overlay-right-sidebar");
234
235     control.onContentLoaded = () => init(map, $ui);
236     $ui.one("show", control.loadContent);
237   };
238
239   return control;
240 };