WazeDev

Interacting with the Map using the SDK

Now that we have a solid settings foundation, let’s make our script interact with the WME map itself! This is where scripts become truly useful - by providing visual feedback and enhancing the editing experience.

In this section, we’ll create a segment highlighter that:

This introduces core concepts you’ll use in almost every WME script: event handling, map layers, geometry manipulation, and real-time visual feedback.

At this point we have a script that reads & loads some user information into a tab, and saves & loads the checked state of our Enabled checkbox. Now we’ll learn how to make your script react when you select segments on the map: it will highlight the selected segments in red, and remove the highlights when nothing is selected or the script is turned off.

Logic Overview

Step 1: Declare Layer Name

Before you start, you’ll want to pick a name for your highlight layer and something to track if your event listener is active.

This makes your code easier to update later and helps keep track of things.

(function () {
  'use strict';

  const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
  const STORAGE_KEY = 'wazedev_Settings'; // Local Storage key

  let wmeSDK; // Declare wmeSDK globally
  let settings = {}; // Global settings object
  
  const SELECTED_LAYER_NAME = "wazedev_SelectedLayer"; // Declare map layer name globally
  let selectionSubscription = null; // Declare variable to store the event listener state

  // <rest of the code>
})();

What/Why:

Step 2: Create Highlight Layer (with Basic Styling)

Now, create a layer that will display your highlights. You only need to do this once when your script starts, we can put this inside the main() method after setupEventHandlers();

async function main() {
 ...
 // Setup event handlers for checkboxes
 setupEventHandlers();

// Add the Layer to the Map!
 wmeSDK.Map.addLayer({
    layerName: SELECTED_LAYER_NAME,
    styleRules: [{
        style: {
            strokeColor: "red",      // Highlight color (red)
            strokeWidth: 12,         // Thickness (12px)
            strokeLinecap: "round",  // Rounded ends
            fillOpacity: 0           // No fill needed for lines
        }
    }]
 });

 console.log(`${SCRIPT_NAME}: Settings initialized!`);
}

💡 Tip: Full list of FeatureStyles can be found at SDK: Interface FeatureStyle

What/Why:

Step 3: Attach Selection Change Handler

Now, set up your script to listen when a user selects (or unselects) something.

This should be done when your script is enabled (e.g. a checkbox is on).

function enableSelectionHighlighting() {
    if (selectionSubscription) {
      selectionSubscription(); // Safely cleanup previous before subscribing!
      selectionSubscription = null;
    }
    selectionSubscription = wmeSDK.Events.on({
      eventName: 'wme-selection-changed',
      eventHandler: drawSelectionHighlight,
    });
  }

When your script is turned off, undo any listening and clear highlights:

function disableSelectionHighlighting() {
    if (selectionSubscription) {
      selectionSubscription(); // This unregisters the event handler!
      selectionSubscription = null;
    }
    wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
  }

What/Why:

Step 4: Drawing Highlights

This function actually does the work of displaying highlights.

When the selection changes, it erases the old highlight and draws a new one, if needed, but only if the Enable checkbox is “on”.

function drawSelectionHighlight() {
    // Check if the script's "Enable" setting is ON
    if (!settings.Enabled) {
        wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
        return;
    }

    // Get the current selection object from the SDK
    const selection = wmeSDK.Editing.getSelection();

    // Always remove all previous features from our layer
    wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });

    // Check if there are any segments actively selected in the selection object
    if (!selection || !selection.ids || selection.objectType !== 'segment') return;

    // Add the selected segments as features to the layer
    selection.ids.forEach((segmentId) => {
        const segment = wmeSDK.DataModel.Segments.getById({ segmentId });
        if (segment && segment.geometry) {
            wmeSDK.Map.addFeatureToLayer({
                layerName: SELECTED_LAYER_NAME,
                feature: {
                    id: `highlighted-${segmentId}`,
                    type: 'Feature',
                    geometry: segment.geometry,
                    properties: {},
                },
            });
        }
    });
}

What/Why:

Step 5: Tie to Settings (Checkbox Logic)

Connect highlighting to your script’s on/off setting.

Whenever your enable checkbox or option is changed, activate or deactivate highlighting.

function onEnabledCheckboxChanged(value) {
    if (settings.Enabled) {
      enableSelectionHighlighting();
      drawSelectionHighlight();
    } else {
      disableSelectionHighlighting();
    }
  }

So now let’s hook it up in the setupEventHandlers() method:

