Add click option to direction widget

4452
9
Jump to solution
09-30-2014 02:42 AM
IonutAlixandroae
Occasional Contributor

Hello, I have an application with the option of directions and routes but I do not know how to add the option that point A and point B to be added with clicks ( it works now only with writing the addresses into box). Also when the directions appears, the unit is in miles but I need it in kilometers, how can I change it?

I have added the application.

Can anyone help me please??

Thanks!!

0 Kudos
1 Solution

Accepted Solutions
JakeSkinner
Esri Esteemed Contributor

You can solve this by adding the reverse geocode location to the routeParams.stops by updating function fired after the map is clicked:

mapClick = map.on("click", function(evt) { 

            clickLocation = evt; 

            routeParams.stops.features.push(map.graphics.add(new esri.Graphic(clickLocation.mapPoint)));               

            locator.locationToAddress(webMercatorUtils.webMercatorToGeographic(evt.mapPoint), 100);

});

Here is update to the JS Fiddle.  You will probably need to do some testing and updating of the code to get the exact functionality that you are looking for.  For example, it may be best to use one 'Solve Route' button rather than have two.  The fiddle should get you started though.

View solution in original post

9 Replies
JakeSkinner
Esri Esteemed Contributor

Hi Ionut,

You can reverse geocode to populate the text boxes with the address.  Below is an update to your code:

<html>

