1

I'm in the process of designing a chat bot and trying to find some Node.js sample code and/or documentation on how to implement the Azure Maps service as part of Bot Framework V4. There are many examples of how this is accomplished in V3, but there seems to be no examples of a V4 solution for Node.js. I'm looking to create a step in my botbuilder-dialog flow that would launch a simple "where do we ship it too" location dialog that would guide the user through the dialog and store the address results as part of that users profile. Any help or advice on this would be appreciated.

  • What is your preferred channel? Facebook, WebChat, Teams etc. – Mick Mar 01 '20 at 19:09
  • 1
    In addition to @Mick's question, are you wanting to display a map? You mention collecting the user's address results which _usually_ come's from user typed input, not a map. Can you elaborate more on the flow? Just clarifying. – Steven Kanberg Mar 03 '20 at 18:12
  • Thanks for you reply. The map requirement itself can be likened to a point of interest or store locator. This is really just for a demo of the map functionality in general and can be applied to multiple use cases. – Phil Johnson Mar 05 '20 at 15:02
  • The flow I would like to duplicate is here, except this seems not to be easily compatible with a dialog flow in sdk v4 https://github.com/microsoft/BotBuilder-Location. – Phil Johnson Mar 05 '20 at 15:05

1 Answers1

1

Yes, this is doable. I created a class (probably overkill, but oh well) in which I make my API call, with my supplied parameters, to get the map. I decided to use Azure Maps (vs Bing Maps) only because I was curious in how it differed. There isn't any reason you couldn't do this with Bing Maps, as well.

In the bot, I am using a component dialog because of how I have the rest of my bot designed. When the dialog ends, it will fall off the stack and return to the parent dialog.

In my scenario, the bot presents the user with a couple choices. "Send me a map" generates a map and sends it in an activity to the client/user. Anything else sends the user onward ending the dialog.

You will need to decide how you are getting the user's location. I developed this with Web Chat in mind, so I am getting the geolocation from the browser and returning it to the bot to be used when getMap() is called.

const { ActivityTypes, InputHints } = require('botbuilder');
const fetch = require('node-fetch');

class MapHelper {
  async getMap(context, latitude, longitude) {
    var requestOptions = {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json'
      },
      redirect: 'follow'
    };

    const result = await fetch(`https://atlas.microsoft.com/map/static/png?subscription-key=${ process.env.AZURE_MAPS_KEY }&api-version=1.0&layer=basic&zoom=13&center=${ longitude },${ latitude }&language=en-US&pins=default|al.67|la12 3|lc000000||'You!'${ longitude } ${ latitude }&format=png`, requestOptions)
      .then(response => response.arrayBuffer())
      .then(async result => {
        const bufferedData = Buffer.from(result, 'binary');
        const base64 = bufferedData.toString('base64');
        const reply = { type: ActivityTypes.Message };
        const attachment = {
          contentType: 'image/png',
          contentUrl: `data:image/png;base64,${ base64 }`
        };

        reply.attachments = [attachment];
        await context.sendActivity(reply, null, InputHints.IgnoringInput);
      })
      .catch(error => {
        if (error) throw new Error(error);
      });

    return result;
  };
};

module.exports.MapHelper = MapHelper;
const { ChoicePrompt, ChoiceFactory, ComponentDialog, ListStyle, WaterfallDialog } = require('botbuilder-dialogs');
const { MapHelper } = require('./mapHelper');

const CONFIRM_LOCALE_DIALOG = 'confirmLocaleDialog';
const CHOICE_PROMPT = 'confirmPrompt';

class ConfirmLocaleDialog extends ComponentDialog {
  constructor() {
    super(CONFIRM_LOCALE_DIALOG);

    this.addDialog(new ChoicePrompt(CHOICE_PROMPT))
      .addDialog(new WaterfallDialog(CONFIRM_LOCALE_DIALOG, [
        this.askLocationStep.bind(this),
        this.getMapStep.bind(this)
    ]));

    this.initialDialogId = CONFIRM_LOCALE_DIALOG;
  }

  async askLocationStep(stepContext) {
    const choices = ['Send me a map', "I'll have none of this nonsense!"];
    return await stepContext.prompt(CHOICE_PROMPT, {
      prompt: 'Good sir, may I pinpoint you on a map?',
      choices: ChoiceFactory.toChoices(choices),
      style: ListStyle.suggestedAction
    });
  }

  async getMapStep(stepContext) {
    const { context, context: { activity } } = stepContext;
    const text = activity.text.toLowerCase();
    if (text === 'send me a map') {
      const { latitude, longitude } = activity.channelData;
      const mapHelper = new MapHelper();
      await mapHelper.getMap(context, latitude, longitude);

      const message = 'Thanks for sharing!';
      await stepContext.context.sendActivity(message);
      return await stepContext.endDialog();
    } else {
      await stepContext.context.sendActivity('No map for you!');
      return await stepContext.endDialog();
    }
  }
}

module.exports.ConfirmLocaleDialog = ConfirmLocaleDialog;
module.exports.CONFIRM_LOCALE_DIALOG = CONFIRM_LOCALE_DIALOG;

enter image description here

Hope of help!

---- EDIT ----

Per request, location data can be obtained from the browser using the below method. It is, of course, dependent on the user granting access to location data.

navigator.geolocation.getCurrentPosition( async (position) => {
  const { latitude, longitude } = position.coords;
  // Do something with the data;
  console.log(latitude, longitude)
})
Steven Kanberg
  • 6,078
  • 2
  • 16
  • 35