function setupEventHandlers() {
    // Find all checkboxes with the settings class in the DOM
    const checkboxes = document.querySelectorAll('.wazedevSettingsCheckbox');
    
    // For each settings checkbox, add a change event listener
    checkboxes.forEach((checkbox) => {
        checkbox.addEventListener('change', function () {
            // Extract the setting name from the checkbox ID by removing the 'wazedev' prefix
            // Example: 'wazedevEnabled' -> 'Enabled'
            const settingName = this.id.substring(7);
            
            // Update the corresponding value in the settings object
            settings[settingName] = this.checked;
            
            // Save the updated settings (persist changes, e.g., to storage)
            saveSettings();
            
            // If the 'Enabled' setting changed, trigger additional actions to update highlighting
            if (settingName === 'Enabled') {
                onEnabledCheckboxChanged(this.checked);
            }
        });
    });
}

What/Why:

Step 6: Clean-up

Whenever your script disables (checkbox off), disableSelectionHighlighting() makes sure:

What/Why:

Key Concepts Introduced

Event-Driven Map Interaction:

Map Layers and Styling:

Geometry and Features:

Resource Management:


Testing Your Map Integration

After implementing these changes, test the functionality:

  1. Enable the script and select a segment - you should see a red highlight
  2. Select multiple segments - all should be highlighted simultaneously
  3. Disable the script - highlights should disappear immediately
  4. Select other objects (places, nodes) - no highlights should appear

Expected Behavior:

Troubleshooting:

Our script should now look like the following

// ==UserScript==
// @name         WazeDev First Script              // Display name in Tampermonkey dashboard
// @namespace    http://tampermonkey.net/          // Unique identifier (use your GreasyFork profile for updates)
// @version      0.1                               // Version number for update tracking
// @description  Learning to script!               // Brief description of functionality
// @author       You                               // Your name or username
// @include      https://beta.waze.com/*           // Run on beta WME (testing environment)
// @include      https://www.waze.com/editor*      // Run on production WME
// @include      https://www.waze.com/*/editor*    // Run on localized WME URLs
// @exclude      https://www.waze.com/user/editor* // Don't run on user profile pages
// @exclude      https://www.waze.com/editor/sdk/* // Don't run on SDK documentation
// @grant        none                              // No special permissions needed
// ==/UserScript==

