GeocoderViewModel.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. /*global define*/
  2. define([
  3. '../../Core/BingMapsApi',
  4. '../../Core/defaultValue',
  5. '../../Core/defined',
  6. '../../Core/defineProperties',
  7. '../../Core/DeveloperError',
  8. '../../Core/jsonp',
  9. '../../Core/Matrix4',
  10. '../../Core/Rectangle',
  11. '../../ThirdParty/knockout',
  12. '../../ThirdParty/when',
  13. '../createCommand'
  14. ], function(
  15. BingMapsApi,
  16. defaultValue,
  17. defined,
  18. defineProperties,
  19. DeveloperError,
  20. jsonp,
  21. Matrix4,
  22. Rectangle,
  23. knockout,
  24. when,
  25. createCommand) {
  26. "use strict";
  27. /**
  28. * The view model for the {@link Geocoder} widget.
  29. * @alias GeocoderViewModel
  30. * @constructor
  31. *
  32. * @param {Object} options Object with the following properties:
  33. * @param {Scene} options.scene The Scene instance to use.
  34. * @param {String} [options.url='//dev.virtualearth.net'] The base URL of the Bing Maps API.
  35. * @param {String} [options.key] The Bing Maps key for your application, which can be
  36. * created at {@link https://www.bingmapsportal.com}.
  37. * If this parameter is not provided, {@link BingMapsApi.defaultKey} is used.
  38. * If {@link BingMapsApi.defaultKey} is undefined as well, a message is
  39. * written to the console reminding you that you must create and supply a Bing Maps
  40. * key as soon as possible. Please do not deploy an application that uses
  41. * this widget without creating a separate key for your application.
  42. * @param {Number} [options.flightDuration=1.5] The duration of the camera flight to an entered location, in seconds.
  43. */
  44. var GeocoderViewModel = function(options) {
  45. //>>includeStart('debug', pragmas.debug);
  46. if (!defined(options) || !defined(options.scene)) {
  47. throw new DeveloperError('options.scene is required.');
  48. }
  49. //>>includeEnd('debug');
  50. this._url = defaultValue(options.url, '//dev.virtualearth.net/');
  51. if (this._url.length > 0 && this._url[this._url.length - 1] !== '/') {
  52. this._url += '/';
  53. }
  54. this._key = BingMapsApi.getKey(options.key);
  55. this._scene = options.scene;
  56. this._flightDuration = defaultValue(options.flightDuration, 1.5);
  57. this._searchText = '';
  58. this._isSearchInProgress = false;
  59. this._geocodeInProgress = undefined;
  60. var that = this;
  61. this._searchCommand = createCommand(function() {
  62. if (that.isSearchInProgress) {
  63. cancelGeocode(that);
  64. } else {
  65. geocode(that);
  66. }
  67. });
  68. knockout.track(this, ['_searchText', '_isSearchInProgress']);
  69. /**
  70. * Gets a value indicating whether a search is currently in progress. This property is observable.
  71. *
  72. * @type {Boolean}
  73. */
  74. this.isSearchInProgress = undefined;
  75. knockout.defineProperty(this, 'isSearchInProgress', {
  76. get : function() {
  77. return this._isSearchInProgress;
  78. }
  79. });
  80. /**
  81. * Gets or sets the text to search for.
  82. *
  83. * @type {String}
  84. */
  85. this.searchText = undefined;
  86. knockout.defineProperty(this, 'searchText', {
  87. get : function() {
  88. if (this.isSearchInProgress) {
  89. return 'Searching...';
  90. }
  91. return this._searchText;
  92. },
  93. set : function(value) {
  94. //>>includeStart('debug', pragmas.debug);
  95. if (typeof value !== 'string') {
  96. throw new DeveloperError('value must be a valid string.');
  97. }
  98. //>>includeEnd('debug');
  99. this._searchText = value;
  100. }
  101. });
  102. /**
  103. * Gets or sets the the duration of the camera flight in seconds.
  104. * A value of zero causes the camera to instantly switch to the geocoding location.
  105. *
  106. * @type {Number}
  107. * @default 1.5
  108. */
  109. this.flightDuration = undefined;
  110. knockout.defineProperty(this, 'flightDuration', {
  111. get : function() {
  112. return this._flightDuration;
  113. },
  114. set : function(value) {
  115. //>>includeStart('debug', pragmas.debug);
  116. if (value < 0) {
  117. throw new DeveloperError('value must be positive.');
  118. }
  119. //>>includeEnd('debug');
  120. this._flightDuration = value;
  121. }
  122. });
  123. };
  124. defineProperties(GeocoderViewModel.prototype, {
  125. /**
  126. * Gets the Bing maps url.
  127. * @memberof GeocoderViewModel.prototype
  128. *
  129. * @type {String}
  130. */
  131. url : {
  132. get : function() {
  133. return this._url;
  134. }
  135. },
  136. /**
  137. * Gets the Bing maps key.
  138. * @memberof GeocoderViewModel.prototype
  139. *
  140. * @type {String}
  141. */
  142. key : {
  143. get : function() {
  144. return this._key;
  145. }
  146. },
  147. /**
  148. * Gets the scene to control.
  149. * @memberof GeocoderViewModel.prototype
  150. *
  151. * @type {Scene}
  152. */
  153. scene : {
  154. get : function() {
  155. return this._scene;
  156. }
  157. },
  158. /**
  159. * Gets the Command that is executed when the button is clicked.
  160. * @memberof GeocoderViewModel.prototype
  161. *
  162. * @type {Command}
  163. */
  164. search : {
  165. get : function() {
  166. return this._searchCommand;
  167. }
  168. }
  169. });
  170. function geocode(viewModel) {
  171. var query = viewModel.searchText;
  172. if (/^\s*$/.test(query)) {
  173. //whitespace string
  174. return;
  175. }
  176. viewModel._isSearchInProgress = true;
  177. var promise = jsonp(viewModel._url + 'REST/v1/Locations', {
  178. parameters : {
  179. query : query,
  180. key : viewModel._key
  181. },
  182. callbackParameterName : 'jsonp'
  183. });
  184. var geocodeInProgress = viewModel._geocodeInProgress = when(promise, function(result) {
  185. if (geocodeInProgress.cancel) {
  186. return;
  187. }
  188. viewModel._isSearchInProgress = false;
  189. if (result.resourceSets.length === 0) {
  190. viewModel.searchText = viewModel._searchText + ' (not found)';
  191. return;
  192. }
  193. var resourceSet = result.resourceSets[0];
  194. if (resourceSet.resources.length === 0) {
  195. viewModel.searchText = viewModel._searchText + ' (not found)';
  196. return;
  197. }
  198. var resource = resourceSet.resources[0];
  199. viewModel._searchText = resource.name;
  200. var bbox = resource.bbox;
  201. var south = bbox[0];
  202. var west = bbox[1];
  203. var north = bbox[2];
  204. var east = bbox[3];
  205. var rectangle = Rectangle.fromDegrees(west, south, east, north);
  206. var camera = viewModel._scene.camera;
  207. var position = camera.getRectangleCameraCoordinates(rectangle);
  208. if (!defined(position)) {
  209. // This can happen during a scene mode transition.
  210. return;
  211. }
  212. viewModel._scene.camera.flyTo({
  213. destination : position,
  214. duration : viewModel._flightDuration,
  215. endTransform : Matrix4.IDENTITY,
  216. convert : false
  217. });
  218. }, function() {
  219. if (geocodeInProgress.cancel) {
  220. return;
  221. }
  222. viewModel._isSearchInProgress = false;
  223. viewModel.searchText = viewModel._searchText + ' (error)';
  224. });
  225. }
  226. function cancelGeocode(viewModel) {
  227. viewModel._isSearchInProgress = false;
  228. if (defined(viewModel._geocodeInProgress)) {
  229. viewModel._geocodeInProgress.cancel = true;
  230. viewModel._geocodeInProgress = undefined;
  231. }
  232. }
  233. return GeocoderViewModel;
  234. });