]> git.openstreetmap.org Git - rails.git/blob - app/assets/javascripts/index/directions-endpoint.js
Write directions sidebar header using .erb template
[rails.git] / app / assets / javascripts / index / directions-endpoint.js
1 OSM.DirectionsEndpoint = function Endpoint(map, input, iconUrl, dragCallback, changeCallback) {
2   const endpoint = {};
3
4   endpoint.marker = L.marker([0, 0], {
5     icon: L.icon({
6       iconUrl: iconUrl,
7       iconSize: [25, 41],
8       iconAnchor: [12, 41],
9       popupAnchor: [1, -34],
10       shadowUrl: OSM.MARKER_SHADOW,
11       shadowSize: [41, 41]
12     }),
13     draggable: true,
14     autoPan: true
15   });
16
17   endpoint.enableListeners = function () {
18     endpoint.marker.on("drag dragend", markerDragListener);
19     input.on("keydown", inputKeydownListener);
20     input.on("change", inputChangeListener);
21   };
22
23   endpoint.disableListeners = function () {
24     endpoint.marker.off("drag dragend", markerDragListener);
25     input.off("keydown", inputKeydownListener);
26     input.off("change", inputChangeListener);
27   };
28
29   function markerDragListener(e) {
30     const latlng = L.latLng(OSM.cropLocation(e.target.getLatLng(), map.getZoom()));
31
32     if (endpoint.geocodeRequest) endpoint.geocodeRequest.abort();
33     delete endpoint.geocodeRequest;
34
35     setLatLng(latlng);
36     setInputValueFromLatLng(latlng);
37     endpoint.value = input.val();
38     if (e.type === "dragend") getReverseGeocode();
39     dragCallback(e.type === "drag");
40   }
41
42   function inputKeydownListener() {
43     input.removeClass("is-invalid");
44   }
45
46   function inputChangeListener(e) {
47     // make text the same in both text boxes
48     const value = e.target.value;
49     endpoint.setValue(value);
50   }
51
52   endpoint.setValue = function (value) {
53     if (endpoint.geocodeRequest) endpoint.geocodeRequest.abort();
54     delete endpoint.geocodeRequest;
55     input.removeClass("is-invalid");
56
57     const coordinatesMatch = value.match(/^\s*([+-]?\d+(?:\.\d*)?)(?:\s+|\s*[/,]\s*)([+-]?\d+(?:\.\d*)?)\s*$/);
58     const latlng = coordinatesMatch && L.latLng(coordinatesMatch[1], coordinatesMatch[2]);
59
60     if (latlng && endpoint.cachedReverseGeocode && endpoint.cachedReverseGeocode.latlng.equals(latlng)) {
61       setLatLng(latlng);
62       if (endpoint.cachedReverseGeocode.notFound) {
63         endpoint.value = value;
64         input.addClass("is-invalid");
65       } else {
66         endpoint.value = endpoint.cachedReverseGeocode.value;
67       }
68       input.val(endpoint.value);
69       changeCallback();
70       return;
71     }
72
73     endpoint.value = value;
74     removeLatLng();
75     input.val(value);
76
77     if (latlng) {
78       setLatLng(latlng);
79       setInputValueFromLatLng(latlng);
80       getReverseGeocode();
81       changeCallback();
82     } else if (endpoint.value) {
83       getGeocode();
84     }
85   };
86
87   endpoint.clearValue = function () {
88     if (endpoint.geocodeRequest) endpoint.geocodeRequest.abort();
89     delete endpoint.geocodeRequest;
90     removeLatLng();
91     delete endpoint.value;
92     input.val("");
93     map.removeLayer(endpoint.marker);
94   };
95
96   endpoint.swapCachedReverseGeocodes = function (otherEndpoint) {
97     const g0 = endpoint.cachedReverseGeocode;
98     const g1 = otherEndpoint.cachedReverseGeocode;
99     delete endpoint.cachedReverseGeocode;
100     delete otherEndpoint.cachedReverseGeocode;
101     if (g0) otherEndpoint.cachedReverseGeocode = g0;
102     if (g1) endpoint.cachedReverseGeocode = g1;
103   };
104
105   function getGeocode() {
106     const viewbox = map.getBounds().toBBoxString(), // <sw lon>,<sw lat>,<ne lon>,<ne lat>
107           geocodeUrl = OSM.NOMINATIM_URL + "search?" + new URLSearchParams({ q: endpoint.value, format: "json", viewbox });
108
109     endpoint.geocodeRequest = new AbortController();
110     fetch(geocodeUrl, { signal: endpoint.geocodeRequest.signal })
111       .then(r => r.json())
112       .then(success)
113       .catch(() => {});
114
115     function success(json) {
116       delete endpoint.geocodeRequest;
117       if (json.length === 0) {
118         input.addClass("is-invalid");
119         // eslint-disable-next-line no-alert
120         alert(I18n.t("javascripts.directions.errors.no_place", { place: endpoint.value }));
121         return;
122       }
123
124       setLatLng(L.latLng(json[0]));
125
126       endpoint.value = json[0].display_name;
127       input.val(json[0].display_name);
128
129       changeCallback();
130     }
131   }
132
133   function getReverseGeocode() {
134     const latlng = endpoint.latlng.clone(),
135           { lat, lng } = latlng,
136           reverseGeocodeUrl = OSM.NOMINATIM_URL + "reverse?" + new URLSearchParams({ lat, lon: lng, format: "json" });
137
138     endpoint.geocodeRequest = new AbortController();
139     fetch(reverseGeocodeUrl, { signal: endpoint.geocodeRequest.signal })
140       .then(r => r.json())
141       .then(success)
142       .catch(() => {});
143
144     function success(json) {
145       delete endpoint.geocodeRequest;
146       if (!json || !json.display_name) {
147         endpoint.cachedReverseGeocode = { latlng: latlng, notFound: true };
148         return;
149       }
150
151       endpoint.value = json.display_name;
152       input.val(json.display_name);
153       endpoint.cachedReverseGeocode = { latlng: latlng, value: endpoint.value };
154     }
155   }
156
157   function setLatLng(ll) {
158     input
159       .attr("data-lat", ll.lat)
160       .attr("data-lon", ll.lng);
161     endpoint.latlng = ll;
162     endpoint.marker
163       .setLatLng(ll)
164       .addTo(map);
165   }
166
167   function removeLatLng() {
168     input
169       .removeAttr("data-lat")
170       .removeAttr("data-lon");
171     delete endpoint.latlng;
172   }
173
174   function setInputValueFromLatLng(latlng) {
175     input.val(latlng.lat + ", " + latlng.lng);
176   }
177
178   return endpoint;
179 };