WME Image Overlays

Makes it possible to add images as overlay on the Waze Map Editor

当前为 2017-05-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name WME Image Overlays
  3. // @author Tom 'Glodenox' Puttemans
  4. // @namespace http://www.tomputtemans.com/
  5. // @description Makes it possible to add images as overlay on the Waze Map Editor
  6. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/.*$/
  7. // @icon data:image/png;base64,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
  8. // @version 0.7
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // Based on the OpenLayers.Layer.Image class
  13. OpenLayers.Layer.OverlayImage = OpenLayers.Class(OpenLayers.Layer, {
  14. isBaseLayer: false,
  15. url: null,
  16. extent: null,
  17. size: null,
  18. tile: null,
  19. rotation: null,
  20. key: null,
  21. initialize: function(name, url, extent, size, key, options) {
  22. options = options || {};
  23. this.url = url;
  24. this.extent = extent;
  25. this.maxExtent = extent;
  26. this.size = size;
  27. this.key = key;
  28. this.rotation = options.rotation || 0;
  29. OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
  30. },
  31. destroy: function() {
  32. if (this.tile) {
  33. this.removeTileMonitoringHooks(this.tile);
  34. this.tile.destroy();
  35. this.tile = null;
  36. }
  37. OpenLayers.Layer.prototype.destroy.apply(this, arguments);
  38. },
  39. setMap: function(map) {
  40. OpenLayers.Layer.prototype.setMap.apply(this, arguments);
  41. },
  42. moveTo:function(bounds, zoomChanged, dragging) {
  43. OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
  44. var firstRendering = (this.tile == null);
  45. if (zoomChanged || firstRendering) {
  46. this.setTileSize();
  47. var ulPx = this.map.getLayerPxFromLonLat({
  48. lon: this.extent.left,
  49. lat: this.extent.top
  50. });
  51.  
  52. if (firstRendering) {
  53. this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, null, this.tileSize);
  54. this.addTileMonitoringHooks(this.tile);
  55. } else {
  56. this.tile.size = this.tileSize.clone();
  57. this.tile.position = ulPx.clone();
  58. }
  59. this.tile.draw();
  60. if (firstRendering) {
  61. this.setRotation(this.rotation);
  62. }
  63. }
  64. },
  65. shift: function(x, y) {
  66. this.extent = this.extent.add(x, y);
  67. var ulPx = this.map.getLayerPxFromLonLat({
  68. lon: this.extent.left,
  69. lat: this.extent.top
  70. });
  71. this.tile.position = ulPx.clone();
  72. this.tile.draw();
  73. },
  74. scale: function(factor) {
  75. this.extent = this.extent.scale(factor);
  76. this.setTileSize();
  77. var ulPx = this.map.getLayerPxFromLonLat({
  78. lon: this.extent.left,
  79. lat: this.extent.top
  80. });
  81. this.tile.position = ulPx.clone();
  82. this.tile.size = this.tileSize.clone();
  83. this.tile.draw();
  84. },
  85. rotate: function(rotation) {
  86. this.setRotation(this.rotation + rotation);
  87. },
  88. setTileSize: function() {
  89. var tileWidth = this.extent.getWidth() / this.map.getResolution();
  90. var tileHeight = this.extent.getHeight() / this.map.getResolution();
  91. this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
  92. },
  93. addTileMonitoringHooks: function(tile) {
  94. tile.onLoadStart = function() {
  95. this.events.triggerEvent("loadstart");
  96. };
  97. tile.events.register("loadstart", this, tile.onLoadStart);
  98. tile.onLoadEnd = function() {
  99. this.events.triggerEvent("loadend");
  100. };
  101. tile.events.register("loadend", this, tile.onLoadEnd);
  102. tile.events.register("unload", this, tile.onLoadEnd);
  103. },
  104. removeTileMonitoringHooks: function(tile) {
  105. tile.unload();
  106. tile.events.un({
  107. "loadstart": tile.onLoadStart,
  108. "loadend": tile.onLoadEnd,
  109. "unload": tile.onLoadEnd,
  110. scope: this
  111. });
  112. },
  113. setUrl: function(newUrl) {
  114. this.url = newUrl;
  115. this.tile.draw();
  116. },
  117. getURL: function(bounds) {
  118. return this.url;
  119. },
  120. setRotation: function(rotation) {
  121. this.rotation = rotation;
  122. this.tile.imgDiv.style.transform = 'rotate(' + rotation + 'deg)';
  123. },
  124. CLASS_NAME: "OpenLayers.Layer.OverlayImage"
  125. });
  126.  
  127. function init(e) {
  128. if (e && e.user == null) {
  129. return;
  130. }
  131. if (document.getElementById('user-info') == null) {
  132. setTimeout(init, 500);
  133. log('user-info element not yet available, page still loading');
  134. return;
  135. }
  136. if (typeof Waze.loginManager === 'undefined') {
  137. setTimeout(init, 300);
  138. return;
  139. }
  140. if (!Waze.loginManager.hasUser()) {
  141. Waze.loginManager.events.register('login', null, init);
  142. Waze.loginManager.events.register('loginStatus', null, init);
  143. // Double check as event might have triggered already
  144. if (!Waze.loginManager.hasUser()) {
  145. return;
  146. }
  147. }
  148.  
  149. var om_strings = {
  150. en: {
  151. tab_title: 'Image Overlays',
  152. add_image: 'Add image overlay',
  153. empty_list: 'No images added yet',
  154. name_missing: 'No name specified',
  155. hide_overlay: 'Hide overlay',
  156. opacity: 'Opacity',
  157. parent_map_layer: 'Parent map layer',
  158. parent_map_layer_help: 'This decides on top of which map layer the image overlay will be drawn',
  159. layer_hidden: 'Hidden',
  160. image_name: 'Name',
  161. image_edit: 'Edit image',
  162. image_remove: 'Remove image',
  163. import_image: 'Import image',
  164. import_image_description: 'You can now paste an image from your clipboard in the WME with Ctrl+V or select an image with the file input field below.',
  165. import_error: 'Could not import image, the image is probably too big to retrieve. If you used the clipboard, you may want to download the image and try the file input field above instead.',
  166. align_image: 'Align with map',
  167. align_image_description: "You can use the controls below to align the image overlay with the map. Use the 'Attach to map' button above to finish.",
  168. attach_image: 'Attach to map',
  169. cancel: 'Cancel'
  170. }
  171. };
  172. om_strings.en_GB = om_strings['en-US'] = om_strings.en;
  173. for (var i = 0; i < I18n.availableLocales.length; i++) {
  174. var locale = I18n.availableLocales[i];
  175. if (I18n.translations[locale]) {
  176. I18n.translations[locale].image_overlays = om_strings[locale];
  177. }
  178. }
  179.  
  180. var tab = addTab();
  181. // Deal with events mode
  182. if (Waze.app.modeController) {
  183. Waze.app.modeController.model.bind('change:mode', function(model, modeId) {
  184. if (modeId == 0) {
  185. addTab(tab);
  186. }
  187. });
  188. }
  189.  
  190. var layer = null;
  191. var currentBlob = null;
  192. var emptyList = document.createElement('span');
  193. emptyList.style.fontStyle = 'italic';
  194. emptyList.appendChild(document.createTextNode(I18n.t('image_overlays.empty_list')));
  195. tab.appendChild(emptyList);
  196. var imagesList = document.createElement('div');
  197. imagesList.className = 'result-list';
  198. imagesList.style.marginBottom = '1em';
  199. tab.appendChild(imagesList);
  200. getIndexedDB(function(db) {
  201. db.transaction(['overlays'], 'readonly')
  202. .objectStore('overlays')
  203. .openCursor()
  204. .addEventListener('success', function(e) {
  205. var cursor = e.target.result;
  206. if (cursor) {
  207. addImageOverlay(cursor.value.name, cursor.key);
  208. cursor.continue();
  209. }
  210. });
  211. });
  212.  
  213. var panel = document.createElement('div');
  214. panel.className = 'hidden';
  215. panel.style.backgroundColor = '#f2f3f4';
  216. panel.style.padding = '15px';
  217.  
  218. var importError = document.createElement('p');
  219. importError.className = 'hidden text-danger';
  220. importError.textContent = I18n.t('image_overlays.import_error');
  221. var pasteListener = function(e) {
  222. var items = e.clipboardData.items;
  223. for (var i = 0; i < items.length; ++i) {
  224. if (items[i].kind == 'file' && items[i].type.indexOf('image/') !== -1) {
  225. var blob = items[i].getAsFile();
  226. if (blob) {
  227. importError.classList.add('hidden');
  228. displayAlignPage(blob);
  229. } else {
  230. importError.classList.remove('hidden');
  231. Waze.map.resize();
  232. }
  233. break;
  234. }
  235. }
  236. };
  237.  
  238. var cancelButton, importLabel, alignLabel, pinLabel;
  239. cancelButton = document.createElement('button');
  240. cancelButton.className = 'btn btn-default';
  241. cancelButton.style.position = 'absolute';
  242. cancelButton.style.right = '15px';
  243. cancelButton.style.fontSize = '14px';
  244. var cancelButtonIcon = document.createElement('i');
  245. cancelButtonIcon.className = 'fa fa-trash-o fa-fw';
  246. cancelButton.appendChild(cancelButtonIcon);
  247. cancelButton.appendChild(document.createTextNode(I18n.t('image_overlays.cancel')));
  248. cancelButton.addEventListener('click', function() {
  249. panel.classList.add('hidden');
  250. Waze.map.resize();
  251. removeLayer();
  252. });
  253. panel.appendChild(cancelButton);
  254. var breadcrumbs = document.createElement('div');
  255. breadcrumbs.className = 'text-center';
  256. breadcrumbs.style.marginBottom = '1.5em';
  257. var breadcrumbSeparator = document.createElement('i');
  258. breadcrumbSeparator.className = 'fa fa-fw fa-angle-right';
  259. importLabel = createBreadcrumb('download', I18n.t('image_overlays.import_image'), 'primary');
  260. breadcrumbs.appendChild(importLabel);
  261. breadcrumbs.appendChild(breadcrumbSeparator);
  262. alignLabel = createBreadcrumb('arrows-alt', I18n.t('image_overlays.align_image'), 'default');
  263. breadcrumbs.appendChild(alignLabel);
  264. breadcrumbs.appendChild(breadcrumbSeparator.cloneNode());
  265. pinLabel = createBreadcrumb('map-pin', I18n.t('image_overlays.attach_image'), 'default');
  266. breadcrumbs.appendChild(pinLabel);
  267. panel.appendChild(breadcrumbs);
  268. var description = document.createElement('p');
  269. description.className = 'text-center';
  270. panel.appendChild(description);
  271. var instructions = document.createElement('div');
  272. instructions.className = 'text-center';
  273. panel.appendChild(instructions);
  274. document.getElementById('map').insertBefore(panel, document.getElementById('map').firstChild);
  275.  
  276. var addImageOverlayButton = document.createElement('button');
  277. addImageOverlayButton.className = 'btn btn-primary';
  278. var addSpan = document.createElement('span');
  279. addSpan.className = 'fa fa-plus';
  280. addSpan.style.marginRight = '5px';
  281. addImageOverlayButton.appendChild(addSpan);
  282. addImageOverlayButton.appendChild(document.createTextNode(I18n.t('image_overlays.add_image')));
  283. addImageOverlayButton.addEventListener('click', displayImportPage);
  284. tab.appendChild(addImageOverlayButton);
  285. var hideOverlayButton = document.createElement('button');
  286. hideOverlayButton.className = 'btn btn-default hidden';
  287. hideOverlayButton.style.float = 'right';
  288. hideOverlayButton.textContent = I18n.t('image_overlays.hide_overlay');
  289. hideOverlayButton.addEventListener('click', removeLayer);
  290. tab.appendChild(hideOverlayButton);
  291.  
  292. var layerControls = document.createElement('div');
  293. layerControls.className = 'hidden clearfix';
  294. layerControls.style.marginTop = '8px';
  295. var opacityRange = document.createElement('input');
  296. opacityRange.type = 'range';
  297. opacityRange.min = 0;
  298. opacityRange.max = 50;
  299. opacityRange.value = 50;
  300. opacityRange.id = 'imageOverlaysOpacity';
  301. var opacityLabel = document.createElement('label');
  302. opacityLabel.textContent = I18n.t('image_overlays.opacity');
  303. opacityLabel.htmlFor = opacityRange.id;
  304. layerControls.appendChild(opacityLabel);
  305. layerControls.appendChild(opacityRange);
  306. var rangeListener = function() {
  307. if (layer && layer.key) {
  308. getIndexedDB(function(db) {
  309. var objectStore = db.transaction(['overlays'], 'readwrite').objectStore('overlays');
  310. objectStore.get(layer.key).addEventListener('success', function(e) {
  311. var overlay = e.target.result;
  312. overlay.opacity = opacityRange.value / 50;
  313. objectStore.put(overlay, layer.key).addEventListener('success', function() {
  314. layer.setOpacity(opacityRange.value / 50);
  315. });
  316. });
  317. });
  318. } else if (layer) {
  319. layer.setOpacity(opacityRange.value / 50);
  320. }
  321. };
  322. opacityRange.addEventListener('input', rangeListener);
  323. opacityRange.addEventListener('change', rangeListener);
  324. var parentLayer = document.createElement('select');
  325. parentLayer.className = 'form-control';
  326. parentLayer.id = 'imageOverlaysParentLayer';
  327. Waze.map.events.on({
  328. addlayer: updateParentLayer,
  329. removelayer: updateParentLayer,
  330. changelayer: updateParentLayer
  331. });
  332. parentLayer.addEventListener('change', function() {
  333. if (layer && layer.key) {
  334. getIndexedDB(function(db) {
  335. var objectStore = db.transaction(['overlays'], 'readwrite').objectStore('overlays');
  336. objectStore.get(layer.key).addEventListener('success', function(e) {
  337. var overlay = e.target.result;
  338. overlay.layerTarget = parentLayer.value;
  339. objectStore.put(overlay, layer.key).addEventListener('success', function() {
  340. var targetIndex = Waze.map.getLayerIndex(Waze.map.getLayersByName(parentLayer.value)[0]) + 1;
  341. Waze.map.setLayerIndex(layer, targetIndex);
  342. });
  343. });
  344. });
  345. } else if (layer) {
  346. var targetIndex = Waze.map.getLayerIndex(Waze.map.getLayersByName(parentLayer.value)[0]) + 1;
  347. Waze.map.setLayerIndex(layer, targetIndex);
  348. }
  349. });
  350. var parentLayerLabel = document.createElement('label');
  351. parentLayerLabel.textContent = I18n.t('image_overlays.parent_map_layer') + ' ';
  352. parentLayerLabel.htmlFor = parentLayer.id;
  353. parentLayerLabel.style.marginTop = '10px';
  354. var parentLayerHelp = document.createElement('i');
  355. parentLayerHelp.className = 'waze-tooltip';
  356. parentLayerHelp.title = I18n.t('image_overlays.parent_map_layer_help');
  357. $(parentLayerHelp).tooltip();
  358. parentLayerLabel.appendChild(parentLayerHelp);
  359. layerControls.appendChild(parentLayerLabel);
  360. layerControls.appendChild(parentLayer);
  361. tab.appendChild(layerControls);
  362.  
  363. var versionBlock = document.createElement('p');
  364. versionBlock.style.fontSize = '0.9em';
  365. versionBlock.style.marginTop = '10px';
  366. var versionInfo = document.createElement('a');
  367. versionInfo.appendChild(document.createTextNode(GM_info.script.name + ' (v' + GM_info.script.version + ')'));
  368. versionInfo.href = 'https://greasyfork.org/scripts/29381-wme-image-overlays';
  369. versionInfo.target = '_blank';
  370. versionBlock.appendChild(versionInfo);
  371. tab.appendChild(versionBlock);
  372.  
  373. function addImageOverlay(name, key, selected) {
  374. emptyList.classList.add('hidden');
  375. var container = document.createElement('div');
  376. container.dataset.key = key;
  377. container.className = 'result session-available';
  378. if (selected) {
  379. container.style.fontWeight = '700';
  380. }
  381. var remove = document.createElement('button');
  382. remove.style.fontSize = '14px';
  383. remove.style.float = 'right';
  384. remove.style.fontWeight = 'normal';
  385. remove.style.marginTop = '-6px';
  386. remove.className = 'fa fa-trash-o';
  387. remove.addEventListener('click', function(e) {
  388. e.stopPropagation();
  389. getIndexedDB(function(db) {
  390. db.transaction(['overlays'], 'readwrite').objectStore('overlays').delete(key).addEventListener('success', function(e) {
  391. container.parentNode.removeChild(container);
  392. removeLayer();
  393. });
  394. });
  395. });
  396. container.appendChild(remove);
  397. var nameContainer = document.createElement('div');
  398. var rename = document.createElement('button');
  399. rename.style.fontSize = '14px';
  400. rename.style.float = 'right';
  401. rename.style.fontWeight = 'normal';
  402. rename.style.marginTop = '-6px';
  403. rename.style.marginLeft = '4px';
  404. rename.className = 'fa fa-pencil';
  405. rename.addEventListener('click', function(e) {
  406. e.stopPropagation();
  407. getIndexedDB(function(db) {
  408. var objectStore = db.transaction(['overlays'], 'readwrite').objectStore('overlays');
  409. objectStore.get(key).addEventListener('success', function(e) {
  410. var overlay = e.target.result;
  411. var response = prompt('Please enter a new name for this image overlay', overlay.name);
  412. if (response && response.length > 0) {
  413. overlay.name = response;
  414. objectStore.put(overlay, key).addEventListener('success', function() {
  415. nameContainer.textContent = overlay.name;
  416. nameContainer.style.fontStyle = '';
  417. });
  418. }
  419. });
  420. });
  421. });
  422. container.appendChild(rename);
  423. if (name && name.length > 0) {
  424. nameContainer.textContent = name;
  425. } else {
  426. nameContainer.style.fontStyle = 'italic';
  427. nameContainer.textContent = I18n.t('image_overlays.name_missing');
  428. }
  429. container.appendChild(nameContainer);
  430. container.addEventListener('click', function() {
  431. getIndexedDB(function(db) {
  432. db.transaction(['overlays'], 'readonly').objectStore('overlays').get(key).addEventListener('success', function(e) {
  433. var overlay = e.target.result;
  434. overlay.extent = new OL.Bounds(overlay.extent);
  435. overlay.key = key;
  436. displayImageOverlay(overlay);
  437. });
  438. });
  439. });
  440. imagesList.appendChild(container);
  441. }
  442.  
  443. function updateParentLayer(currentLayer) {
  444. if (!currentLayer || typeof currentLayer == 'object') {
  445. currentLayer = parentLayer.value;
  446. }
  447. while (parentLayer.firstChild) {
  448. parentLayer.removeChild(parentLayer.firstChild);
  449. }
  450. Waze.map.layers.forEach(function(layer) {
  451. if (layer.name != 'Image Overlay') {
  452. var layerOption = document.createElement('option');
  453. layerOption.value = layer.name;
  454. layerOption.textContent = layer.name + (layer.visibility ? '' : ' (' + I18n.t('image_overlays.layer_hidden') + ')');
  455. layerOption.selected = layer.name == currentLayer;
  456. parentLayer.appendChild(layerOption);
  457. }
  458. });
  459. }
  460.  
  461. function displayImportPage() {
  462. document.addEventListener('paste', pasteListener);
  463. importLabel.className = 'label label-primary';
  464. alignLabel.className = 'label label-default';
  465. pinLabel.style.cursor = 'default';
  466. pinLabel.removeEventListener('click', pinToMap);
  467. removeLayer();
  468. description.textContent = I18n.t('image_overlays.import_image_description');
  469. var addImageInput = document.createElement('input');
  470. addImageInput.type = 'file';
  471. addImageInput.accepts = 'image/*';
  472. addImageInput.className = 'center-block';
  473. addImageInput.addEventListener('change', function() {
  474. displayAlignPage(addImageInput.files[0]);
  475. });
  476. instructions.textContent = '';
  477. instructions.appendChild(addImageInput);
  478. instructions.appendChild(importError);
  479. panel.classList.remove('hidden');
  480. Waze.map.resize();
  481. }
  482.  
  483. function displayAlignPage(blob) {
  484. currentBlob = blob;
  485. document.removeEventListener('paste', pasteListener);
  486. importLabel.className = 'label label-success';
  487. alignLabel.className = 'label label-primary';
  488. parentLayer.selectedIndex = 0;
  489.  
  490. displayImageOverlay({
  491. 'blob': blob,
  492. 'extent': Waze.map.getExtent(),
  493. 'rotation': 0
  494. }, true);
  495.  
  496. pinLabel.style.cursor = 'pointer';
  497. pinLabel.addEventListener('click', pinToMap);
  498.  
  499. description.textContent = I18n.t('image_overlays.align_image_description');
  500. instructions.textContent = '';
  501. instructions.appendChild(createControlButton('rotate-left', function() {
  502. layer.rotate(-45);
  503. }, '45°'));
  504. instructions.appendChild(createControlButton('rotate-left', function() {
  505. layer.rotate(-0.5);
  506. }));
  507. instructions.appendChild(createControlButton('arrow-up', function() {
  508. layer.shift(0, 10 * Waze.map.getResolution());
  509. }));
  510. instructions.appendChild(createControlButton('rotate-right', function() {
  511. layer.rotate(0.5);
  512. }));
  513. instructions.appendChild(createControlButton('rotate-right', function() {
  514. layer.rotate(45);
  515. }, '45°'));
  516. instructions.appendChild(document.createElement('br'));
  517. instructions.appendChild(createControlButton('arrow-left', function() {
  518. layer.shift(-10 * Waze.map.getResolution(), 0);
  519. }));
  520. instructions.appendChild(createControlButton('crosshairs', function() {
  521. var layerCenter = layer.extent.getCenterLonLat();
  522. layer.shift(Waze.map.getCenter().lon - layerCenter.lon, Waze.map.getCenter().lat - layerCenter.lat);
  523. }));
  524. instructions.appendChild(createControlButton('arrow-right', function() {
  525. layer.shift(10 * Waze.map.getResolution(), 0);
  526. }));
  527. instructions.appendChild(document.createElement('br'));
  528. instructions.appendChild(createControlButton('minus', function() {
  529. layer.scale(1 - (0.05 * Waze.map.getResolution()));
  530. }));
  531. instructions.appendChild(createControlButton('arrow-down', function() {
  532. layer.shift(0, -10 * Waze.map.getResolution());
  533. }));
  534. instructions.appendChild(createControlButton('plus', function() {
  535. layer.scale(1 + (0.05 * Waze.map.getResolution()));
  536. }));
  537.  
  538. Waze.map.resize();
  539. }
  540.  
  541. function pinToMap() {
  542. getIndexedDB(function(db) {
  543. var obj = {
  544. 'blob': currentBlob,
  545. 'name': currentBlob.name,
  546. 'extent': layer.extent.toArray(),
  547. 'rotation': layer.rotation,
  548. 'opacity': opacityRange.value,
  549. 'layerTarget': parentLayer.value
  550. };
  551. var req = db.transaction(['overlays'], 'readwrite').objectStore('overlays').add(obj);
  552. req.addEventListener('success', function(e) {
  553. panel.className = 'hidden';
  554. Waze.map.resize();
  555. addImageOverlay(obj.name, e.target.result, true);
  556. });
  557. });
  558. }
  559.  
  560. function displayImageOverlay(overlay, rescale) {
  561. var url = window.URL.createObjectURL(overlay.blob);
  562. var img = document.createElement('img');
  563. img.addEventListener('load', function() {
  564. removeLayer();
  565. if (rescale) { // Rescale the extent for the image so it has the correct aspect ratio
  566. var mapExtentAspectRatio = overlay.extent.getWidth() / overlay.extent.getHeight();
  567. var imageAspectRatio = img.naturalWidth / img.naturalHeight;
  568. if (mapExtentAspectRatio > imageAspectRatio) {
  569. var widthDiff = overlay.extent.getWidth() - (overlay.extent.getHeight() * imageAspectRatio);
  570. overlay.extent = new OL.Bounds([overlay.extent.left + widthDiff/2 , overlay.extent.bottom, overlay.extent.right - widthDiff/2, overlay.extent.top]);
  571. } else {
  572. var heightDiff = overlay.extent.getHeight() - (overlay.extent.getWidth() / imageAspectRatio);
  573. overlay.extent = new OL.Bounds([overlay.extent.left, overlay.extent.bottom + heightDiff/2, overlay.extent.right, overlay.extent.top - heightDiff/2]);
  574. }
  575. overlay.extent = overlay.extent.scale(0.8);
  576. }
  577. layer = new OL.Layer.OverlayImage('Image Overlay', url, overlay.extent, new OL.Size(img.naturalWidth, img.naturalHeight), overlay.key, { 'rotation': overlay.rotation, 'opacity': overlay.opacity || 1 });
  578. Waze.map.addLayer(layer);
  579. for (var i = 0; i < imagesList.childNodes.length; i++) {
  580. imagesList.childNodes[i].style.fontWeight = (imagesList.childNodes[i].dataset.key == overlay.key ? '700' : '');
  581. }
  582. layerControls.classList.remove('hidden');
  583. hideOverlayButton.classList.remove('hidden');
  584. opacityRange.value = (overlay.opacity ? overlay.opacity * 50 : 50);
  585. updateParentLayer(overlay.layerTarget);
  586. var targetIndex = Waze.map.getLayerIndex(Waze.map.getLayersByName(overlay.layerTarget || "")[0]);
  587. if (!targetIndex || targetIndex <= 0) {
  588. targetIndex = Waze.map.getLayerIndex(Waze.map.getLayerByUniqueName('satellite_imagery'));
  589. }
  590. Waze.map.setLayerIndex(layer, targetIndex+1);
  591. Waze.map.zoomToExtent(overlay.extent);
  592. });
  593. img.src = url;
  594. }
  595.  
  596. function removeLayer() {
  597. if (layer) {
  598. Waze.map.removeLayer(layer);
  599. layer = null;
  600. layerControls.classList.add('hidden');
  601. hideOverlayButton.classList.add('hidden');
  602. for (var i = 0; i < imagesList.childNodes.length; i++) {
  603. imagesList.childNodes[i].style.fontWeight = '';
  604. }
  605. }
  606. }
  607. }
  608.  
  609. // Create a tab and possibly receive a previous tab to restore (usually in case of a mode change)
  610. function addTab(recoveredTab) {
  611. var userInfo = document.getElementById('user-info'),
  612. tabHandles = userInfo.querySelector('.nav-tabs'),
  613. tabs = userInfo.querySelector('.tab-content'),
  614. tabHandle = document.createElement('li'),
  615. tab = document.createElement('div');
  616. tabHandle.innerHTML = '<a href="#sidepanel-imageoverlays" data-toggle="tab" title="' + I18n.t('image_overlays.tab_title') + '"><span class="fa fa-picture-o"></span></a>';
  617. if (recoveredTab) {
  618. tab = recoveredTab;
  619. } else {
  620. tab.id = 'sidepanel-imageoverlays';
  621. tab.className = 'tab-pane';
  622. }
  623. tabHandles.appendChild(tabHandle);
  624. $(tabHandle.childNodes[0]).tooltip();
  625. tabs.appendChild(tab);
  626. return tab;
  627. }
  628.  
  629. function createBreadcrumb(icon, text, status) {
  630. var label = document.createElement('span');
  631. label.className = 'label label-' + status;
  632. label.style.fontSize = '1.2em';
  633. label.style.cursor = 'default';
  634. var i = document.createElement('i');
  635. i.className = 'fa fa-fw fa-' + icon;
  636. label.appendChild(i);
  637. label.appendChild(document.createTextNode(' ' + text));
  638. return label;
  639. }
  640.  
  641. function createControlButton(icon, callback, text) {
  642. var controlButton = document.createElement('button');
  643. var controlButtonIcon = document.createElement('i');
  644. controlButtonIcon.className = 'fa fa-fw fa-' + icon;
  645. controlButton.appendChild(controlButtonIcon);
  646. if (text) {
  647. controlButton.appendChild(document.createTextNode(' ' + text));
  648. }
  649. controlButton.addEventListener('click', callback);
  650. return controlButton;
  651. }
  652.  
  653. function getIndexedDB(callback) {
  654. var req = indexedDB.open('ImageOverlays', 1);
  655. req.addEventListener('upgradeneeded', function(e) {
  656. e.target.result.createObjectStore('overlays', { autoIncrement: true });
  657. });
  658. req.addEventListener('error', log);
  659. req.addEventListener('success', function(e) {
  660. callback(e.target.result);
  661. });
  662. }
  663.  
  664. function log(message) {
  665. if (typeof message === 'string') {
  666. console.log('%c' + GM_info.script.name + ' (v' + GM_info.script.version + '): %c' + message, 'color:black', 'color:#d97e00');
  667. } else {
  668. console.log('%c' + GM_info.script.name + ' (v' + GM_info.script.version + ')', 'color:black', message);
  669. }
  670. }
  671.  
  672. init();