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