<head>

  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

  <meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">

  <title>Driving Directions</title>

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dojo/dijit/themes/claro/claro.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/dgrid/css/dgrid.css">

  <link rel="stylesheet" href="http://js.arcgis.com/3.10/js/esri/css/esri.css">

  <style>

    html,body {

      font-family: Arial,Helvetica,sans-serif;

      height:100%;

      margin:0;

    }

    #map {

      height:100%;

      overflow:hidden;

    }

      #HomeButton {

      position: absolute;

      top: 95px;

      left: 20px;

      z-index: 50;

    }

    #dialog {

      top: 15px;

      right: 15px;

      position: absolute;

      padding: 5px;

      width: 380px;

      background-color: #ffffff;

      border-radius: 5px;

      margin: 8px;

      box-shadow: 0px 1px 3px #888;

    }

    #dialog input{

      margin: 0.5em;

      width: 20em;

    }

    #grid{

      overflow-x:hidden;

      overflow-y:auto;

    }

   

    input#locations{

      margin: 0.5em auto;

      width:  8em;

      display: block;

    }

   

    input#directions {

      margin: 0.5em auto;

      width:  8em;

      display: block;

    }

   

    input#clearLocations {

      margin: 0.5em auto;

      width:  8em;

      display: block;

    }

    .dgrid-row{

      padding:5px;

      margin-bottom:5px;

      min-height:50px;

      border-bottom: solid 1px #C0C0C0;

    }

    .dgrid-row .detail div {

      cursor: pointer;

    }

    .dgrid-row .detail div:hover{

      text-decoration:underline;

    }

    .distance{

      float:right;

      color:#C0C0C0;

      font-style:italic;

    }

  </style>

  <script src="http://js.arcgis.com/3.10/"></script>

  <script>

    var map, geoCount, fromSymbol, toSymbol, mapClick, locatorDone;

    require([

      "esri/map",

      "esri/layers/ArcGISDynamicMapServiceLayer",

      "esri/dijit/HomeButton",

      "esri/dijit/Scalebar",

      "esri/dijit/OverviewMap",

      "esri/dijit/BasemapGallery",

      "esri/arcgis/utils",

      "dojo/parser",

      "esri/tasks/locator",

      "esri/SpatialReference",

      "esri/tasks/RouteTask",

      "esri/tasks/RouteParameters",

      "esri/tasks/FeatureSet",

      "esri/units",

      "esri/config",

      "esri/lang",

      "esri/symbols/PictureMarkerSymbol",

      "esri/graphic",

      "esri/symbols/SimpleLineSymbol",

      "esri/urlUtils",

      "dojo/promise/all",

      "dojo/_base/array",

      "esri/Color",

      "esri/geometry/webMercatorUtils",

      "dojo/dom",

      "dojo/dom-construct",

      "dojo/on",

      "dojo/number",

      "dgrid/Grid",

      "dojo/domReady!"

   

    ], function(

      Map,

      ArcGISDynamicMapServiceLayer,

      HomeButton,

      Scalebar,

      OverviewMap,

      BasemapGallery,

      arcgisUtils,

      parser,

      Locator,

      SpatialReference,

      RouteTask,

      RouteParameters,

      FeatureSet,

      esriUnits,

      esriConfig,

      esriLang,

      PictureMarkerSymbol,

      Graphic,

      SimpleLineSymbol,

      urlUtils,

      all,

      arrayUtils,

      Color,

      webMercatorUtils,

      dom,

      domConstruct,

      on,

      number,

      Grid

    ) { parser.parse();

      var locator, routeTask, routeParams = [], segmentGraphic, directionFeatures, grid;

      on(dom.byId("directions"), "click", getDirections);

     

      // Use a proxy to access the routing service, which requires credits

      urlUtils.addProxyRule({

        urlPrefix : "route.arcgis.com",

        proxyUrl : "/sproxy"

      });

     

      //Create a map with an initial extent. Change the extent to match the area you would like to show.

      map = new Map("map", {

        basemap: "topo",

        center: [25.55, 45.55],

        zoom: 10

      });

       var operationalLayer = new ArcGISDynamicMapServiceLayer("http://eismgeo.dlinkddns.com/eismgeo/rest/services/Aplicatie_NASlope/Aplicatie_NA/MapServer",{"opacity":1});

      map.addLayer(operationalLayer);

     

      var home = new HomeButton({

        map: map

      }, "HomeButton");

      home.startup();

     

       var scalebar = new Scalebar({

          map: map,

          // "dual" displays both miles and kilmometers

          // "english" is the default, which displays miles

          // use "metric" for kilometers

          scalebarUnit: "dual"

        });

       

      var overviewMapDijit = new OverviewMap({

          map: map,

          visible: false

        });

        overviewMapDijit.startup();

       

      //add the basemap gallery, in this case we'll display maps from ArcGIS.com including bing maps

      var basemapGallery = new BasemapGallery({

        showArcGISBasemaps: true,

        map: map

      }, "basemapGallery");

      basemapGallery.startup();

     

      basemapGallery.on("error", function(msg) {

        console.log("basemap gallery error:  ", msg);

      });

      //Add a geocoding server as the locator. This locator will be used to find the origin and destination coordinates by input addresses.

      locator = new Locator("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");

      locator.outSpatialReference = map.spatialReference;

     

      //Fire errorHandler if the locator return en error.

      locator.on("error", errorHandler);

      //Add a network analyst server with related parameters to execute the routing task.

      routeTask = new RouteTask("http://eismgeo.dlinkddns.com/eismgeo/rest/services/Aplicatie_NASlope/Aplicatie_NA/NAServer/Route");

      routeParams = new RouteParameters();

      routeParams.stops = new FeatureSet();

      routeParams.returnRoutes = false;

      routeParams.returnDirections = true;

      routeParams.directionsLengthUnits = esriUnits.KILOMETERS;

      routeParams.outSpatialReference = new SpatialReference({ wkid:102100 });

      //Show the route when the routing task is solved successfully, otherwise fire errorHandler.

      routeTask.on("solve-complete", showRoute);

      routeTask.on("error", errorHandler);

     

      fromSymbol = new PictureMarkerSymbol({

          "angle":0,

          "xoffset":0,

          "yoffset":10,

          "type":"esriPMS",

          "url":"http://static.arcgis.com/images/Symbols/Shapes/GreenPin1LargeB.png",

          "contentType":"image/png",

          "width":24,

          "height":24

        });

        toSymbol = new PictureMarkerSymbol({

          "angle":0,

          "xoffset":0,

          "yoffset":12,

          "type":"esriPMS",

          "url":"http://static.arcgis.com/images/Symbols/Shapes/RedPin1LargeB.png",

          "contentType":"image/png",

          "width":24,

          "height":24

        });

      //Execute a routing task when clicking "get direction".

      function getDirections() {

        routeParams.stops.features = [];

        map.graphics.clear();

        //Get origin address.

        var optionsFrom = {

          address: { "SingleLine": dom.byId("fromTxf").value },

          outFields: ["Loc_name"]

        }

        var fromAddress = locator.addressToLocations(optionsFrom);

     

        //Get destination address.

        var optionsTo = {

          address: { "SingleLine": dom.byId("toTxf").value },

          outFields: ["Loc_name"]

        }

        var toAddress = locator.addressToLocations(optionsTo);

        //Use dojo/promises/all to manage multiple asynchronous tasks. Once both geocodes finish, a route is calculated.

        //http://livedocs.dojotoolkit.org/dojo/promise/all

        all({

          from: fromAddress,

          to: toAddress

        }).then(configureRoute);

      }

     

      //Check if the origin and destination addresses are executed successfully

      //and solve the routing task.

      function configureRoute(results) {                    

        var fromStop = getCandidate(results.from);

        if ( fromStop === null ) {

          errorHandler("The origin address is invalid");

        } else {

          var fromGraphic = new Graphic(fromStop.location, fromSymbol, { address:fromStop.address });

          routeParams.stops.features[0] = map.graphics.add(fromGraphic);

        };

       

        var toStop = getCandidate(results.to);

        if ( toStop === null ) {

          errorHandler("The destination address is invalid");

        } else {

          var toGraphic = new Graphic(toStop.location, toSymbol, { address:toStop.address });

          routeParams.stops.features[1] = map.graphics.add(toGraphic);

        };

        if ( fromStop !== null && toStop !== null ) {

          routeTask.solve(routeParams);

        }

      }

       

      //Handle all the coordinate candidates of the origin and destination addresses and

      //return the candidate with the highest score.

      function getCandidate(candidates){

        var stop = null, score = 0;

        arrayUtils.forEach(candidates, function(candidate){

          if( candidate.score > score ) {

           stop = candidate;

           score = candidate.score;

          }

        });

        return stop;

      }

      //Show the result of the routing task.

      function showRoute(e) {

        var data = [];

        if ( grid ) {

          grid.refresh();

        }

       

        var directions = e.result.routeResults[0].directions;

        directionFeatures = directions.features;

        var routeSymbol = new SimpleLineSymbol().setColor(new Color([0,0,255,0.5])).setWidth(4);

       

        // Zoom to results.

        map.setExtent(directions.mergedGeometry.getExtent(), true);

        // Add route to the map.

        var routeGraphic = new Graphic(directions.mergedGeometry, routeSymbol);

        map.graphics.add(routeGraphic);

        routeGraphic.getShape().moveToBack();

        map.setExtent(directions.extent, true);

       

        //Display the directions.

        var directionsInfo = e.result.routeResults[0].directions.features;

        var totalDistance = number.format(directions.totalLength);

        var totalLength = number.format(directions.totalTime);

        data = arrayUtils.map(directionsInfo,function(feature,index){

          return {

            "detail": feature.attributes.text,

            "distance": number.format(feature.attributes.length,{places:2}),

            "index": index

          }

        });

        grid = new Grid({

          renderRow: renderList,

          showHeader:false

        }, "grid");

        grid.renderArray(data);

        grid.on(".dgrid-row:click", zoomToSegment);

      }

     

      function renderList(obj,options){

        console.log(obj);

        var template = "<div class='detail'><div style='max-width:70%;float:left;'>${detail}</div><span style='float:right;' class='distance'>${distance} km</span></div>";      

        return domConstruct.create("div", { innerHTML: esriLang.substitute(obj, template) });

      }

      //Display any errors that were caught when attempting to solve the route.

      function errorHandler(err) {

        alert("An error occured\n" + err);

      }

     

      function zoomToSegment(e) {

        //Grid row id corresponds to the segment to highlight

        var index = grid.row(e).id;

        var segment = directionFeatures[index];

        var segmentSymbol = new SimpleLineSymbol().setColor(new Color([255,0,0,0.5])).setWidth(8);

        map.setExtent(segment.geometry.getExtent(), true);

        if ( !segmentGraphic ) {

          segmentGraphic = map.graphics.add(new Graphic(segment.geometry, segmentSymbol));

        } else {

          segmentGraphic.setGeometry(segment.geometry);

        }

      }

     

      //Reverse Geocode       

        on(dom.byId("locations"), "click", function() {

          geoCount = 1; 

          map.setMapCursor("crosshair");

                   

          locatorDone = locator.on("location-to-address-complete", function(evt){

             reverseGeocode(evt, geoCount);

          });

         

          locator.on("error", function(){

            alert("Could not find address.  Please select another location.")

          })

         

          mapClick = map.on("click", function(evt) {                   

            locator.locationToAddress(webMercatorUtils.webMercatorToGeographic(evt.mapPoint), 100);

          });

        });

       

        function reverseGeocode(evt, num){

          var val = num;

        

          if(val == 1){

            symbol = fromSymbol;

            txtBox = "fromTxf";

          }

          else if(val == 2){

            symbol = toSymbol;

            txtBox = "toTxf";

            removeClick();           

          }                             

                          

          if (evt.address.address) {          

            var address = evt.address.address.Address + ", " + evt.address.address.City + ", " + evt.address.address.Region;

             

              dom.byId(txtBox).value = address;

             

              var location = webMercatorUtils.geographicToWebMercator(evt.address.location);

              var graphic = new Graphic(location, symbol, address);

              map.graphics.add(graphic);

              geoCount++;    

            }              

        }

        function removeClick(){

          map.setMapCursor("default");

          locatorDone.remove();

          mapClick.remove();

        }

       

        on(dom.byId("clearLocations"), "click", clearForm);

       

        function clearForm(){

          map.setMapCursor("default");

          if(grid){

            grid.refresh();

          }

          if(mapClick){

            mapClick.remove();

          }

          if(locatorDone){

            locatorDone.remove();

          }

          map.graphics.clear();

          dom.byId("fromTxf").value = '';

          dom.byId("toTxf").value = '';

        }

         

    });

  </script>

