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