Bing Maps API – Routenplaner
Wer für sich oder einen Kunden eine eigene Routenplanerkomponente entwickeln möchte, sollte sich Bing Maps mal genauer ansehen. Nicht so populär wie Google Maps, aber eben so gut! Denn wer sich mit der Bing Maps API näher beschäftigt, wird feststellen, dass sich relativ einfach eigenen, tolle Lösungen umsetzen lassen.
Ich habe da mal was vorbereitet.
Über das Backend werden vier Kunden- und Adressdatensätze in die View und weiter in die Bing Map geladen. Rechts neben der Maps werden die ermittelten Wegpunkte mit den jeweiligen Zeiten und Entfernungen aufgelistet. Der Anwender kann diese nun per Drag & Drop umsortieren und die Route neu berechnen. Zum Schluss kann die gewünschte Route zur Speicherung/Verarbeitung an den Server gesendet werden.
Mit der Maus kann bspw. Kunde 4 auf Station 2 verschoben werden.
Wenn die Route nun neu berechnet wird, ergibt sich eine neue, längere Route von 15,91 Km.
Für diese Demo wird im Controller eine Liste von Wegepunkten (Kunden) erstellt und an die View übergeben.
Die zweite Methode “SaveTour” nimmt die kalkulierten Routendaten wieder entgegen, um diese im Backend zu verarbeiten.
public ActionResult Index()
{ var vm = new AzureWebsite.Models.TourViewModel
{ //just some sample data for the view
WayPoints = new List<Models.TourWayPointModel>{
new Models.TourWayPointModel() {
Id= 1,
Address="Zirkusweg 1, 20359 Hamburg",
Name="Kunde 1",
Order=1,
Minutes=0,
Distance=0
},
new Models.TourWayPointModel() {
Id= 2,
Address="Spaldingstraße 1, 20097 Hamburg",
Name="Kunde 2",
Order=2,
Minutes=0,
Distance=0
},
new Models.TourWayPointModel() {
Id= 3,
Address="Hamburger Straße 31, 22083 Hamburg",
Name="Kunde 3",
Order=3,
Minutes=0,
Distance=0
},
new Models.TourWayPointModel() {
Id= 4,
Address="Dorotheenstraße 1, 22301 Hamburg",
Name="Kunde 4",
Order=4,
Minutes=0,
Distance=0
}
}
};
return View(vm);
}
public ActionResult SaveTour(AzureWebsite.Models.TourViewModel vm)
{
//Do something usefull with the route data
return RedirectToAction("Index");
}
In der View ist etwas mehr Anwendungslogik erforderlich. Ich habe den Code der Seite in drei Teile geteilt, damit er etwas übersichtlicher ist.
- Teil 1: Anwendungslogik
- Teil 2: Umsortieren der Wegepunkte mit Drag & Drop
- Teil 3: HTML
Der erste Teil mit der Anwendungslogik ist mit Abstand der größte.
Teil 1: Anwendungslogik mit Bing Map und Routenplanermodul (Microsoft.Maps.Directions)
<script type="text/javascript"
src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0">
</script>
<script type="text/javascript">
var directionsManager;
var map;
function GetMap() {
map = new Microsoft.Maps.Map(document.getElementById("myMap"), {
credentials: 'Your Bing Map Key'
});
Microsoft.Maps.loadModule('Microsoft.Maps.Directions', {
callback: directionsModuleLoaded
});
}
function directionsModuleLoaded() {
directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
var container = document.getElementById('itineraryDiv');
directionsManager.setRenderOptions({
itineraryContainer: container
});
//Add event handlers to directions manager.
Microsoft.Maps.Events.addHandler
(directionsManager, 'directionsError', directionsError);
Microsoft.Maps.Events.addHandler
(directionsManager, 'directionsUpdated', directionsUpdated);
// Set Route Mode to driving
directionsManager.setRequestOptions
({ routeMode: Microsoft.Maps.Directions.RouteMode.driving,
routeDraggable: false,
routeAvoidance: [Microsoft.Maps.Directions.RouteAvoidance.avoidHighways],
distanceUnit: Microsoft.Maps.Directions.DistanceUnit.kilometers, avoidTraffic: true })
;
var waypoints = []; @{ foreach (AzureWebsite.Models.TourWayPointModel waypoint
in Model.WayPoints) {
string str = "waypoints.push({ Id:\"" + waypoint.Id.ToString() + "\",
Name:\"" + waypoint.Name.Replace(Environment.NewLine, "") + "\",
Address:\"" + waypoint.Address + "\",
index:\"" + waypoint.Order.ToString() + "\"});"; WriteLiteral(str); } }
//create route using the waypoints
DirectionComponent.createRoute(waypoints); }
function directionsError(e) {
alert('Error: ' + e.message + '\r\nResponse Code: ' + e.responseCode)
}
// Called on route changed in Map
function directionsUpdated(e)
{
//Aktualisieren der Arbeitsfläche
DirectionComponent.refreshEditorPanel(e);
}
//custom app logic
var DirectionComponent = (function (modul) {
//Create new route by the given waypoints
modul.createRoute = function (pointsOfRoute) {
directionsManager.resetDirections({ removeAllWaypoints: true,
resetRenderOptions: true });
//create Bing Maps waypoint for each customer waypoint
for (i = 0; i < pointsOfRoute.length; i++) {
let point = pointsOfRoute[i];
let waypoint = new Microsoft.Maps.Directions.Waypoint
({ address: point.Address, businessDetails:
{ businessName: point.Name, entityId: point.Is },
shortAddress: point.Name });
directionsManager.addWaypoint(waypoint); }
//calculate tour
directionsManager.calculateDirections(); },
modul.refreshEditorPanel = function (e) {
let list = document.getElementById('sortable');
//initialisiere Inhalt list.innerHTML = '';
let routeIdx = directionsManager.getRequestOptions().routeIndex;
//Get the distance of the route, rounded to 2 decimal places.
let distance = Math.round(e.routeSummary[routeIdx].distance * 100) / 100;
//Get the distance units used to calculate the route.
let units = directionsManager.getRequestOptions().distanceUnit;
let distanceUnits = 'miles';
if (units == Microsoft.Maps.Directions.DistanceUnit.kilimeters)
{ distanceUnits = 'km' }
//Time is in seconds, convert to minutes and round off.
let time = Math.round(e.routeSummary[routeIdx].timeWithTraffic / 60);
let panel = document.getElementById('routeInfoPanel');
let node = document.createElement("P");
node.innerHTML =
'Distance: ' + distance + ' ' + distanceUnits + '<br/>
Time with traffic: ' + time + ' Minutes';
panel.appendChild(node);
let routepartsInfos = this.getCurrentRouteInfo();
let template = "<div id='wayPoint_' >" +
"<div id='index_' ></div>" +
"<b></b></div>" +
"<div></div>" +
"<div><b>Time:</b> Minutes</div>" +
"<div><b>Distance:</b> Km</div>"
//Container elemtes for transporting data
+ "<div class='name-item' style='display:None'></div>"
+ "<div class='id-item' style='display:None'></div>"
+ "<div class='adress-item' style='display:None'></div>"
+ "</div>"
let waypoints = directionsManager.getAllWaypoints();
//fill template if (waypoints.length > 0)
{ for (i = 0; i < waypoints.length; i++)
{ let waypoint = waypoints[i];
//use businessdata to store more customer information
let businessDetails = waypoint.getBusinessDetails();
let itemElem = document.createElement("LI");
let displayName = businessDetails.businessName;
itemElem.innerHTML = template;
itemElem.innerHTML = itemElem.innerHTML.replace(
//g, businessDetails.entityId).replace("", i)
.replace(//g, displayName).replace(//g,
waypoint.getAddress()).replace("",
waypoint._isViapoint);
if (i - 1 >= 0 && i - 1 < routepartsInfos.length)
{ itemElem.innerHTML = itemElem.innerHTML.replace(""
, routepartsInfos[i - 1].time).replace(""
, routepartsInfos[i - 1].distance); }
else
{ itemElem.innerHTML = itemElem.innerHTML.replace("", 0)
.replace("", 0); }
list.appendChild(itemElem); } } },
//Get information from the route legs
modul.getCurrentRouteInfo = function () {
var resultArray = [];
var routes = directionsManager.getRouteResult();
// There must be exactly one route
if (routes && routes.length == 1)
{
var route = routes[0];
for (i = 0; i < route.routeLegs.length; i++) {
let routeLeg = route.routeLegs[i];
resultArray.push({
time: Math.round(routeLeg.summary.time / 60),
distance: (Math.round(routeLeg.summary.distance * 100) / 100) }); } }
return resultArray; },
//Create route from user order waypoint
list modul.calculateTour = function () {
let list = document.getElementById('sortable');
let reorderArray = [];
for (i = 0; i < list.children.length; i++) {
let li = list.children[i];
let wayPointId = $(li).find(".id-item").text();
let name = $(li).find(".name-item").text();
let address = $(li).find(".adress-item").text();
reorderArray.push({ Id: wayPointId, Name: name, Address: address, index: i }); }
//start new route calculation
this.createRoute(reorderArray); },
//Send route data to server for storing
modul.saveWayPoints = function () {
//gather route information
let routeLegInfo = this.getCurrentRouteInfo();
let waypoints = directionsManager.getAllWaypoints();
//fill populate data
let waypointModels = [];
for (i = 0; i < waypoints.length; i++) {
let wayPoint = waypoints[i];
let businessDetails = waypoints[i].getBusinessDetails();
waypointModels.push({
Id: businessDetails.entityId,
Order: i,
Minutes: this.getMinutes(routeLegInfo, waypoints, wayPoint, i - 1),
Distances: this.getDistance(routeLegInfo, waypoints, wayPoint, i - 1),
Name: businessDetails.businessName,
Address: wayPoint.getAddress()
});
}
let tourModel = {
Id: parseInt('@Model.Id'),
WayPoints: waypointModels,
};
//send route data to server (storing or what ever)
this.sendToServer(tourModel); },
modul.sendToServer = function (tourModel) {
jQuery.ajax({ type: "POST", url: "@Url.Action("SaveTour")",
dataType: "json", contentType: "application/json;
charset=utf-8", data: JSON.stringify(tourModel),
success: function (data) { alert(data); },
failure: function (errMsg) { alert(errMsg); } }); },
modul.getMinutes = function (routeLegInfos, wayPoints, wayPoint, routeLegindex)
{
var min = 0;
if (routeLegindex >= 0 && routeLegindex < routeLegInfos.length)
{
var wayPointIndex = routeLegindex;
min = routeLegInfos[routeLegindex].time;
} return min;
},
modul.getDistance = function (routeLegInfos, wayPoints, wayPoint, routeLegindex) {
let dist = 0;
if (routeLegindex >= 0 && routeLegindex < routeLegInfos.length) { let wayPointIndex = routeLegindex; dist = routeLegInfos[routeLegindex].distance; } return dist; } return modul; }(DirectionComponent || {}));
</script>
Teil 2: Die Liste der Wegepunkte sortierbar machen
<script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script> $(function () { $("#sortable").sortable(); $("#sortable").disableSelection(); }); $(document).ready(function () { GetMap(); }); </script>
Teil 3: HTML-Struktur und CSS
<style> #column1 { float: left; width: 70%; } #column2 { float: left; width: 30%; } .map-container { margin-top: 20px; } #sortable { list-style-type: none; margin: 0; padding: 0; } #sortable li { margin: 0 3px 3px 3px; padding: 0.4em; padding-left: 1.5em; font-size: 1em; height: 110px; background: #f6f6f6; border: 1px solid #c5c5c5; } </style> <div class="map-container"> <div id="column1"> <div id="myMap" style="position: relative; width: 600px; height: 400px;"></div> </div> <div id="column2"> <div id='routeInfoPanel'> <ul id="sortable"></ul> </div> <button onclick="DirectionComponent.calculateTour()">Berechnen</button> <button onclick="DirectionComponent.saveWayPoints()">Speichern</button> </div> <div id='itineraryDiv' style="position:relative; width:400px;"></div> </div>
Ich hoffe der Artikel hat euch gefallen und kann nur jedem empfehlen die Bing Maps API auszuprobieren. Es lohnt sich!