Geocoder.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. import defined from '../../Core/defined.js';
  2. import defineProperties from '../../Core/defineProperties.js';
  3. import destroyObject from '../../Core/destroyObject.js';
  4. import DeveloperError from '../../Core/DeveloperError.js';
  5. import FeatureDetection from '../../Core/FeatureDetection.js';
  6. import knockout from '../../ThirdParty/knockout.js';
  7. import getElement from '../getElement.js';
  8. import GeocoderViewModel from './GeocoderViewModel.js';
  9. var startSearchPath = 'M29.772,26.433l-7.126-7.126c0.96-1.583,1.523-3.435,1.524-5.421C24.169,8.093,19.478,3.401,13.688,3.399C7.897,3.401,3.204,8.093,3.204,13.885c0,5.789,4.693,10.481,10.484,10.481c1.987,0,3.839-0.563,5.422-1.523l7.128,7.127L29.772,26.433zM7.203,13.885c0.006-3.582,2.903-6.478,6.484-6.486c3.579,0.008,6.478,2.904,6.484,6.486c-0.007,3.58-2.905,6.476-6.484,6.484C10.106,20.361,7.209,17.465,7.203,13.885z';
  10. var stopSearchPath = 'M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z';
  11. /**
  12. * A widget for finding addresses and landmarks, and flying the camera to them. Geocoding is
  13. * performed using the {@link http://msdn.microsoft.com/en-us/library/ff701715.aspx|Bing Maps Locations API}.
  14. *
  15. * @alias Geocoder
  16. * @constructor
  17. *
  18. * @param {Object} options Object with the following properties:
  19. * @param {Element|String} options.container The DOM element or ID that will contain the widget.
  20. * @param {Scene} options.scene The Scene instance to use.
  21. * @param {GeocoderService[]} [options.geocoderServices] The geocoder services to be used
  22. * @param {Boolean} [options.autoComplete = true] True if the geocoder should query as the user types to autocomplete
  23. * @param {Number} [options.flightDuration=1.5] The duration of the camera flight to an entered location, in seconds.
  24. * @param {Geocoder~DestinationFoundFunction} [options.destinationFound=GeocoderViewModel.flyToDestination] A callback function that is called after a successful geocode. If not supplied, the default behavior is to fly the camera to the result destination.
  25. */
  26. function Geocoder(options) {
  27. //>>includeStart('debug', pragmas.debug);
  28. if (!defined(options) || !defined(options.container)) {
  29. throw new DeveloperError('options.container is required.');
  30. }
  31. if (!defined(options.scene)) {
  32. throw new DeveloperError('options.scene is required.');
  33. }
  34. //>>includeEnd('debug');
  35. var container = getElement(options.container);
  36. var viewModel = new GeocoderViewModel(options);
  37. viewModel._startSearchPath = startSearchPath;
  38. viewModel._stopSearchPath = stopSearchPath;
  39. var form = document.createElement('form');
  40. form.setAttribute('data-bind', 'submit: search');
  41. var textBox = document.createElement('input');
  42. textBox.type = 'search';
  43. textBox.className = 'cesium-geocoder-input';
  44. textBox.setAttribute('placeholder', 'Enter an address or landmark...');
  45. textBox.setAttribute('data-bind', '\
  46. textInput: searchText,\
  47. disable: isSearchInProgress,\
  48. event: { keyup: handleKeyUp, keydown: handleKeyDown, mouseover: deselectSuggestion },\
  49. css: { "cesium-geocoder-input-wide" : keepExpanded || searchText.length > 0 },\
  50. hasFocus: _focusTextbox');
  51. this._onTextBoxFocus = function() {
  52. // as of 2016-10-19, setTimeout is required to ensure that the
  53. // text is focused on Safari 10
  54. setTimeout(function() {
  55. textBox.select();
  56. }, 0);
  57. };
  58. textBox.addEventListener('focus', this._onTextBoxFocus, false);
  59. form.appendChild(textBox);
  60. this._textBox = textBox;
  61. var searchButton = document.createElement('span');
  62. searchButton.className = 'cesium-geocoder-searchButton';
  63. searchButton.setAttribute('data-bind', '\
  64. click: search,\
  65. cesiumSvgPath: { path: isSearchInProgress ? _stopSearchPath : _startSearchPath, width: 32, height: 32 }');
  66. form.appendChild(searchButton);
  67. container.appendChild(form);
  68. var searchSuggestionsContainer = document.createElement('div');
  69. searchSuggestionsContainer.className = 'search-results';
  70. searchSuggestionsContainer.setAttribute('data-bind', 'visible: _suggestionsVisible');
  71. var suggestionsList = document.createElement('ul');
  72. suggestionsList.setAttribute('data-bind', 'foreach: _suggestions');
  73. var suggestions = document.createElement('li');
  74. suggestionsList.appendChild(suggestions);
  75. suggestions.setAttribute('data-bind', 'text: $data.displayName, \
  76. click: $parent.activateSuggestion, \
  77. event: { mouseover: $parent.handleMouseover}, \
  78. css: { active: $data === $parent._selectedSuggestion }');
  79. searchSuggestionsContainer.appendChild(suggestionsList);
  80. container.appendChild(searchSuggestionsContainer);
  81. knockout.applyBindings(viewModel, form);
  82. knockout.applyBindings(viewModel, searchSuggestionsContainer);
  83. this._container = container;
  84. this._searchSuggestionsContainer = searchSuggestionsContainer;
  85. this._viewModel = viewModel;
  86. this._form = form;
  87. this._onInputBegin = function(e) {
  88. if (!container.contains(e.target)) {
  89. viewModel._focusTextbox = false;
  90. viewModel.hideSuggestions();
  91. }
  92. };
  93. this._onInputEnd = function(e) {
  94. if (container.contains(e.target)) {
  95. viewModel._focusTextbox = true;
  96. viewModel.showSuggestions();
  97. }
  98. };
  99. //We subscribe to both begin and end events in order to give the text box
  100. //focus no matter where on the widget is clicked.
  101. if (FeatureDetection.supportsPointerEvents()) {
  102. document.addEventListener('pointerdown', this._onInputBegin, true);
  103. document.addEventListener('pointerup', this._onInputEnd, true);
  104. document.addEventListener('pointercancel', this._onInputEnd, true);
  105. } else {
  106. document.addEventListener('mousedown', this._onInputBegin, true);
  107. document.addEventListener('mouseup', this._onInputEnd, true);
  108. document.addEventListener('touchstart', this._onInputBegin, true);
  109. document.addEventListener('touchend', this._onInputEnd, true);
  110. document.addEventListener('touchcancel', this._onInputEnd, true);
  111. }
  112. }
  113. defineProperties(Geocoder.prototype, {
  114. /**
  115. * Gets the parent container.
  116. * @memberof Geocoder.prototype
  117. *
  118. * @type {Element}
  119. */
  120. container : {
  121. get : function() {
  122. return this._container;
  123. }
  124. },
  125. /**
  126. * Gets the parent container.
  127. * @memberof Geocoder.prototype
  128. *
  129. * @type {Element}
  130. */
  131. searchSuggestionsContainer : {
  132. get : function() {
  133. return this._searchSuggestionsContainer;
  134. }
  135. },
  136. /**
  137. * Gets the view model.
  138. * @memberof Geocoder.prototype
  139. *
  140. * @type {GeocoderViewModel}
  141. */
  142. viewModel : {
  143. get : function() {
  144. return this._viewModel;
  145. }
  146. }
  147. });
  148. /**
  149. * @returns {Boolean} true if the object has been destroyed, false otherwise.
  150. */
  151. Geocoder.prototype.isDestroyed = function() {
  152. return false;
  153. };
  154. /**
  155. * Destroys the widget. Should be called if permanently
  156. * removing the widget from layout.
  157. */
  158. Geocoder.prototype.destroy = function() {
  159. if (FeatureDetection.supportsPointerEvents()) {
  160. document.removeEventListener('pointerdown', this._onInputBegin, true);
  161. document.removeEventListener('pointerup', this._onInputEnd, true);
  162. } else {
  163. document.removeEventListener('mousedown', this._onInputBegin, true);
  164. document.removeEventListener('mouseup', this._onInputEnd, true);
  165. document.removeEventListener('touchstart', this._onInputBegin, true);
  166. document.removeEventListener('touchend', this._onInputEnd, true);
  167. }
  168. this._viewModel.destroy();
  169. knockout.cleanNode(this._form);
  170. knockout.cleanNode(this._searchSuggestionsContainer);
  171. this._container.removeChild(this._form);
  172. this._container.removeChild(this._searchSuggestionsContainer);
  173. this._textBox.removeEventListener('focus', this._onTextBoxFocus, false);
  174. return destroyObject(this);
  175. };
  176. /**
  177. * A function that handles the result of a successful geocode.
  178. * @callback Geocoder~DestinationFoundFunction
  179. * @param {GeocoderViewModel} viewModel The view model.
  180. * @param {Cartesian3|Rectangle} destination The destination result of the geocode.
  181. */
  182. export default Geocoder;