]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index/directions-route-output.js
Merge remote-tracking branch 'upstream/pull/6913'
[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_m";
17   let downloadURL = null;
18
19   function translateDistanceUnits(m) {
20     if (distanceUnits === "mi_ft") {
21       return [m / 0.3048, "ft", m / 1609.344, "mi"];
22     } else if (distanceUnits === "mi_yd") {
23       return [m / 0.9144, "yd", m / 1609.344, "mi"];
24     } else {
25       return [m, "m", m / 1000, "km"];
26     }
27   }
28
29   function formatTotalDistance(minorValue, minorName, majorValue, majorName) {
30     const scope = "javascripts.directions.distance_in_units";
31
32     if (minorValue < 1000 || majorValue < 0.25) {
33       return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue) });
34     } else if (majorValue < 10) {
35       return OSM.i18n.t(majorName, { scope, distance: majorValue.toFixed(1) });
36     } else {
37       return OSM.i18n.t(majorName, { scope, distance: Math.round(majorValue) });
38     }
39   }
40
41   function formatStepDistance(minorValue, minorName, majorValue, majorName) {
42     const scope = "javascripts.directions.distance_in_units";
43
44     if (minorValue < 5) {
45       return "";
46     } else if (minorValue < 200) {
47       return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue / 10) * 10 });
48     } else if (minorValue < 1500 || majorValue < 0.25) {
49       return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue / 100) * 100 });
50     } else if (majorValue < 5) {
51       return OSM.i18n.t(majorName, { scope, distance: majorValue.toFixed(1) });
52     } else {
53       return OSM.i18n.t(majorName, { scope, distance: Math.round(majorValue) });
54     }
55   }
56
57   function formatHeight(minorValue, minorName) {
58     const scope = "javascripts.directions.distance_in_units";
59
60     return OSM.i18n.t(minorName, { scope, distance: Math.round(minorValue) });
61   }
62
63   function formatTime(s) {
64     let m = Math.round(s / 60);
65     const h = Math.floor(m / 60);
66
67     m -= h * 60;
68
69     return h + ":" + (m < 10 ? "0" : "") + m;
70   }
71
72   function writeSummary(route) {
73     $("#directions_route_distance").val(formatTotalDistance(...translateDistanceUnits(route.distance)));
74     $("#directions_route_time").val(formatTime(route.time));
75
76     if (typeof route.ascend !== "undefined" && typeof route.descend !== "undefined") {
77       $("#directions_route_ascend_descend").prop("hidden", false);
78       $("#directions_route_ascend").val(formatHeight(...translateDistanceUnits(route.ascend)));
79       $("#directions_route_descend").val(formatHeight(...translateDistanceUnits(route.descend)));
80     } else {
81       $("#directions_route_ascend_descend").prop("hidden", true);
82       $("#directions_route_ascend").val("");
83       $("#directions_route_descend").val("");
84     }
85   }
86
87   function writeSteps(route) {
88     $("#directions_route_steps").empty();
89
90     for (const [i, [direction, instruction, dist, lineseg]] of route.steps.entries()) {
91       const row = $("<tr class='turn'/>").appendTo($("#directions_route_steps"));
92
93       if (direction) {
94         row.append("<td class='ps-3'><svg width='20' height='20' class='d-block'><use href='#routing-sprite-" + direction + "' /></svg></td>");
95       } else {
96         row.append("<td class='ps-3'>");
97       }
98
99       row.append(`<td class="text-break"><b>${i + 1}.</b> ${instruction}`);
100       row.append("<td class='pe-3 distance text-body-secondary text-end'>" + formatStepDistance(...translateDistanceUnits(dist)));
101
102       row.on("click", function () {
103         popup
104           .setLatLng(lineseg[0])
105           .setContent(`<p><b>${i + 1}.</b> ${instruction}</p>`)
106           .openOn(map);
107       });
108
109       row
110         .on("mouseenter", function () {
111           highlight
112             .setLatLngs(lineseg)
113             .addTo(map);
114         })
115         .on("mouseleave", function () {
116           map.removeLayer(highlight);
117         });
118     }
119   }
120
121   const routeOutput = {};
122
123   routeOutput.write = function (route) {
124     polyline
125       .setLatLngs(route.line)
126       .addTo(map);
127
128     writeSummary(route);
129     writeSteps(route);
130
131     $("#directions_distance_units_settings input").off().on("change", function () {
132       distanceUnits = this.value;
133       writeSummary(route);
134       writeSteps(route);
135     });
136
137     const blob = new Blob([JSON.stringify(polyline.toGeoJSON())], { type: "application/geo+json" });
138
139     URL.revokeObjectURL(downloadURL);
140     downloadURL = URL.createObjectURL(blob);
141     $("#directions_route_download").prop("href", downloadURL);
142
143     $("#directions_route_credit")
144       .text(route.credit)
145       .prop("href", route.creditlink);
146     $("#directions_route_demo")
147       .text(route.credit)
148       .prop("href", route.demolink);
149   };
150
151   routeOutput.fit = function () {
152     map.fitBounds(polyline.getBounds().pad(0.05));
153   };
154
155   routeOutput.isVisible = function () {
156     return map.hasLayer(polyline);
157   };
158
159   routeOutput.remove = function () {
160     map
161       .removeLayer(popup)
162       .removeLayer(polyline);
163
164     $("#directions_distance_units_settings input").off();
165
166     $("#directions_route_steps").empty();
167
168     URL.revokeObjectURL(downloadURL);
169     $("#directions_route_download").prop("href", "");
170   };
171
172   return routeOutput;
173 };