</head>

<body class="claro">

    <div data-dojo-type="dijit/layout/BorderContainer"

       data-dojo-props="design:'headline', gutters:false"

       style="width:100%;height:100%;margin:0;">

      <div id="map"

         data-dojo-type="dijit/layout/ContentPane"

         data-dojo-props="region:'center'"

         style="padding:0;">

      <div style="position:absolute; left:60px; top:20px; z-Index:999;">

        <div data-dojo-type="dijit/TitlePane"

             data-dojo-props="title:'Switch Basemap', closable:false, open:false">

          <div data-dojo-type="dijit/layout/ContentPane" style="width:380px; height:280px; overflow:auto;">

            <div id="basemapGallery"></div>

           

          </div>

        </div>

      </div>

    </div>

  </div>

</body>

<body class="claro">

  <div id="map"></div>

  <div id="dialog">

    <div>

      <label for="fromTxf"><img src="http://static.arcgis.com/images/Symbols/Shapes/GreenPin1LargeB.png" width=24 height=24></label>

      <input type="text" id="fromTxf" value=" ">

    </div>

    <div >

      <label for="toTxf"><img src="http://static.arcgis.com/images/Symbols/Shapes/RedPin1LargeB.png" width=24 height=24></label>

      <input type="text" id="toTxf" value=" ">

    </div>

    <input id="locations" type="button"  value="Add Locations">                                                  

    <input id="directions" type="button" value="Obține direcții">

    <input id="clearLocations" type="button"  value="Clear Locations">

    <div id="directionsDetail" style="clear:both;">

      <div id="grid"></div>

    </div>

  </div>

