PageSourceSearch

https://www.cyipt.bike/js/lib/Leaflet.LayerViewer/lib/geocoder.js

js cyipt.bike collected 2026-10-03 23:03:03 UTC 10,525 bytes, 332 lines download raw bytes

1/** Attaches autocomplete functionality to the input element
2 * Options is an object with these properties:
3 * source is either a static list of data, or function that returns a list (e.g. via ajax) of results. Each result should have at least these properties: label, value, desc
4 * select is a callback that takes a filtered result as arg, and can be run for side effects such as setting a marker on a map
5 * renderItem is a callback for rendering the menu items, it takes as arguments the menu element and item and should return a rendered list item.
6*  Items are rendered by default with the label on a first line, and a smaller font description on the second.
7*
8* Where possible this file should not assume that cyclestreetsNS has been loaded.
9*/
10/*jslint browser: true, devel: true, passfail: false, continue: true, eqeq: true, forin: true, nomen: true, plusplus: true, regexp: true, unparam: true, vars: true, white: true */
11/*global jQuery, cyclestreetsNS, map, L */
12
13var autocomplete = (function ($) {
14    'use strict';
15
16    return {
17
18// Public methods
19
20	addTo: function (inputElement, options) {
21
22	    var element = $(inputElement);
23
24	    // Default options
25	    if (options === undefined) {options = {};}
26
27	    // Default options to the source url
28	    if (typeof options === "string") {options = {sourceUrl: options};}
29
30	    // Default to geocoder function
31	    if (options.source === undefined) {
32		options.source = function (request, response) {autocomplete.geocoderAjax (element, options.sourceUrl, request, response);};
33	    }
34
35	    // If there is a map on the page then default to centering on the selection
36	    if (options.select === undefined) {
37
38		// Select callback - centres map on selection
39		options.select = autocomplete.centreMap;
40	    }
41
42	    // Default function for rendering the menu
43	    if (options.renderItem === undefined) {
44
45		// Default render over two lines, second line smaller font size
46		options.renderItem = function( ul, item ) {
47		    return $( "<li>" )
48			.append( "<a>" + item.label + "<br /><span>" + item.desc + "</span></a>" )
49			.appendTo( ul );
50		};
51	    }
52
53	    // Control caching of results
54	    if (options.cacheResults !== undefined) {
55
56		// Create the cache or turn off caching
57		element.data('autocompleteCache', options.cacheResults ? {} : false);
58	    }
59
60	    // Run on document ready
61	    $(function () {
62			
63			// Disable enter key when focus is on input element itself (i.e. not on a drop-down value)
64			$(inputElement).keypress(function(e) {
65				var code = e.keyCode || e.which;
66				if (code === 13) {return false;}
67			});
68			
69 		// Add the autocomplete to the element
70		$(inputElement).autocomplete({
71		    appendTo: '#geocoder',	// Ensures this is within the CSS scopes defined by the application, rather than being attached to the surrounding GUI's body which may have different CSS
72		    source: options.source,
73		    minLength: 3,
74		    focus: function (event, ui) {
75
76			// This is called when the focussed menu item changes, show the label (rather than the value) in the input element
77			$(inputElement).val(ui.item.label);
78
79 			// Prevent the widget from inserting the value.
80			return false;
81		    },
82		    select: function (event, ui) {
83
84			// Apply any select callback
85			if (options.select !== undefined) {return options.select (event, ui);}
86
87			// Set the value to the label
88			$(inputElement).val(ui.item.label);
89			
90 			// Prevent the widget from inserting the value.
91			return false;
92		    }
93		}).data("ui-autocomplete")._renderItem = options.renderItem;
94	    });
95	},
96
97
98	// Centres map on lon lat in the autocomplete result
99	centreMap: function (event, ui) {
100
101	    // Declarations
102	    var result = ui.item;
103		
104	    // Abandon if no map
105	    if (cyclestreetsNS === undefined || cyclestreetsNS.map === undefined) {return;}
106		
107		// Zoom to fit the bounds of the polygon
108		var geojsonItemLayer = L.geoJson(ui.item.feature);
109		cyclestreetsNS.map.fitBounds(geojsonItemLayer.getBounds());
110		
111		// Zoom out if now too close
112		if (cyclestreetsNS.map.getZoom() > 17) {
113			cyclestreetsNS.map.setZoom(17);
114		}
115	},
116
117
118// Private methods
119
120	// Function used to retrieve results from a Nominatim server
121	geocoderAjax: function (element, url, request, response) {
122
123	    // The url should already include the api key, this object c
123ollates additional query string arguments for the CycleStreets geocoder api call
124	    // http://www.cyclestreets.net/api/v2/geocoder/
125	    var urlParams = {
126			q: request.term, // I.e. the search string
127			limit: 12	// Ideally this and perhaps other parameters that are sent to the geocoder could be settings
128	    },
129
130	    // Useful var to help determine any bounding box for the geocoding search
131	    bounds,
132
133	    // Bind the term for use in caching
134	    term = request.term,
135
136	    // Does the search term include a comma, which can be used in searches to specify particular places, such as other countries
137	    containsComma = /,/.test(term),
138
139	    // Cache is stored with the element
140	    cache = element.data('autocompleteCache'),
141
142	    // Flag to detect if the term has been found in the cache
143            foundInCache = false;
144
145	    // When caching is enabled for this element
146	    if (cache) {
147
148		// Scan the cache for data matching the term
149		$.each(cache, function (key, data) {
150
151		    // Skip if no match
152		    if (term !== key) {return true;}
153
154		    // Apply the response callback
155		    response(data);
156
157		    // Flag found
158		    foundInCache = true;
159
160		    // End the search
161		    return false;
162		});
163	    }
164
165	    // End if the result has been found in the cache
166	    if (foundInCache) return;
167
168	    // Get any preferred bounds - they are packed as W,S,E,N
169	    if (window.cyclestreetsNS !== undefined && cyclestreetsNS.getPreferedNameSearchBoundingBox()) {
170	
171			// Set as bbox WSEN
172			urlParams.bbox = cyclestreetsNS.getPreferedNameSearchBoundingBox();
173	    }
174
175	    // If there is a map on the page then assume it will provide bounds
176	    else if ($("#map")) {
177	
178			// Callback that returns a csv: <left>,<top>,<right>,<bottom>
179			urlParams.bbox = autocomplete.mapBoundsCallBack();
180	    }
181
182	    // Country codes (commas present in search string skip the country restriction)
183	    if (!containsComma && window.cyclestreetsNS !== undefined && cyclestreetsNS.getGeocodingCountryCodes()) {urlParams.countrycodes = cyclestreetsNS.getGeocodingCountryCodes();}
184            // #!# Hotfix - needs to be dealt with upstream properly in the library:
185            urlParams.countrycodes = 'gb,ie';
186
187	    // Bounded
188	    if (window.cyclestreetsNS !== undefined && cyclestreetsNS.getBoundedSearch()) {urlParams.bounded = cyclestreetsNS.getBoundedSearch();}
189
190	    $.ajax({
191		url: url,
192
193		// The next two options configure jQuery to parse the jsonp returned by call
194		dataType: (window.cyclestreetsNS !== undefined && cyclestreetsNS.getUseJsonpTransport() ? 'jsonp' : 'json'),
195		// Nominatim supports the value of this parameter which wraps json output in a callback function.
196		// The callback function name is generated by jQuery, and parses the json.
197		jsonp: (window.cyclestreetsNS !== undefined && cyclestreetsNS.getUseJsonpTransport() ? 'callback' :  null),
198
199		data: urlParams,
200
201		// Provides a useful chance to show the api request url
202		beforeSend: function (jqXHR, settings) {
203
204		    // Skip cyclestreetsNS not present
205		    if (window.cyclestreetsNS === undefined) {return;}
206
207		    // Show the requested url
208		    cyclestreetsNS.debugtagmsg(settings.url);
209		},
210
211		// On success run returned data through a filter
212		success: function (data, textStatus, jqXHR) {
213
214		    // Declarations
215		    var results = [];
216
217		    // No data
218		    if (!data) {
219
220			// No results
221			results = [];
222
223		    } else if (data.error) {
224
225			// By using these values the user can see that an error has occurred
226			results = [{
227			    value: false,
228			    label: 'A geocoding error occurred',
229			    desc: data.error}];
230
231		    } else if (!data.features) {
232
233			// No results
234			results = [];
235
236		    } else {
237
238			// Render each feature
239			results = $.map(data.features, autocomplete.renderGeocoderResult);
240		    }
241
242		    // Cache the results in the element
243		    if (cache) {
244			cache[term] = results;
245			element.data('autocompleteCache', cache);
246		    }
247
248		    // Apply the response callback
249		    response (results);
250		},
251
252		// Autocomplete needs to call response() even when an error occurs to avoid falling into an invali
252d state.
253		error: function (jqXHR, textStatus, errorThrown) {
254
255		    // By using these values the user can see that an error has occurred
256		    response ([{
257			value: errorThrown,
258			label: "An error occurred: " + textStatus,
259			desc: "Error type: " + errorThrown}]);
260		},
261
262		// Handle some http error codes
263		statusCode: {
264		    404: function () {
265			alert( "The name lookup service cannot be found" );
266		    }
267		}
268	    });
269	},
270
271
272	// Bounds callback - retrieves current bbox of map
273	mapBoundsCallBack: function () {
274
275	    // Declarations
276	    var bounds;
277
278	    // Abandon if no map
279	    if (window.cyclestreetsNS === undefined || cyclestreetsNS.map === undefined) {return;}
280
281	    // Visible bounds of the map
282	    bounds = cyclestreetsNS.map.getBounds();
283
284	    // Expand the bounds to be as least as big as a city
285	    bounds = this.atLeastAsBigAsACity (bounds);
286		
287		// Return WSEN
288		return bounds.toBBoxString();
289	},
290	
291	
292	// Expand the bounds to be as least as big as a city
293	atLeastAsBigAsACity: function (bounds)
294	{
295	    // Declarations
296	    var centre = bounds.getCenter(),
297
298	    // City size in degrees NS, 1 degree = 100km, so 0.1 degree = 10km.
299	    sizeNSdegrees = 0.25,
300
301	    // Accommodate the forshortening of longitude
302	    sizeEWdegrees = sizeNSdegrees / Math.cos(centre.lat * Math.PI / 180);
303
304	    // Extend the bounds to the NW
305	    bounds = bounds.extend (L.latLng(centre.lat + sizeNSdegrees, centre.lng - sizeEWdegrees));
306
307	    // Extend the bounds to the SE
308	    bounds = bounds.extend (L.latLng(centre.lat - sizeNSdegrees, centre.lng + sizeEWdegrees));
309
310	    // Result
311	    return bounds;
312	},
313
314	// Used to process each result from the geocoder
315	renderGeocoderResult: function (item)
316	{
317	    // Form result object
318	    var result = {
319			value: item.properties.name + ', ' + item.properties.near,	// Feeding it back into the query seems to be the best way of getting a repeatable result.
320			label: item.properties.name,
321			desc: item.properties.near,
322			lat: item.geometry.coordinates[1],
323			lon: item.geometry.coordinates[0],
324			feature: item
325	    };
326		
327	    // Result
328	    return result;
329	}
330    };
331}(jQuery));
332

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.