1. 1 : const Situm = require('@situm/cordova.situm');
  2. 2 :
  3. 3 : /**
  4. 4 : * Here's an example on how to use this controller class:
  5. 5 : * <div id="codeSnippet">
  6. 6 : * <pre id="textToCopy">
  7. 7 : * <p style="font-weight: bold;position: absolute;top: 10px;left: 10px;text-decoration: underline">JAVASCRIPT</p>
  8. 8 : * cordova.plugins.MapView.onLoad((controller: any) => {
  9. 9 : * // Once the MapView was loaded you can start managing our map by:
  10. 10 : *
  11. 11 : * // 1. Sending actions like selecting or navigating to a POI in a building:
  12. 12 : * // controller.selectPoi('YOUR_POI_IDENTIFIER');
  13. 13 : * // controller.navigateToPoi('YOUR_POI_IDENTIFIER');
  14. 14 : *
  15. 15 : * // 2. Listen to events that take place inside our map like a POI being selected or deselected:
  16. 16 : * controller.onPoiSelected((poiSelectedResult: any) => {
  17. 17 : * console.log("EXAMPLE> onPoiSelected -> ", poiSelectedResult);
  18. 18 : * });
  19. 19 : *
  20. 20 : * controller.onPoiDeselected((poiDeselectedResult: any) => {
  21. 21 : * console.log("EXAMPLE> onPoiDeselected -> ", poiDeselectedResult);
  22. 22 : * });
  23. 23 : * });
  24. 24 : * </pre>
  25. 25 : *
  26. 26 : * <button id="copySnippetButton">Copy</button>
  27. 27 : * </div>
  28. 28 : *
  29. 29 : * <script>
  30. 30 : document.getElementById("copySnippetButton").addEventListener("click", function() {
  31. 31 : var textToCopy = document.getElementById("textToCopy");
  32. 32 :
  33. 33 : var range = document.createRange();
  34. 34 : range.selectNode(textToCopy);
  35. 35 : window.getSelection().removeAllRanges();
  36. 36 : window.getSelection().addRange(range);
  37. 37 :
  38. 38 : document.execCommand("copy");
  39. 39 : window.getSelection().removeAllRanges();
  40. 40 : this.textContent = "Copied!";
  41. 41 : });
  42. 42 : </script>
  43. 43 : *
  44. 44 : *
  45. 45 : * @namespace MapViewControllerImpl
  46. 46 : * @name MapViewController
  47. 47 : */
  48. 48 : class MapViewControllerImpl {
  49. 49 : _onLoadCallback = undefined;
  50. 50 : _onPoiSelectedCallback = undefined;
  51. 51 : _onPoiDeselectedCallback = undefined;
  52. 52 : _buildings = undefined;
  53. 53 : _mapView = undefined;
  54. 54 : _isNavigating = false;
  55. 55 : _navigationType = "";
  56. 56 :
  57. 57 : constructor() {
  58. 58 : Situm.internalSetEventDelegate(this._handleSdkNativeEvents.bind(this));
  59. 59 : }
  60. 60 :
  61. 61 : _prepare(mapView) {
  62. 62 : this._mapView = mapView;
  63. 63 : let useViewerNavigation = mapView.getAttribute('use-viewer-navigation')
  64. 64 : ? mapView.getAttribute('use-viewer-navigation')
  65. 65 : : null;
  66. 66 : if (useViewerNavigation != null) {
  67. 67 : this._setNavigationType(useViewerNavigation);
  68. 68 : }
  69. 69 : }
  70. 70 :
  71. 71 : _setNavigationType(useViewerNavigation) {
  72. 72 : if (useViewerNavigation) {
  73. 73 : if (useViewerNavigation === "true") {
  74. 74 : this._navigationType = 'webAssembly';
  75. 75 : } else {
  76. 76 : this._navigationType = 'sdk';
  77. 77 : }
  78. 78 : }
  79. 79 :
  80. 80 : }
  81. 81 :
  82. 82 : _setOnLoadCallback(callback) {
  83. 83 : this._onLoadCallback = callback;
  84. 84 : }
  85. 85 :
  86. 86 : _sendMessageToViewer(type, payload) {
  87. 87 : let message = {
  88. 88 : type: type,
  89. 89 : payload: payload
  90. 90 : };
  91. 91 : if (this._mapView && this._mapView.firstElementChild) {
  92. 92 : this._mapView.firstElementChild.contentWindow.postMessage(
  93. 93 : message,
  94. 94 : this._mapView._getViewerDomain()
  95. 95 : );
  96. 96 : }
  97. 97 : }
  98. 98 :
  99. 99 : _getDeviceId(callback) {
  100. 100 : Situm.getDeviceId(
  101. 101 : (deviceId) => callback(deviceId),
  102. 102 : () => {
  103. 103 : console.error('Error retrieving the device id.');
  104. 104 : callback(null);
  105. 105 : }
  106. 106 : );
  107. 107 : }
  108. 108 :
  109. 109 : // ==================================================
  110. 110 : // SDK MESSAGES:
  111. 111 : // ==================================================
  112. 112 :
  113. 113 : _handleSdkNativeEvents(eventName, payload) {
  114. 114 : switch (eventName) {
  115. 115 : case 'onLocationUpdate':
  116. 116 : // TODO: iOS is sending messages here not related to Location. Check
  117. 117 : // some fields to avoid assuming that we receive an object of type Location.
  118. 118 : if (payload.buildingIdentifier && payload.position) {
  119. 119 : this._handleOnLocationUpdate(payload);
  120. 120 : } else if (payload.statusName) {
  121. 121 : this._handleOnLocationStatus(payload);
  122. 122 : }
  123. 123 : break;
  124. 124 : }
  125. 125 : }
  126. 126 :
  127. 127 : _handleOnLocationUpdate(payload) {
  128. 128 : this._sendMessageToViewer('location.update', payload);
  129. 129 : if (this._isNavigating) {
  130. 130 : Situm.updateNavigationWithLocation(
  131. 131 : [payload],
  132. 132 : () => {
  133. 133 : // Do nothing.
  134. 134 : },
  135. 135 : () => {
  136. 136 : console.error('Error at updateNavigationWithLocation');
  137. 137 : }
  138. 138 : );
  139. 139 : }
  140. 140 : }
  141. 141 :
  142. 142 : _handleOnLocationStatus(payload) {
  143. 143 : this._sendMessageToViewer('location.update_status', {
  144. 144 : status: payload.statusName
  145. 145 : });
  146. 146 : }
  147. 147 :
  148. 148 : // ==================================================
  149. 149 : // MAP-VIEWER MESSAGES:
  150. 150 : // ==================================================
  151. 151 :
  152. 152 : _handleMapViewMessages(m) {
  153. 153 : switch (m.type) {
  154. 154 : case 'app.map_is_ready':
  155. 155 : if (
  156. 156 : this._onLoadCallback &&
  157. 157 : typeof this._onLoadCallback === 'function'
  158. 158 : ) {
  159. 159 : this._onLoadCallback(this);
  160. 160 : console.debug('Map is ready!');
  161. 161 : }
  162. 162 : if (this._navigationType) {
  163. 163 : this._sendNavigationConfig(this._navigationType);
  164. 164 : }
  165. 165 : break;
  166. 166 : case 'cartography.poi_selected':
  167. 167 : console.debug(`poi (${m.payload.identifier}) was selected`);
  168. 168 : const poiSelectedResult = {
  169. 169 : poi: {
  170. 170 : identifier: m.payload.identifier,
  171. 171 : buildingIdentifier: m.payload.buildingIdentifier
  172. 172 : }
  173. 173 : };
  174. 174 : this._onPoiSelectedCallback(poiSelectedResult);
  175. 175 : break;
  176. 176 : case 'cartography.poi_deselected':
  177. 177 : const poiDeselectedResult = {
  178. 178 : poi: {
  179. 179 : identifier: m.payload.identifier,
  180. 180 : buildingIdentifier: m.payload.buildingIdentifier
  181. 181 : }
  182. 182 : };
  183. 183 : this._onPoiDeselectedCallback(poiDeselectedResult);
  184. 184 : break;
  185. 185 : case 'directions.requested':
  186. 186 : this._onDirectionsRequested(m.payload);
  187. 187 : break;
  188. 188 : case 'navigation.requested':
  189. 189 : this._onNavigationRequested(m.payload);
  190. 190 : break;
  191. 191 : case 'navigation.stopped':
  192. 192 : this._onNavigationCancel();
  193. 193 : break;
  194. 194 : case 'viewer.navigation.started':
  195. 195 : this._onViewerNavigationStarted(m.payload);
  196. 196 : break;
  197. 197 : case 'viewer.navigation.updated':
  198. 198 : this._onViewerNavigationUpdated(m.payload);
  199. 199 : break;
  200. 200 : case 'viewer.navigation.stopped':
  201. 201 : this._onViewerNavigationStopped(m.payload);
  202. 202 : break;
  203. 203 : default:
  204. 204 : console.debug('Got unmanaged message: ', m);
  205. 205 : break;
  206. 206 : }
  207. 207 : }
  208. 208 :
  209. 209 : // Fetch the given building and return it or undefined if not found.
  210. 210 : _ensureBuilding(buildingId, callback) {
  211. 211 : if (this._buildings) {
  212. 212 : let building = this._buildings.find(
  213. 213 : (b) => b.buildingIdentifier == buildingId
  214. 214 : );
  215. 215 : callback(building);
  216. 216 : } else {
  217. 217 : // Fetch buildings and calculate route.
  218. 218 : cordova.plugins.Situm.fetchBuildings(
  219. 219 : (res) => {
  220. 220 : this._buildings = res;
  221. 221 : let building = this._buildings.find(
  222. 222 : (b) => b.buildingIdentifier == buildingId
  223. 223 : );
  224. 224 : callback(building);
  225. 225 : },
  226. 226 : (err) => {
  227. 227 : callback(undefined);
  228. 228 : }
  229. 229 : );
  230. 230 : }
  231. 231 : }
  232. 232 :
  233. 233 : // DIRECTIONS:
  234. 234 :
  235. 235 : _onDirectionsRequested(payload) {
  236. 236 : let directionsRequest = payload.directionsRequest;
  237. 237 : let mapViewerData = {
  238. 238 : identifier: payload.identifier,
  239. 239 : originIdentifier: payload.originIdentifier,
  240. 240 : destinationIdentifier: payload.destinationIdentifier,
  241. 241 : type: directionsRequest.accessibilityMode
  242. 242 : };
  243. 243 : this._ensureBuilding(payload.buildingIdentifier, (building) => {
  244. 244 : if (building) {
  245. 245 : Situm.requestDirections(
  246. 246 : [
  247. 247 : building,
  248. 248 : directionsRequest.from,
  249. 249 : directionsRequest.to,
  250. 250 : directionsRequest
  251. 251 : ],
  252. 252 : (route) => {
  253. 253 : this._sendMessageToViewer('directions.update', {
  254. 254 : ...route,
  255. 255 : ...mapViewerData
  256. 256 : });
  257. 257 : },
  258. 258 : (error) => {
  259. 259 : this._sendMessageToViewer('directions.update', {
  260. 260 : error: -1,
  261. 261 : identifier: mapViewerData.identifier
  262. 262 : });
  263. 263 : }
  264. 264 : );
  265. 265 : } else {
  266. 266 : this._sendMessageToViewer('directions.update', {
  267. 267 : error: -1,
  268. 268 : identifier: payload.identifier
  269. 269 : });
  270. 270 : }
  271. 271 : });
  272. 272 : }
  273. 273 :
  274. 274 : // NAVIGATION
  275. 275 :
  276. 276 : _onNavigationRequested(payload) {
  277. 277 : let directionsRequest = payload.directionsRequest;
  278. 278 : let mapViewerData = {
  279. 279 : identifier: payload.identifier,
  280. 280 : originIdentifier: payload.originIdentifier,
  281. 281 : destinationIdentifier: payload.destinationIdentifier,
  282. 282 : type: directionsRequest.accessibilityMode
  283. 283 : };
  284. 284 : let navigationRequest = payload.navigationRequest;
  285. 285 : this._ensureBuilding(payload.buildingIdentifier, (building) => {
  286. 286 : // Request directions again to update the calculated route on the native side.
  287. 287 : Situm.requestDirections(
  288. 288 : [
  289. 289 : building,
  290. 290 : directionsRequest.from,
  291. 291 : directionsRequest.to,
  292. 292 : directionsRequest
  293. 293 : ],
  294. 294 : (route) => {
  295. 295 : this._isNavigating = true;
  296. 296 : this._sendMessageToViewer('navigation.start', {
  297. 297 : ...route,
  298. 298 : ...mapViewerData
  299. 299 : });
  300. 300 : Situm.requestNavigationUpdates(
  301. 301 : [navigationRequest],
  302. 302 : (progress) => {
  303. 303 : // Navigation is working, handle different progress types:
  304. 304 : if (progress.type == 'progress') {
  305. 305 : progress.type = 'PROGRESS'; // The map-viewer waits for an upper case "type".
  306. 306 : this._sendMessageToViewer('navigation.update', progress);
  307. 307 : } else if (progress.type == 'destinationReached') {
  308. 308 : this._sendMessageToViewer('navigation.update', {
  309. 309 : type: 'DESTINATION_REACHED'
  310. 310 : });
  311. 311 : this._isNavigating = false;
  312. 312 : } else if (progress.type == 'userOutsideRoute') {
  313. 313 : this._sendMessageToViewer('navigation.update', {
  314. 314 : type: 'OUT_OF_ROUTE'
  315. 315 : });
  316. 316 : }
  317. 317 : },
  318. 318 : (error) => {
  319. 319 : this._sendMessageToViewer('directions.update', {
  320. 320 : error: -1,
  321. 321 : identifier: mapViewerData.identifier
  322. 322 : });
  323. 323 : this._isNavigating = false;
  324. 324 : }
  325. 325 : );
  326. 326 : },
  327. 327 : (error) => {
  328. 328 : this._sendMessageToViewer('directions.update', {
  329. 329 : error: -1,
  330. 330 : identifier: mapViewerData.identifier
  331. 331 : });
  332. 332 : this._isNavigating = false;
  333. 333 : }
  334. 334 : );
  335. 335 : });
  336. 336 : }
  337. 337 :
  338. 338 : _onNavigationCancel() {
  339. 339 : this._isNavigating = false;
  340. 340 : Situm.removeNavigationUpdates(
  341. 341 : () => {
  342. 342 : // Do nothing.
  343. 343 : },
  344. 344 : () => {
  345. 345 : console.error('Error removing navigation updates.');
  346. 346 : }
  347. 347 : );
  348. 348 : }
  349. 349 :
  350. 350 : _onViewerNavigationStarted(webPayload) {
  351. 351 : if (this._isNavigating) {
  352. 352 : return;
  353. 353 : }
  354. 354 : this._isNavigating = true;
  355. 355 : let externalNavigation = { messageType: "NavigationStarted", payload: webPayload};
  356. 356 : Situm.updateNavigationState(externalNavigation, () => {}, () => {});
  357. 357 : }
  358. 358 :
  359. 359 : _onViewerNavigationUpdated(webPayload) {
  360. 360 : if (!this._isNavigating) {
  361. 361 : return;
  362. 362 : }
  363. 363 : if (webPayload.type == "PROGRESS") {
  364. 364 : let externalNavigation = { messageType: "NavigationUpdated", payload: webPayload};
  365. 365 : Situm.updateNavigationState(externalNavigation, () => {}, () => {});
  366. 366 : } else if (webPayload.type == "DESTINATION_REACHED") {
  367. 367 : let externalNavigation = { messageType: "DestinationReached", payload: webPayload};
  368. 368 : Situm.updateNavigationState(externalNavigation, () => {}, () => {});
  369. 369 : this._isNavigating = false;
  370. 370 : } else if (webPayload.type == "OUT_OF_ROUTE") {
  371. 371 : let externalNavigation = { messageType: "OutsideRoute", payload: webPayload};
  372. 372 : Situm.updateNavigationState(externalNavigation, () => {}, () => {});
  373. 373 : this._isNavigating = false;
  374. 374 : }
  375. 375 :
  376. 376 : }
  377. 377 :
  378. 378 : _onViewerNavigationStopped(webPayload) {
  379. 379 : if (!this._isNavigating) {
  380. 380 : return;
  381. 381 : }
  382. 382 : this._isNavigating = false;
  383. 383 : let externalNavigation = { messageType: "NavigationCancelled", payload: webPayload};
  384. 384 : Situm.updateNavigationState(externalNavigation, () => {}, () => {});
  385. 385 : }
  386. 386 :
  387. 387 :
  388. 388 :
  389. 389 : // ==================================================
  390. 390 : // ACTIONS
  391. 391 : // ==================================================
  392. 392 :
  393. 393 : /**
  394. 394 : * Allows you to select a {@link POI} programmatically in your venue. This will cause the {@link MapView} to center the view on that POI and show its information.
  395. 395 : *
  396. 396 : * @param {number} identifier The unique identifier of the resource.
  397. 397 : * @memberof MapViewController
  398. 398 : * @name selectPoi
  399. 399 : * @method
  400. 400 : * */
  401. 401 : selectPoi(identifier) {
  402. 402 : this._sendMessageToViewer('cartography.select_poi', {
  403. 403 : identifier: identifier
  404. 404 : });
  405. 405 : }
  406. 406 :
  407. 407 : _sendNavigationConfig(navigationType) {
  408. 408 : this._sendMessageToViewer('app.set_config_item', [{key:'internal.navigationLibrary',value:navigationType}]);
  409. 409 : }
  410. 410 :
  411. 411 : /**
  412. 412 : * Allows you to navigate to a {@link POI} programmatically in your venue. This will cause the {@link MapView} to start navigation mode displaying the route between the user's location and the POI specified by parameters.
  413. 413 : *
  414. 414 : * The types of {@link accessibilityMode} you can use are:
  415. 415 : * - 'CHOOSE_SHORTEST' : Calculates the shortest route to the destination {@link POI}.
  416. 416 : * - 'ONLY_ACCESSIBLE' : Calculates the shortest route to the destination {@link POI} but avoiding stairs and prioritizing accessible floor changes such as lifts.
  417. 417 : * - 'ONLY_NOT_ACCESSIBLE_FLOOR_CHANGES' : Calculates the shortest route to the destination {@link POI} but avoiding lifts and prioritizing non-accessible floor changes such as stairs.
  418. 418 : *
  419. 419 : * accessibilityMode defaults to CHOOSE_SHORTEST.
  420. 420 : *
  421. 421 : * @param {number} identifier The identifier of the POI.
  422. 422 : * @param {'CHOOSE_SHORTEST' | 'ONLY_ACCESSIBLE' | 'ONLY_NOT_ACCESSIBLE_FLOOR_CHANGES' | undefined} accessibilityMode Choose the route type to calculate.
  423. 423 : * @memberof MapViewController
  424. 424 : * @name navigateToPoi
  425. 425 : * @method
  426. 426 : * */
  427. 427 : navigateToPoi(identifier, accessibilityMode) {
  428. 428 : this._sendMessageToViewer('navigation.start', {
  429. 429 : navigationTo: Number.parseInt(identifier),
  430. 430 : type: accessibilityMode
  431. 431 : });
  432. 432 : }
  433. 433 :
  434. 434 : /**
  435. 435 : * Allows you to change the initial language that &lt;map-view> uses to display its UI.
  436. 436 : * Checkout the <a href="https://situm.com/docs/13-internationalization/">Situm docs</a> to see the list of supported languages.
  437. 437 : *
  438. 438 : * @param {string} language an ISO 639-1 code.
  439. 439 : * @memberof MapViewController
  440. 440 : * @name setLanguage
  441. 441 : * @method
  442. 442 : */
  443. 443 : setLanguage(language) {
  444. 444 : this._sendMessageToViewer('ui.set_language', language);
  445. 445 : }
  446. 446 :
  447. 447 : /**
  448. 448 : * @typedef {Object} MapViewDirectionsOptions - Options to apply to directions requests
  449. 449 : * @property {string[]} includedTags - List of tags that you want to use when calculating a route. Only the tags added here will be used. If there are other tags in the graph they won't be used. The edges without a tag will be used. If you don't set any tag, all the graph will be used to calculate the route. You can learn more about this topic on https://situm.com/docs/cartography-management/#tags
  450. 450 : * @property {string[]} excludedTags - List of tags that you want your route to avoid. If you exclude a tag the route will never pass through an edge that have this tag. If the route can only be generated passing through an edge with this tag the route calculation will fail. You can learn more about this topic on https://situm.com/docs/cartography-management/#tags.
  451. 451 : */
  452. 452 :
  453. 453 : /**
  454. 454 : * If you want to change the route calculated based on tags you can use this interface.
  455. 455 : * Using this interface you can change all the routes that will be calculated including or excluding tags.
  456. 456 : * Use the method this method after the MapView ends loading
  457. 457 : * You can call this as many times you want and the mapviewer will use the last options that you set.
  458. 458 : *
  459. 459 : * @param {MapViewDirectionsOptions} directionsOptions the desired MapViewDirectionsOptions
  460. 460 : * @memberof MapViewController
  461. 461 : * @name setDirectionsOptions
  462. 462 : * @method
  463. 463 : */
  464. 464 : setDirectionsOptions(directionsOptions) {
  465. 465 : this._sendMessageToViewer('directions.set_options', {
  466. 466 : includedTags: directionsOptions.includedTags,
  467. 467 : excludedTags: directionsOptions.excludedTags
  468. 468 : });
  469. 469 : }
  470. 470 :
  471. 471 : // ==================================================
  472. 472 : // EVENTS
  473. 473 : // ==================================================
  474. 474 :
  475. 475 : /**
  476. 476 : * Informs you, through the function you pass as callback ({@link cb}), that a {@link POI} was selected in your building.
  477. 477 : * @param {Function} cb A callback that returns a {@link PoiSelectedResult} by its parameters.
  478. 478 : * @memberof MapViewController
  479. 479 : * @name onPoiSelected
  480. 480 : * @method
  481. 481 : * */
  482. 482 : onPoiSelected(cb) {
  483. 483 : this._onPoiSelectedCallback = cb;
  484. 484 : }
  485. 485 :
  486. 486 : /**
  487. 487 : * Informs you, through the function you pass as callback ({@link cb}), that a {@link POI} was deselected in your building.
  488. 488 : * @param {Function} cb A callback that returns a {@link PoiDeselectedResult} by its parameters.
  489. 489 : * @memberof MapViewController
  490. 490 : * @name onPoiDeselected
  491. 491 : * @method
  492. 492 : * */
  493. 493 : onPoiDeselected(cb) {
  494. 494 : this._onPoiDeselectedCallback = cb;
  495. 495 : }
  496. 496 : }
  497. 497 :
  498. 498 : let MapViewController = new MapViewControllerImpl();
  499. 499 :
  500. 500 : module.exports = MapViewController;