provider.dynamic.google.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. /**
  2. * DevExtreme (ui/map/provider.dynamic.google.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var $ = require("../../core/renderer");
  11. var window = require("../../core/utils/window").getWindow();
  12. var noop = require("../../core/utils/common").noop;
  13. var devices = require("../../core/devices");
  14. var Promise = require("../../core/polyfills/promise");
  15. var extend = require("../../core/utils/extend").extend;
  16. var map = require("../../core/utils/iterator").map;
  17. var DynamicProvider = require("./provider.dynamic");
  18. var errors = require("../widget/ui.errors");
  19. var Color = require("../../color");
  20. var ajax = require("../../core/utils/ajax");
  21. var isDefined = require("../../core/utils/type").isDefined;
  22. var GOOGLE_MAP_READY = "_googleScriptReady";
  23. var GOOGLE_URL = "https://maps.googleapis.com/maps/api/js?callback=" + GOOGLE_MAP_READY;
  24. var INFO_WINDOW_CLASS = "gm-style-iw";
  25. var CustomMarker;
  26. var initCustomMarkerClass = function() {
  27. CustomMarker = function(options) {
  28. this._position = options.position;
  29. this._offset = options.offset;
  30. this._$overlayContainer = $("<div>").css({
  31. position: "absolute",
  32. display: "none",
  33. cursor: "pointer"
  34. }).append(options.html);
  35. this.setMap(options.map)
  36. };
  37. CustomMarker.prototype = new google.maps.OverlayView;
  38. CustomMarker.prototype.onAdd = function() {
  39. var $pane = $(this.getPanes().overlayMouseTarget);
  40. $pane.append(this._$overlayContainer);
  41. this._clickListener = google.maps.event.addDomListener(this._$overlayContainer.get(0), "click", function(e) {
  42. google.maps.event.trigger(this, "click");
  43. e.preventDefault()
  44. }.bind(this));
  45. this.draw()
  46. };
  47. CustomMarker.prototype.onRemove = function() {
  48. google.maps.event.removeListener(this._clickListener);
  49. this._$overlayContainer.remove()
  50. };
  51. CustomMarker.prototype.draw = function() {
  52. var position = this.getProjection().fromLatLngToDivPixel(this._position);
  53. this._$overlayContainer.css({
  54. left: position.x + this._offset.left,
  55. top: position.y + this._offset.top,
  56. display: "block"
  57. })
  58. }
  59. };
  60. var googleMapsLoaded = function() {
  61. return window.google && window.google.maps
  62. };
  63. var googleMapsLoader;
  64. var GoogleProvider = DynamicProvider.inherit({
  65. _mapType: function(type) {
  66. var mapTypes = {
  67. hybrid: google.maps.MapTypeId.HYBRID,
  68. roadmap: google.maps.MapTypeId.ROADMAP,
  69. satellite: google.maps.MapTypeId.SATELLITE
  70. };
  71. return mapTypes[type] || mapTypes.hybrid
  72. },
  73. _movementMode: function(type) {
  74. var movementTypes = {
  75. driving: google.maps.TravelMode.DRIVING,
  76. walking: google.maps.TravelMode.WALKING
  77. };
  78. return movementTypes[type] || movementTypes.driving
  79. },
  80. _resolveLocation: function(location) {
  81. return new Promise(function(resolve) {
  82. var latLng = this._getLatLng(location);
  83. if (latLng) {
  84. resolve(new google.maps.LatLng(latLng.lat, latLng.lng))
  85. } else {
  86. this._geocodeLocation(location).then(function(geocodedLocation) {
  87. resolve(geocodedLocation)
  88. })
  89. }
  90. }.bind(this))
  91. },
  92. _geocodedLocations: {},
  93. _geocodeLocationImpl: function(location) {
  94. return new Promise(function(resolve) {
  95. if (!isDefined(location)) {
  96. resolve(new google.maps.LatLng(0, 0));
  97. return
  98. }
  99. var geocoder = new google.maps.Geocoder;
  100. geocoder.geocode({
  101. address: location
  102. }, function(results, status) {
  103. if (status === google.maps.GeocoderStatus.OK) {
  104. resolve(results[0].geometry.location)
  105. } else {
  106. errors.log("W1006", status);
  107. resolve(new google.maps.LatLng(0, 0))
  108. }
  109. })
  110. })
  111. },
  112. _normalizeLocation: function(location) {
  113. return {
  114. lat: location.lat(),
  115. lng: location.lng()
  116. }
  117. },
  118. _normalizeLocationRect: function(locationRect) {
  119. return {
  120. northEast: this._normalizeLocation(locationRect.getNorthEast()),
  121. southWest: this._normalizeLocation(locationRect.getSouthWest())
  122. }
  123. },
  124. _loadImpl: function() {
  125. return new Promise(function(resolve) {
  126. if (googleMapsLoaded()) {
  127. resolve()
  128. } else {
  129. if (!googleMapsLoader) {
  130. googleMapsLoader = this._loadMapScript()
  131. }
  132. googleMapsLoader.then(function() {
  133. if (googleMapsLoaded()) {
  134. resolve();
  135. return
  136. }
  137. this._loadMapScript().then(resolve)
  138. }.bind(this))
  139. }
  140. }.bind(this)).then(function() {
  141. initCustomMarkerClass()
  142. })
  143. },
  144. _loadMapScript: function() {
  145. return new Promise(function(resolve) {
  146. var key = this._keyOption("google");
  147. window[GOOGLE_MAP_READY] = resolve;
  148. ajax.sendRequest({
  149. url: GOOGLE_URL + (key ? "&key=" + key : ""),
  150. dataType: "script"
  151. })
  152. }.bind(this)).then(function() {
  153. try {
  154. delete window[GOOGLE_MAP_READY]
  155. } catch (e) {
  156. window[GOOGLE_MAP_READY] = void 0
  157. }
  158. })
  159. },
  160. _init: function() {
  161. return new Promise(function(resolve) {
  162. this._resolveLocation(this._option("center")).then(function(center) {
  163. var showDefaultUI = this._option("controls");
  164. this._map = new google.maps.Map(this._$container[0], {
  165. zoom: this._option("zoom"),
  166. center: center,
  167. disableDefaultUI: !showDefaultUI
  168. });
  169. var listener = google.maps.event.addListener(this._map, "idle", function() {
  170. resolve(listener)
  171. })
  172. }.bind(this))
  173. }.bind(this)).then(function(listener) {
  174. google.maps.event.removeListener(listener)
  175. })
  176. },
  177. _attachHandlers: function() {
  178. this._boundsChangeListener = google.maps.event.addListener(this._map, "bounds_changed", this._boundsChangeHandler.bind(this));
  179. this._clickListener = google.maps.event.addListener(this._map, "click", this._clickActionHandler.bind(this))
  180. },
  181. _boundsChangeHandler: function() {
  182. var bounds = this._map.getBounds();
  183. this._option("bounds", this._normalizeLocationRect(bounds));
  184. var center = this._map.getCenter();
  185. this._option("center", this._normalizeLocation(center));
  186. if (!this._preventZoomChangeEvent) {
  187. this._option("zoom", this._map.getZoom())
  188. }
  189. },
  190. _clickActionHandler: function(e) {
  191. this._fireClickAction({
  192. location: this._normalizeLocation(e.latLng)
  193. })
  194. },
  195. updateDimensions: function() {
  196. var center = this._option("center");
  197. google.maps.event.trigger(this._map, "resize");
  198. this._option("center", center);
  199. return this.updateCenter()
  200. },
  201. updateMapType: function() {
  202. this._map.setMapTypeId(this._mapType(this._option("type")));
  203. return Promise.resolve()
  204. },
  205. updateBounds: function() {
  206. return Promise.all([this._resolveLocation(this._option("bounds.northEast")), this._resolveLocation(this._option("bounds.southWest"))]).then(function(result) {
  207. var bounds = new google.maps.LatLngBounds;
  208. bounds.extend(result[0]);
  209. bounds.extend(result[1]);
  210. this._map.fitBounds(bounds)
  211. }.bind(this))
  212. },
  213. updateCenter: function() {
  214. return this._resolveLocation(this._option("center")).then(function(center) {
  215. this._map.setCenter(center);
  216. this._option("center", this._normalizeLocation(center))
  217. }.bind(this))
  218. },
  219. updateZoom: function() {
  220. this._map.setZoom(this._option("zoom"));
  221. return Promise.resolve()
  222. },
  223. updateControls: function() {
  224. var showDefaultUI = this._option("controls");
  225. this._map.setOptions({
  226. disableDefaultUI: !showDefaultUI
  227. });
  228. return Promise.resolve()
  229. },
  230. isEventsCanceled: function(e) {
  231. var gestureHandling = this._map && this._map.get("gestureHandling");
  232. var isInfoWindowContent = $(e.target).closest(".".concat(INFO_WINDOW_CLASS)).length > 0;
  233. if (isInfoWindowContent || "desktop" !== devices.real().deviceType && "cooperative" === gestureHandling) {
  234. return false
  235. }
  236. return this.callBase()
  237. },
  238. _renderMarker: function(options) {
  239. return this._resolveLocation(options.location).then(function(location) {
  240. var marker;
  241. if (options.html) {
  242. marker = new CustomMarker({
  243. map: this._map,
  244. position: location,
  245. html: options.html,
  246. offset: extend({
  247. top: 0,
  248. left: 0
  249. }, options.htmlOffset)
  250. })
  251. } else {
  252. marker = new google.maps.Marker({
  253. position: location,
  254. map: this._map,
  255. icon: options.iconSrc || this._option("markerIconSrc")
  256. })
  257. }
  258. var infoWindow = this._renderTooltip(marker, options.tooltip);
  259. var listener;
  260. if (options.onClick || options.tooltip) {
  261. var markerClickAction = this._mapWidget._createAction(options.onClick || noop);
  262. var markerNormalizedLocation = this._normalizeLocation(location);
  263. listener = google.maps.event.addListener(marker, "click", function() {
  264. markerClickAction({
  265. location: markerNormalizedLocation
  266. });
  267. if (infoWindow) {
  268. infoWindow.open(this._map, marker)
  269. }
  270. }.bind(this))
  271. }
  272. return {
  273. location: location,
  274. marker: marker,
  275. listener: listener
  276. }
  277. }.bind(this))
  278. },
  279. _renderTooltip: function(marker, options) {
  280. if (!options) {
  281. return
  282. }
  283. options = this._parseTooltipOptions(options);
  284. var infoWindow = new google.maps.InfoWindow({
  285. content: options.text
  286. });
  287. if (options.visible) {
  288. infoWindow.open(this._map, marker)
  289. }
  290. return infoWindow
  291. },
  292. _destroyMarker: function(marker) {
  293. marker.marker.setMap(null);
  294. if (marker.listener) {
  295. google.maps.event.removeListener(marker.listener)
  296. }
  297. },
  298. _renderRoute: function(options) {
  299. return Promise.all(map(options.locations, function(point) {
  300. return this._resolveLocation(point)
  301. }.bind(this))).then(function(locations) {
  302. return new Promise(function(resolve) {
  303. var origin = locations.shift();
  304. var destination = locations.pop();
  305. var waypoints = map(locations, function(location) {
  306. return {
  307. location: location,
  308. stopover: true
  309. }
  310. });
  311. var request = {
  312. origin: origin,
  313. destination: destination,
  314. waypoints: waypoints,
  315. optimizeWaypoints: true,
  316. travelMode: this._movementMode(options.mode)
  317. };
  318. (new google.maps.DirectionsService).route(request, function(response, status) {
  319. if (status === google.maps.DirectionsStatus.OK) {
  320. var color = new Color(options.color || this._defaultRouteColor()).toHex();
  321. var directionOptions = {
  322. directions: response,
  323. map: this._map,
  324. suppressMarkers: true,
  325. preserveViewport: true,
  326. polylineOptions: {
  327. strokeWeight: options.weight || this._defaultRouteWeight(),
  328. strokeOpacity: options.opacity || this._defaultRouteOpacity(),
  329. strokeColor: color
  330. }
  331. };
  332. var route = new google.maps.DirectionsRenderer(directionOptions);
  333. var bounds = response.routes[0].bounds;
  334. resolve({
  335. instance: route,
  336. northEast: bounds.getNorthEast(),
  337. southWest: bounds.getSouthWest()
  338. })
  339. } else {
  340. errors.log("W1006", status);
  341. resolve({
  342. instance: new google.maps.DirectionsRenderer({})
  343. })
  344. }
  345. }.bind(this))
  346. }.bind(this))
  347. }.bind(this))
  348. },
  349. _destroyRoute: function(routeObject) {
  350. routeObject.instance.setMap(null)
  351. },
  352. _fitBounds: function() {
  353. this._updateBounds();
  354. if (this._bounds && this._option("autoAdjust")) {
  355. var zoomBeforeFitting = this._map.getZoom();
  356. this._preventZoomChangeEvent = true;
  357. this._map.fitBounds(this._bounds);
  358. this._boundsChangeHandler();
  359. var zoomAfterFitting = this._map.getZoom();
  360. if (zoomBeforeFitting < zoomAfterFitting) {
  361. this._map.setZoom(zoomBeforeFitting)
  362. } else {
  363. this._option("zoom", zoomAfterFitting)
  364. }
  365. delete this._preventZoomChangeEvent
  366. }
  367. return Promise.resolve()
  368. },
  369. _extendBounds: function(location) {
  370. if (this._bounds) {
  371. this._bounds.extend(location)
  372. } else {
  373. this._bounds = new google.maps.LatLngBounds;
  374. this._bounds.extend(location)
  375. }
  376. },
  377. clean: function() {
  378. if (this._map) {
  379. google.maps.event.removeListener(this._boundsChangeListener);
  380. google.maps.event.removeListener(this._clickListener);
  381. this._clearMarkers();
  382. this._clearRoutes();
  383. delete this._map;
  384. this._$container.empty()
  385. }
  386. return Promise.resolve()
  387. }
  388. });
  389. module.exports = GoogleProvider;