(function () {
  'use strict';

  const SCRIPT_NAME = GM_info.script.name; // Get the Script name from the @name tag of the UserScript Meta Keys
  const STORAGE_KEY = 'wazedev_Settings'; // Local Storage key

  let wmeSDK; // Declare wmeSDK globally
  let settings = {}; // Global settings object

  const SELECTED_LAYER_NAME = 'wazedev_SelectedLayer'; // Declare map layer name globally
  let selectionSubscription = null; // Declare variable to store the event listener state

  if (window.SDK_INITIALIZED) {
    console.log(`${SCRIPT_NAME}: SDK initialized...`);

    window.SDK_INITIALIZED.then(bootstrap).catch((err) => {
      console.error(`${SCRIPT_NAME}: SDK initialization failed`, err);
    });
  } else {
    console.warn(`${SCRIPT_NAME}: SDK_INITIALIZED is undefined`);
  }

  function bootstrap() {
    try {
      wmeSDK = getWmeSdk({
        scriptId: SCRIPT_NAME.replaceAll(' ', ''),
        scriptName: SCRIPT_NAME,
      });

      Promise.all([wmeReady()])
        .then(() => {
          console.log(`${SCRIPT_NAME}: All dependencies are ready.`);
          init();
        })
        .catch((error) => {
          console.error(`${SCRIPT_NAME}: Error during bootstrap`, error);
        });
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Failed to initialize SDK`, error);
    }
  }

  function wmeReady() {
    return new Promise((resolve) => {
      if (wmeSDK.State.isReady()) {
        console.log(`${SCRIPT_NAME}: WME is ready.`);
        resolve();
      } else {
        console.log(`${SCRIPT_NAME}: Waiting for WME to be ready...`);
        wmeSDK.Events.once({ eventName: 'wme-ready' })
          .then(() => {
            console.log(`${SCRIPT_NAME}: WME is ready now.`);
            resolve();
          })
          .catch((error) => {
            console.error(`${SCRIPT_NAME}: Error while waiting for WME to be ready:`, error);
          });
      }
    });
  }

  function init() {
    console.log(`${SCRIPT_NAME}: Script initialized successfully!`);

    // Build HTML content for our tab
    const section = document.createElement('div');
    section.innerHTML = `
    <div>
        <h2>Our First Script!</h2>
        <section>
            <input type="checkbox" id="wazedevEnabled" class="wazedevSettingsCheckbox">
            <label for="wazedevEnabled">Enable WazeDev Script</label>
        </section>
        <hr>
        <section>
            <h3>User Info</h3>
            Username: <a href="#" id="wazedevUsernameLink" target="_blank"><span id="wazedevUsername"></span></a><br>
            Rank: <span id="wazedevRank"></span><br>
            Area manager: <span id="wazedevAM"></span><br>
            Country manager: <span id="wazedevCM"></span>
        </section>
        <hr>
        <section>
            <h3>Detailed Profile</h3>
            <div id="wazedevProfileLoading">Loading profile data...</div>
            <div id="wazedevProfileData" style="display: none;">
                Total Edits: <span id="wazedevTotalEdits"></span><br>
                Today's Edits: <span id="wazedevTodayEdits"></span>
            </div>
        </section>
        <hr>
        <section id="wazedevEditsByType" style="display: none;">
            <h3>Edits by Type</h3>
            Segments: <span id="wazedevSegments"></span><br>
            Venues: <span id="wazedevVenues"></span><br>
            Map Problems: <span id="wazedevMapProblems"></span><br>
            Update Requests: <span id="wazedevUpdateRequests"></span><br>
            Place Update Requests: <span id="wazedevPlaceUpdateRequests"></span><br>
            Segment House Numbers: <span id="wazedevSegmentHouseNumbers"></span>
        </section>
    </div>
`;

    // Register the script tab using the SDK
    wmeSDK.Sidebar.registerScriptTab()
      .then(({ tabLabel, tabPane }) => {
        tabLabel.textContent = 'WazeDev';
        tabLabel.title = SCRIPT_NAME;
        tabPane.appendChild(section);

        // Initialize the main() function after tab is created
        main();
      })
      .catch((error) => {
        console.error(`${SCRIPT_NAME}: Error creating script tab`, error);
      });
  }

  // Save settings to localStorage
  function saveSettings() {
    try {
      const localSettings = { Enabled: settings.Enabled };
      localStorage.setItem(STORAGE_KEY, JSON.stringify(localSettings));
      console.log(`${SCRIPT_NAME}: Settings saved`, localSettings);
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Error saving settings:`, error);
    }
  }

  // Load settings from localStorage
  function loadSettings() {
    try {
      const loadedSettings = JSON.parse(localStorage.getItem(STORAGE_KEY));
      const defaultSettings = { Enabled: false };
      settings = loadedSettings ? loadedSettings : defaultSettings;

      // Add missing default settings
      for (const prop in defaultSettings) {
        if (!settings.hasOwnProperty(prop)) {
          settings[prop] = defaultSettings[prop];
        }
      }
      console.log(`${SCRIPT_NAME}: Settings loaded`, settings);
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Error loading settings:`, error);
      settings = { Enabled: false };
    }
  }

  // Helper to set checkbox state
  function setChecked(checkboxId, checked) {
    const checkbox = document.getElementById(checkboxId);
    if (checkbox) checkbox.checked = checked;
  }

  // Setup event handlers for our controls
  function setupEventHandlers() {
    // Find all checkboxes with the settings class in the DOM
    const checkboxes = document.querySelectorAll('.wazedevSettingsCheckbox');

    // For each settings checkbox, add a change event listener
    checkboxes.forEach((checkbox) => {
      checkbox.addEventListener('change', function () {
        // Extract the setting name from the checkbox ID by removing the 'wazedev' prefix
        // Example: 'wazedevEnabled' -> 'Enabled'
        const settingName = this.id.substring(7);

        // Update the corresponding value in the settings object
        settings[settingName] = this.checked;

        // Save the updated settings (persist changes, e.g., to storage)
        saveSettings();

        // If the 'Enabled' setting changed, trigger additional actions to update highlighting
        if (settingName === 'Enabled') {
          onEnabledCheckboxChanged(this.checked);
        }
      });
    });
  }

  function enableSelectionHighlighting() {
    if (selectionSubscription) {
      selectionSubscription(); // Safely cleanup previous before subscribing!
      selectionSubscription = null;
    }
    selectionSubscription = wmeSDK.Events.on({
      eventName: 'wme-selection-changed',
      eventHandler: drawSelectionHighlight,
    });
  }

  function disableSelectionHighlighting() {
    if (selectionSubscription) {
      selectionSubscription(); // This unregisters the event handler!
      selectionSubscription = null;
    }
    wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
  }

  function drawSelectionHighlight() {
    // Check if the script's "Enable" setting is ON
    if (!settings.Enabled) {
      wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });
      return;
    }

    // Get the current selection object from the SDK
    const selection = wmeSDK.Editing.getSelection();

    // Always remove all previous features from our layer
    wmeSDK.Map.removeAllFeaturesFromLayer({ layerName: SELECTED_LAYER_NAME });

    // Check if there are any segments actively selected in the selection object
    if (!selection || !selection.ids || selection.objectType !== 'segment') return;

    // Add the selected segments as features to the layer
    selection.ids.forEach((segmentId) => {
      const segment = wmeSDK.DataModel.Segments.getById({ segmentId });
      if (segment && segment.geometry) {
        wmeSDK.Map.addFeatureToLayer({
          layerName: SELECTED_LAYER_NAME,
          feature: {
            id: `highlighted-${segmentId}`,
            type: 'Feature',
            geometry: segment.geometry,
            properties: {},
          },
        });
      }
    });
  }

  function onEnabledCheckboxChanged(value) {
    if (settings.Enabled) {
      enableSelectionHighlighting();
      drawSelectionHighlight();
    } else {
      disableSelectionHighlighting();
    }
  }

  async function main() {
    // Load saved settings first
    loadSettings();

    // Set the default state of the "Enable" checkbox at script startup
    setChecked('wazedevEnabled', settings.Enabled);

    // Continue with user info/profile update
    const userSession = wmeSDK.State.getUserInfo();

    // Set basic user info
    document.getElementById('wazedevUsername').textContent = userSession.userName;
    document.getElementById('wazedevRank').textContent = userSession.rank;
    document.getElementById('wazedevAM').textContent = userSession.isAreaManager ? 'Yes' : 'No';
    document.getElementById('wazedevCM').textContent = userSession.isCountryManager ? 'Yes' : 'No';

    // Set profile link
    const profileLink = wmeSDK.DataModel.Users.getUserProfileLink({ userName: userSession.userName });
    document.getElementById('wazedevUsernameLink').href = profileLink;

    // Fetch detailed user profile
    try {
      const userProfile = await wmeSDK.DataModel.Users.getUserProfile({ userName: userSession.userName });

      document.getElementById('wazedevProfileLoading').style.display = 'none';
      document.getElementById('wazedevProfileData').style.display = 'block';
      document.getElementById('wazedevEditsByType').style.display = 'block';

      document.getElementById('wazedevTotalEdits').textContent = userProfile.totalEditCount.toLocaleString();
      const todayEdits = userProfile.dailyEditCount[userProfile.dailyEditCount.length - 1] || 0;
      document.getElementById('wazedevTodayEdits').textContent = todayEdits;

      const editTypes = userProfile.editCountByType;
      document.getElementById('wazedevSegments').textContent = editTypes.segments.toLocaleString();
      document.getElementById('wazedevVenues').textContent = editTypes.venues.toLocaleString();
      document.getElementById('wazedevMapProblems').textContent = editTypes.mapProblems.toLocaleString();
      document.getElementById('wazedevUpdateRequests').textContent = editTypes.updateRequests.toLocaleString();
      document.getElementById('wazedevPlaceUpdateRequests').textContent = editTypes.placeUpdateRequests.toLocaleString();
      document.getElementById('wazedevSegmentHouseNumbers').textContent = editTypes.segmentHouseNumbers.toLocaleString();

      console.log(`${SCRIPT_NAME}: Profile data loaded successfully`);
    } catch (error) {
      console.error(`${SCRIPT_NAME}: Error fetching user profile:`, error);
      document.getElementById('wazedevProfileLoading').textContent = 'Error loading profile data';
    }

    // Setup event handlers for checkboxes
    setupEventHandlers();

    // Add the Layer to the Map!
    wmeSDK.Map.addLayer({
      layerName: SELECTED_LAYER_NAME,
      styleRules: [
        {
          style: {
            strokeColor: 'red', // Highlight color (red)
            strokeWidth: 12, // Thickness (12px)
            strokeLinecap: 'round', // Rounded ends
            fillOpacity: 0, // No fill needed for lines
          },
        },
      ],
    });

    onEnabledCheckboxChanged(settings.Enabled); // Activate script effect if previously enabled

    console.log(`${SCRIPT_NAME}: Settings initialized!`);
  }
})();

Use a Modern code editors like VS Code, WebStorm, and many browser-based IDEs?

Then check out the final version of the script wiht JSDOC support here!