]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index/directions-route-output.js
Merge remote-tracking branch 'upstream/pull/5201'
[rails.git] / app / assets / javascripts / index / directions-route-output.js
1 OSM.DirectionsRouteOutput = function (map) {
2   const popup = L.popup({ autoPanPadding: [100, 100] });
3
4   const polyline = L.polyline([], {
5     color: "#03f",
6     opacity: 0.3,
7     weight: 10
8   });
9
10   const highlight = L.polyline([], {
11     color: "#ff0",
12     opacity: 0.5,
13     weight: 12
14   });
15
16   let distanceUnits = "km";
17   let downloadURL = null;
18   let route = {};
19
20   function translateDistanceUnits(m) {
21     const scope = "javascripts.directions.distance_in_units.";
22     if (distanceUnits === "mi") return [scope + "miles", m / 0.3048, "ft", m / 1609.344, "mi"];
23     return [scope + "meters", m, "m", m / 1000, "km"];
24   }
25
26   function formatTotalDistance(m) {
27     const [scope, minorValue, minorName, majorValue, majorName] = translateDistanceUnits(m);
28
29     if (minorValue < 1000 || majorValue < 0.25) {
30       return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue) });
31     } else if (majorValue < 10) {
32       return OSM.i18n.t(majorName, { scope, distance: majorValue.toFixed(1) });
33     } else {
34       return OSM.i18n.t(majorName, { scope, distance: Math.round(majorValue) });
35     }
36   }
37
38   function formatStepDistance(m) {
39     const [scope, minorValue, minorName, majorValue, majorName] = translateDistanceUnits(m);
40
41     if (minorValue < 5) {
42       return "";
43     } else if (minorValue < 200) {
44       return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue / 10) * 10 });
45     } else if (minorValue < 1500 || majorValue < 0.25) {
46       return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue / 100) * 100 });
47     } else if (majorValue < 5) {
48       return OSM.i18n.t(majorName, { scope, distance: majorValue.toFixed(1) });
49     } else {
50       return OSM.i18n.t(majorName, { scope, distance: Math.round(majorValue) });
51     }
52   }
53
54   function formatHeight(m) {
55     const [scope, value, name] = translateDistanceUnits(m);
56
57     return OSM.i18n.t(name, { scope, distance: Math.round(value) });
58   }
59
60   function formatTime(s) {
61     let m = Math.round(s / 60);
62     const h = Math.floor(m / 60);
63     m -= h * 60;
64     return h + ":" + (m < 10 ? "0" : "") + m;
65   }
66
67   function writeContent() {
68     if (this?.dataset?.unit) distanceUnits = this.dataset.unit;
69
70     $("#directions_route_distance").val(formatTotalDistance(route.distance));
71     $("#directions_route_time").val(formatTime(route.time));
72     $("#directions_route_ascend_descend").prop("hidden", typeof route.ascend === "undefined" || typeof route.descend === "undefined");
73     $("#directions_route_ascend").val(formatHeight(route.ascend ?? 0));
74     $("#directions_route_descend").val(formatHeight(route.descend ?? 0));
75     $("#directions_route_steps").empty();
76
77     for (const [i, step] of route.steps.entries()) {
78       writeStep(step, i).appendTo("#directions_route_steps");
79     }
80   }
81
82   function writeStep([direction, instruction, dist, lineseg], i) {
83     const popupText = `<b>${i + 1}.</b> ${instruction}`;
84     let icon = "";
85     if (direction) icon = `<svg width="20" height="20" class="d-block"><use href="#routing-sprite-${direction}" /></svg>`;
86
87     return $("<tr class='turn'/>")
88       .append(`<td class="ps-3">${icon}</td>`)
89       .append(`<td>${popupText}</td>`)
90       .append(`<td class="pe-3 distance text-body-secondary text-end">${formatStepDistance(dist)}</td>`)
91       .on("click", function () {
92         popup
93           .setLatLng(lineseg[0])
94           .setContent(`<p>${popupText}</p>`)
95           .openOn(map);
96       })
97       .on("mouseenter", function () {
98         highlight
99           .setLatLngs(lineseg)
100           .addTo(map);
101       })
102       .on("mouseleave", function () {
103         map.removeLayer(highlight);
104       });
105   }
106
107   const routeOutput = {};
108
109   routeOutput.write = function (r) {
110     route = r;
111     polyline
112       .setLatLngs(route.line)
113       .addTo(map);
114
115     writeContent();
116     $("#directions_route input[data-unit]").off().on("change", writeContent);
117
118     const blob = new Blob([JSON.stringify(polyline.toGeoJSON())], { type: "application/json" });
119     URL.revokeObjectURL(downloadURL);
120     downloadURL = URL.createObjectURL(blob);
121     $("#directions_route_download").prop("href", downloadURL);
122
123     $("#directions_route_credit")
124       .text(route.credit)
125       .prop("href", route.creditlink);
126   };
127
128   routeOutput.fit = function () {
129     map.fitBounds(polyline.getBounds().pad(0.05));
130   };
131
132   routeOutput.isVisible = function () {
133     return map.hasLayer(polyline);
134   };
135
136   routeOutput.remove = function () {
137     map
138       .removeLayer(popup)
139       .removeLayer(polyline);
140
141     $("#directions_route input[data-unit]").off();
142
143     $("#directions_route_steps").empty();
144
145     URL.revokeObjectURL(downloadURL);
146     $("#directions_route_download").prop("href", "");
147   };
148
149   return routeOutput;
150 };