Showcase: selectable features in WME map

Shows how to add a vector feature layer where the features can receive events, just like Waze's features

  1. // ==UserScript==
  2. // @name Showcase: selectable features in WME map
  3. // @author Tom 'Glodenox' Puttemans
  4. // @namespace http://www.tomputtemans.com/
  5. // @version 0.3
  6. // @description Shows how to add a vector feature layer where the features can receive events, just like Waze's features
  7. // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor.*$/
  8. // ==/UserScript==
  9.  
  10. /* global W, OpenLayers */
  11.  
  12. async function onWmeReady() {
  13. // Create vector layer
  14. let mapLayer = new OpenLayers.Layer.Vector("feature_selection_showcase", {
  15. styleMap: new OpenLayers.StyleMap({
  16. 'default': new OpenLayers.Style({
  17. pointRadius: 50,
  18. strokeColor: '#aaa',
  19. strokeWidth: 4,
  20. fillColor: '${fillColor}',
  21. fontColor: '#fff',
  22. fontWeight: 'bold',
  23. fontSize: '50px',
  24. label: '${text}'
  25. }),
  26. 'highlight': new OpenLayers.Style({
  27. strokeColor: '#aaa',
  28. fillColor: '${highlightFillColor}'
  29. }),
  30. 'select': new OpenLayers.Style({
  31. strokeColor: '#fff',
  32. fillColor: '${fillColor}'
  33. }),
  34. 'highlightselected': new OpenLayers.Style({
  35. strokeColor: '#fff',
  36. fillColor: '${highlightFillColor}'
  37. })
  38. })
  39. });
  40. W.map.addLayer(mapLayer);
  41. // Move the SVG root of the new layer into the layer RootContainer used by Waze.
  42. let layerContainer = W.selectionManager.selectionMediator._rootContainerLayer;
  43. layerContainer.layers.push(mapLayer);
  44. layerContainer.collectRoots();
  45.  
  46. // Needed if you want to track the unselect event
  47. let selectedFeature = null;
  48.  
  49. // Event handling example
  50. // We need to filter out the highlight events of the other layers, hence the checks within the handlers
  51. W.selectionManager.selectionMediator.on({
  52. "map:selection:featureIn": (e) => e.layer == mapLayer && console.log("highlight", e), // e is the highlighted OpenLayers.Feature.Vector instance
  53. "map:selection:featureOut": (e) => e.layer == mapLayer && console.log("unhighlight", e) // e is the no longer highlighted OpenLayers.Feature.Vector instance
  54. });
  55. W.selectionManager.events.on({
  56. "selectionchanged": (e) => {
  57. let matchedFeature = e.selected.find(feature => feature.layer == mapLayer);
  58. if (matchedFeature) {
  59. selectedFeature = matchedFeature;
  60. console.log("select", selectedFeature);
  61. } else if (selectedFeature) {
  62. console.log("unselect", selectedFeature);
  63. selectedFeature = null;
  64. }
  65. }
  66. });
  67. // Implementation note: W.selectionManager also has more specific "app:selection:featureselected" and "app:selection:featureunselected" events, but the unselection event doesn't trigger if you select another vector directly
  68. // If you wish to use those events, e.layers contains the affected layers. Alternatively, you could implement the setSelected method in the attributes, which gets a boolean attribute indicating a select or unselect.
  69.  
  70. // Create a test location at the center of the map to showcase the highlighting and selection
  71. let location = W.map.getCenter();
  72. let vectorPoint = new OpenLayers.Geometry.Point(location.lon, location.lat);
  73. let text = "foo";
  74. // The repositoryObject contains methods required by the Waze logic
  75. let vectorAttributes = {
  76. text: text,
  77. type: 'custom',
  78. fillColor: "#555",
  79. highlightFillColor: "#888",
  80. index: `${text}`,
  81. repositoryObject: {
  82. isDeleted: () => false,
  83. setSelected: (state) => null, // You could implement this method to get select/unselect callbacks
  84. isNew: () => false,
  85. getType: () => null,
  86. getID: () => -1
  87. }
  88. };
  89. let testVector = new OpenLayers.Feature.Vector(vectorPoint, vectorAttributes);
  90. // Not needed by Waze, but some common scripts rely on the presence of a model field
  91. testVector.model = vectorAttributes;
  92. mapLayer.addFeatures([ testVector ]);
  93. }
  94.  
  95. // Below you just find the usual bootstrap code, nothing needs to be changed there
  96.  
  97. function onWmeInitialized() {
  98. if (W.userscripts?.state?.isReady) {
  99. console.log('W is ready and in "wme-ready" state. Proceeding with initialization.');
  100. onWmeReady();
  101. } else {
  102. console.log('W is ready, but not in "wme-ready" state. Adding event listener.');
  103. document.addEventListener('wme-ready', onWmeReady, { once: true });
  104. }
  105. }
  106.  
  107. function bootstrap() {
  108. if (!W) {
  109. console.log('W is not available. Adding event listener.');
  110. document.addEventListener('wme-initialized', onWmeInitialized, { once: true });
  111. } else {
  112. onWmeInitialized();
  113. }
  114. }
  115.  
  116. bootstrap();