</body>

<body>

  <div id="map" class="map">

    <div id="HomeButton"></div>

  </div>

</body>

</html>

IonutAlixandroae
Occasional Contributor

Hi Jake,

I tested the updated code and it works!! Thanks you very much !!!

The network present in the application is made of 3 types of roads : roads/streets circulated by cars and tracks and paths where people walks ( those paths and tracks are from mountain area).

The problem is that when I click in a zone where are predominantly tracks and paths , the geocoding doesn`t works

and I can`t get a stop in that area. Also, in the direction panel I do not have a total of km - it shows me the length per parts ...

P.S. - I`m not quite good at codding and java scripting but I do want to learn

Thanks you again !!

0 Kudos
IonutAlixandroae
Occasional Contributor

I have an idea : is it possible that in areas where reverse geocoding is not possible and it can`t get an address, instead of this to apprear the XY coordinate and create a virtual buffer to intersect to closest road( and the intersection point between buffer and road to be the stop/start point) ??

Thanks!!

0 Kudos
JakeSkinner
Esri Esteemed Contributor

Take a look at this sample.  You can incorporate this into your current app.  Here is a quick example.  It will create a point and add it to the routeParams.stops when the locator errors due to no address found.  You can then click 'Solve Route' to get directions.

0 Kudos
IonutAlixandroae
Occasional Contributor

