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!!
Solved! Go to Solution.
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.
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>
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 !!
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!!
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 :
Picture 2 :
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.
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 !
Thank you very much !!