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({
9 function init(map, $ui) {
12 $ui.find("#link_marker").on("change", toggleMarker);
14 $ui.find(".btn-group .btn")
15 .on("click", function (e) {
17 if (!$(this).hasClass("btn-primary")) return;
18 const id = "#" + $(this).attr("for");
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")
30 $ui.find(".share-tab [id]").on("click", select);
34 $ui.find("#mapnik_scale").on("change", update);
36 $ui.find("#image_filter").bind("change", toggleFilter);
38 const csrfInput = $ui.find("#csrf_export")[0];
39 [[csrfInput.name, csrfInput.value]] = Object.entries(OSM.csrf);
45 marker.on("dragend", movedMarker);
46 map.on("move", movedMap);
47 map.on("moveend baselayerchange overlayadd overlayremove", update);
56 $("#mapnik_scale").val(getScale());
61 map.removeLayer(marker);
62 map.options.scrollWheelZoom = map.options.doubleClickZoom = true;
63 locationFilter.disable();
67 function toggleMarker() {
68 if ($(this).is(":checked")) {
69 marker.setLatLng(map.getCenter());
71 map.options.scrollWheelZoom = map.options.doubleClickZoom = "center";
73 map.removeLayer(marker);
74 map.options.scrollWheelZoom = map.options.doubleClickZoom = true;
79 function toggleFilter() {
80 if ($(this).is(":checked")) {
81 locationFilter.setBounds(map.getBounds().pad(-0.2));
82 locationFilter.enable();
84 locationFilter.disable();
90 marker.setLatLng(map.getCenter());
94 function movedMarker() {
95 if (map.hasLayer(marker)) {
96 map.off("move", movedMap);
97 map.on("moveend", updateOnce);
98 map.panTo(marker.getLatLng());
102 function updateOnce() {
103 map.off("moveend", updateOnce);
104 map.on("move", movedMap);
108 function escapeHTML(string) {
109 const htmlEscapes = {
116 return string === null ? "" : String(string).replace(/[&<>"']/g, function (match) {
117 return htmlEscapes[match];
122 const layer = map.getMapBaseLayer();
123 const canEmbed = Boolean(layer && layer.options.canEmbed);
124 let bounds = map.getBounds();
127 .prop("checked", map.hasLayer(marker));
130 .prop("checked", locationFilter.isEnabled());
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));
139 const params = new URLSearchParams({
140 bbox: bounds.toBBoxString(),
141 layer: map.getMapBaseLayerId()
144 if (map.hasLayer(marker)) {
145 const latLng = marker.getLatLng().wrap();
146 params.set("marker", latLng.lat + "," + latLng.lng);
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");
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>");
167 .attr("href", map.getGeoUri(marker))
168 .text(map.getGeoUri(marker));
172 if (locationFilter.isEnabled()) {
173 bounds = locationFilter.getBounds();
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));
181 $("#mapnik_minlon").val(bounds.getWest());
182 $("#mapnik_minlat").val(bounds.getSouth());
183 $("#mapnik_maxlon").val(bounds.getEast());
184 $("#mapnik_maxlat").val(bounds.getNorth());
186 if (scale < maxScale) {
187 scale = roundScale(maxScale);
188 $("#mapnik_scale").val(scale);
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);
196 const canDownloadImage = Boolean(layer && layer.options.canDownloadImage);
198 $("#mapnik_image_layer").text(canDownloadImage ? layer.options.name : "");
199 $("#map_format").val(canDownloadImage ? layer.options.layerId : "");
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);
207 $("#export-image").toggle(canDownloadImage);
208 $("#export-warning").toggle(!canDownloadImage);
209 $("#mapnik_scale_row").toggle(canDownloadImage && layer.options.layerId === "mapnik");
213 $(this).trigger("select");
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));
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);
232 control.onAddPane = function (map, button, $ui) {
233 $("#content").addClass("overlay-right-sidebar");
235 control.onContentLoaded = () => init(map, $ui);
236 $ui.one("show", control.loadContent);