If you suggest to add barriers I do not need them. I need to be able to click and add start/stop points both in area with address and without address ( in area with address to use reverse geocoding to appear the address in the box - same as you did; and in the areas where reverse geocoding doesn`t work maybe to create a point based on its coordinates and maybe with a tolerance of .... meters to find the nearest street point that will be considered start/stop point).

In the example you gave me ( Edit fiddle - JSFiddle  ) if I click Add Locations ( Start Location in a city zone where reverse geocoding works and End Location in a mountain zone where reverse geocoding doesn`t works ) - see Picture 1 , and after I click Solve Route an error appears - see Picture 2...

I saw the samples but I don`t understand how to use them. I think I`m stucked.....

Thanks !!

Picture 1 :

BeforeSolve.jpg

Picture 2 :

AfterSolve.jpg

0 Kudos
JakeSkinner
Esri Esteemed Contributor

You can solve this by adding the reverse geocode location to the routeParams.stops by updating function fired after the map is clicked:

mapClick = map.on("click", function(evt) { 

            clickLocation = evt; 

            routeParams.stops.features.push(map.graphics.add(new esri.Graphic(clickLocation.mapPoint)));               

            locator.locationToAddress(webMercatorUtils.webMercatorToGeographic(evt.mapPoint), 100);

});

Here is update to the JS Fiddle.  You will probably need to do some testing and updating of the code to get the exact functionality that you are looking for.  For example, it may be best to use one 'Solve Route' button rather than have two.  The fiddle should get you started though.

IonutAlixandroae
Occasional Contributor

Now it works! There are 2 buttons because you can see one button`s id is direction and another`s is solveRoute, if I delete the Obtine directii button the app won`t work, and if I delete the second one( the one you added) then the Route can not be solved anymore.

I tested it and now I can add stops anywhere and if I click Solve Route the route is solved, it works. only that in areas where addresses does not appear, the box is empty.

P.S. : how do I export the code from Fiddle to txt.html ?

Thank you !

0 Kudos
JakeSkinner
Esri Esteemed Contributor

Attached is a text file with the code.

IonutAlixandroae
Occasional Contributor

Thank you very much !!

0 Kudos