This commit is contained in:
Ricky Brundritt 2017-10-19 15:10:03 -07:00
Родитель 8b8c7f8ee5
Коммит 321a36e2ca
13 изменённых файлов: 440 добавлений и 67 удалений

Просмотреть файл

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2013/01/nuspec.xsd">
<metadata>
<id>BingMapsRESTToolkit</id>
<version>1.0.6</version>
<version>1.0.7</version>
<title>Bing Maps REST Services Toolkit</title>
<authors>Microsoft</authors>
<owners>microsoft bingmaps</owners>

Просмотреть файл

@ -1,4 +1,9 @@
## Version 1.0.6 ##
## Version 1.0.7
* Extended the RouteRequest class so that it can support more than 25 waypoints. It will simply break the request up into multiple sub-requests, process them, then merge the responses together.
* Bug fix for Distance Matrix waypoint geocoding.
## Version 1.0.6
* Created a .NET Standard v1.4 assembly.
* Add Custom Map Styles support for static images.
@ -7,22 +12,22 @@
* Add Execute method to all requests which contains custom business logic for processing each request. No need to use the ServiceManager now.
* Add new imagery types; AerialWithLabelsOnDemand, BirdseyeV2, BirdseyeV2WithLabels, CanvasGray, CanvasDark, CanvasLight, and RoadOnDemand.
## Version 1.0.5 - 3/22/2017 ##
## Version 1.0.5 - 3/22/2017
* Add support for Geospatial Endpoint service.
## Version 1.0.4 - 2/16/2017 ##
## Version 1.0.4 - 2/16/2017
* Added support for catching network related exceptions.
## Version 1.0.3 - 2/15/2017 ##
## Version 1.0.3 - 2/15/2017
* Added Globalization support for coordinates.
## Version 1.0.2 - 2/8/2017 ##
## Version 1.0.2 - 2/8/2017
* Removed DetailAddress class and replaced with the standard Address class.
## Version 1.0.1 - 1/30/2017 ##
## Version 1.0.1 - 1/30/2017
* Added Imagery Providers to response classes.

Просмотреть файл

@ -1,6 +1,6 @@
![Bing Maps Logo](https://github.com/Microsoft/Bing-Maps-V8-TypeScript-Definitions/blob/master/images/BingMapsLogoTeal.png)
[![NuGet](https://img.shields.io/badge/NuGet-1.0.6-blue.svg)](https://www.nuget.org/packages/BingMapsRESTToolkit)
[![NuGet](https://img.shields.io/badge/NuGet-1.0.7-blue.svg)](https://www.nuget.org/packages/BingMapsRESTToolkit)
[![license](https://img.shields.io/badge/license-MIT-yellow.svg)](https://github.com/Microsoft/BingMapsRESTToolkit/blob/master/LICENSE.md)
# Bing Maps REST Toolkit for .NET #
@ -14,6 +14,7 @@ This is a portable .NET class library which provides a set of tools that make it
* Handles errors and rate limiting by catching exception and returning response with error message.
* Automatically determines when a POST request should be made instead of a GET request.
* Fast indexed lookups of Distance Matrix results.
* Supports calculating routes of any size.
## NuGet Package ##

Просмотреть файл

@ -27,6 +27,7 @@
<Button Content="Reverse Geocode" Click="ReverseGeocodeBtn_Clicked" Height="30"/>
<Button Content="Elevation" Click="ElevationBtn_Clicked" Height="30"/>
<Button Content="Route" Click="RouteBtn_Clicked" Height="30"/>
<Button Content="Long Route" Click="LongRouteBtn_Clicked" Height="30"/>
<Button Content="Transit Route" Click="TransitRouteBtn_Clicked" Height="30"/>
<Button Content="Traffic" Click="TrafficBtn_Clicked" Height="30"/>
<Button Content="Imagery Metadata" Click="ImageMetadataBtn_Clicked" Height="30"/>

Просмотреть файл

@ -167,6 +167,68 @@ namespace RESTToolkitTestApp
ProcessRequest(r);
}
/// <summary>
/// Demostrates how to make a Driving Route Request that has more than 25 waypoints.
/// </summary>
private void LongRouteBtn_Clicked(object sender, RoutedEventArgs e)
{
var r = new RouteRequest()
{
RouteOptions = new RouteOptions()
{
Avoid = new List<AvoidType>()
{
AvoidType.MinimizeTolls
},
TravelMode = TravelModeType.Driving,
DistanceUnits = DistanceUnitType.Miles,
Heading = 45,
RouteAttributes = new List<RouteAttributeType>()
{
RouteAttributeType.RoutePath
},
Optimize = RouteOptimizationType.TimeWithTraffic
},
Waypoints = new List<SimpleWaypoint>() //29 waypoints, more than what the routing service normally handles, so the request will break this up into two requests and merge the results.
{
new SimpleWaypoint(47.5886, -122.336),
new SimpleWaypoint(47.5553, -122.334),
new SimpleWaypoint(47.5557, -122.316),
new SimpleWaypoint(47.5428, -122.322),
new SimpleWaypoint(47.5425, -122.341),
new SimpleWaypoint(47.5538, -122.362),
new SimpleWaypoint(47.5647, -122.384),
new SimpleWaypoint(47.5309, -122.380),
new SimpleWaypoint(47.5261, -122.351),
new SimpleWaypoint(47.5137, -122.382),
new SimpleWaypoint(47.5101, -122.337),
new SimpleWaypoint(47.4901, -122.341),
new SimpleWaypoint(47.4850, -122.320),
new SimpleWaypoint(47.5024, -122.263),
new SimpleWaypoint(47.4970, -122.226),
new SimpleWaypoint(47.4736, -122.265),
new SimpleWaypoint(47.4562, -122.287),
new SimpleWaypoint(47.4452, -122.338),
new SimpleWaypoint(47.4237, -122.292),
new SimpleWaypoint(47.4230, -122.257),
new SimpleWaypoint(47.3974, -122.249),
new SimpleWaypoint(47.3765, -122.277),
new SimpleWaypoint(47.3459, -122.302),
new SimpleWaypoint(47.3073, -122.280),
new SimpleWaypoint(47.3115, -122.228),
new SimpleWaypoint(47.2862, -122.218),
new SimpleWaypoint(47.2714, -122.294),
new SimpleWaypoint(47.2353, -122.306),
new SimpleWaypoint(47.1912, -122.408)
},
BingMapsKey = BingMapsKey
};
ProcessRequest(r);
RequestUrlTbx.Text = "Request broken up into multiple sub-requests.";
}
/// <summary>
/// Demostrates how to make a Transit Route Request.
/// </summary>
@ -392,7 +454,7 @@ namespace RESTToolkitTestApp
/// <summary>
/// Demostrates how to make a Distance Matrix Request.
/// </summary>
private async void DistanceMatrixBtn_Clicked(object sender, RoutedEventArgs e)
private void DistanceMatrixBtn_Clicked(object sender, RoutedEventArgs e)
{
var r = new DistanceMatrixRequest()
{
@ -409,7 +471,8 @@ namespace RESTToolkitTestApp
},
BingMapsKey = BingMapsKey,
TimeUnits = TimeUnitType.Minutes,
DistanceUnits = DistanceUnitType.Miles
DistanceUnits = DistanceUnitType.Miles,
TravelMode = TravelModeType.Transit
};
ProcessRequest(r);
@ -446,6 +509,10 @@ namespace RESTToolkitTestApp
#region Private Methods
/// <summary>
/// This method has a lot of logic that is specific to the sample. To process a request you can easily just call the Execute method on the request.
/// </summary>
/// <param name="request"></param>
private async void ProcessRequest(BaseRestRequest request)
{
try
@ -455,8 +522,6 @@ namespace RESTToolkitTestApp
ResultTreeView.ItemsSource = null;
RequestUrlTbx.Text = request.GetRequestUrl();
var start = DateTime.Now;
//Execute the request.
@ -472,6 +537,8 @@ namespace RESTToolkitTestApp
}
});
RequestUrlTbx.Text = request.GetRequestUrl();
var end = DateTime.Now;
var processingTime = end - start;

Просмотреть файл

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard1.4</TargetFramework>
<Version>1.0.6</Version>
<Version>1.0.7</Version>
<Authors>Microsoft</Authors>
<Company>Microsoft</Company>
<Description>A toolkit that makes it easy to access the Bing Maps REST services from .NET</Description>

Просмотреть файл

@ -23,6 +23,7 @@
*/
using System;
using System.Collections.Generic;
namespace BingMapsRESTToolkit
{

Просмотреть файл

@ -38,6 +38,62 @@ namespace BingMapsRESTToolkit
#endregion
#region Constructor
/// <summary>
/// A pushpin that is rendered on a static map image.
/// </summary>
public ImageryPushpin()
{
}
/// <summary>
/// A pushpin that is rendered on a static map image.
/// </summary>
/// <param name="latitude">The latitude coordinate.</param>
/// <param name="longitude">The longitude coordinate.</param>
public ImageryPushpin(double latitude, double longitude):
this(new Coordinate(latitude, longitude), 1, null)
{
}
/// <summary>
/// A pushpin that is rendered on a static map image.
/// </summary>
/// <param name="coord">The coordinate where to position the pushpin.</param>
public ImageryPushpin(Coordinate coord) :
this(coord, 1, null)
{
}
/// <summary>
/// A pushpin that is rendered on a static map image.
/// </summary>
/// <param name="latitude">The latitude coordinate.</param>
/// <param name="longitude">The longitude coordinate.</param>
/// <param name="iconStyle">The style of icon to render as a pushpin.</param>
/// <param name="label">The label to put on top of the pushpin.</param>
public ImageryPushpin(double latitude, double longitude, int iconStyle, string label):
this(new Coordinate(latitude, longitude), iconStyle, label)
{
}
/// <summary>
/// A pushpin that is rendered on a static map image.
/// </summary>
/// <param name="coord">The coordinate where to position the pushpin.</param>
/// <param name="iconStyle">The style of icon to render as a pushpin.</param>
/// <param name="label">The label to put on top of the pushpin.</param>
public ImageryPushpin(Coordinate coord, int iconStyle, string label)
{
this.label = label;
this.iconStyle = iconStyle;
this.Location = coord;
}
#endregion
#region Public Properties
/// <summary>

Просмотреть файл

@ -174,7 +174,7 @@ namespace BingMapsRESTToolkit
#region Internal Methods
internal string GetUrlParam()
internal string GetUrlParam(int startIdx)
{
var sb = new StringBuilder();
@ -198,14 +198,17 @@ namespace BingMapsRESTToolkit
}
}
if (TravelMode == TravelModeType.Driving && distanceBeforeFirstTurn > 0)
if (startIdx == 0)
{
sb.AppendFormat("&dbft={0}", distanceBeforeFirstTurn);
}
if (TravelMode == TravelModeType.Driving && distanceBeforeFirstTurn > 0)
{
sb.AppendFormat("&dbft={0}", distanceBeforeFirstTurn);
}
if (heading > 0)
{
sb.AppendFormat("&hd={0}", heading);
if (heading > 0)
{
sb.AppendFormat("&hd={0}", heading);
}
}
if (DistanceUnits != DistanceUnitType.Kilometers)
@ -258,7 +261,7 @@ namespace BingMapsRESTToolkit
}
}
if (TravelMode != TravelModeType.Walking)
if (TravelMode != TravelModeType.Walking)
{
sb.AppendFormat("&maxSolns={0}", maxSolutions);
}

Просмотреть файл

@ -180,45 +180,42 @@ namespace BingMapsRESTToolkit
/// <param name="waypoint">The simple waypoint to geocode.</param>
/// <param name="baseRequest">A base request that has the information need to perform a geocode, primarily a Bing Maps key.</param>
/// <returns>A Task in which the simple waypoint will be geocoded.</returns>
internal static Task TryGeocode(SimpleWaypoint waypoint, BaseRestRequest baseRequest)
internal static async Task TryGeocode(SimpleWaypoint waypoint, BaseRestRequest baseRequest)
{
return new Task(async () =>
if (waypoint != null && waypoint.Coordinate == null && !string.IsNullOrEmpty(waypoint.Address))
{
if (waypoint != null && waypoint.Coordinate == null && !string.IsNullOrEmpty(waypoint.Address))
var request = new GeocodeRequest()
{
var request = new GeocodeRequest()
{
Query = waypoint.Address,
MaxResults = 1,
BingMapsKey = baseRequest.BingMapsKey,
Culture = baseRequest.Culture,
Domain = baseRequest.Domain,
UserIp = baseRequest.UserIp,
UserLocation = baseRequest.UserLocation,
UserMapView = baseRequest.UserMapView,
UserRegion = baseRequest.UserRegion
};
Query = waypoint.Address,
MaxResults = 1,
BingMapsKey = baseRequest.BingMapsKey,
Culture = baseRequest.Culture,
Domain = baseRequest.Domain,
UserIp = baseRequest.UserIp,
UserLocation = baseRequest.UserLocation,
UserMapView = baseRequest.UserMapView,
UserRegion = baseRequest.UserRegion
};
try
{
var r = await ServiceManager.GetResponseAsync(request);
try
{
var r = await ServiceManager.GetResponseAsync(request);
if (r != null && r.ResourceSets != null &&
r.ResourceSets.Length > 0 &&
r.ResourceSets[0].Resources != null &&
r.ResourceSets[0].Resources.Length > 0)
{
var l = r.ResourceSets[0].Resources[0] as Location;
waypoint.Coordinate = new Coordinate(l.Point.Coordinates[0], l.Point.Coordinates[1]);
}
}
catch
if (r != null && r.ResourceSets != null &&
r.ResourceSets.Length > 0 &&
r.ResourceSets[0].Resources != null &&
r.ResourceSets[0].Resources.Length > 0)
{
//Do nothing.
var l = r.ResourceSets[0].Resources[0] as Location;
waypoint.Coordinate = new Coordinate(l.Point.Coordinates[0], l.Point.Coordinates[1]);
}
}
});
catch
{
//Do nothing.
}
}
}
/// <summary>

Просмотреть файл

@ -26,5 +26,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.6.0")]
[assembly: AssemblyFileVersion("1.0.6.0")]
[assembly: AssemblyVersion("1.0.7.0")]
[assembly: AssemblyFileVersion("1.0.7.0")]

Просмотреть файл

@ -368,7 +368,7 @@ namespace BingMapsRESTToolkit
if (RouteOptions != null)
{
sb.Append(RouteOptions.GetUrlParam());
sb.Append(RouteOptions.GetUrlParam(0));
}
}

Просмотреть файл

@ -26,6 +26,8 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
namespace BingMapsRESTToolkit
{
@ -39,7 +41,7 @@ namespace BingMapsRESTToolkit
/// <summary>
/// Specifies two or more locations that define the route and that are in sequential order.
/// A route is defined by a set of waypoints and viaWaypoints (intermediate locations that the route must pass through).
/// You can have a maximum of 25 waypoints, and a maximum of 10 viaWaypoints between each set of waypoints.
/// You can have a maximum of 10 viaWaypoints between each set of waypoints.
/// The start and end points of the route cannot be viaWaypoints.
/// </summary>
public List<SimpleWaypoint> Waypoints { get; set; }
@ -49,12 +51,143 @@ namespace BingMapsRESTToolkit
/// </summary>
public RouteOptions RouteOptions { get; set; }
private int batchSize = 25;
/// <summary>
/// The maximium number of waypoints that can be in a single request. If the batchSize is smaller than the number of waypoints, when the request is executed, it will break the request up into multiple requests. Must by between 2 and 25. Default: 25.
/// </summary>
public int BatchSize
{
get
{
return batchSize;
}
set
{
if(value >=2 && value <= 25)
{
batchSize = value;
}
}
}
#endregion
#region Public Methods
/// <summary>
/// Gets the request URL to perform a query for route directions.
/// Executes the request. If there are more waypoints than the batchSize value (default 25), only a MaxSolutions is set to 1, and Tolerances is set to null.
/// </summary>
/// <returns>A response containing the requested data.</returns>
public override async Task<Response> Execute()
{
return await this.Execute(null);
}
/// <summary>
/// Executes the request. If there are more waypoints than the batchSize value (default 25), only a MaxSolutions is set to 1, and Tolerances is set to null.
/// </summary>
/// <param name="remainingTimeCallback">A callback function in which the estimated remaining time is sent.</param>
/// <returns>A response containing the requested data.</returns>
public override async Task<Response> Execute(Action<int> remainingTimeCallback)
{
if(Waypoints.Count <= batchSize)
{
return await base.Execute();
}
//There is more waypoints than the batchSize value (default 25), break it up into multiple requests. Only allow a single route in the response and no tolerances.
if (RouteOptions != null)
{
if (RouteOptions.MaxSolutions > 1)
{
RouteOptions.MaxSolutions = 1;
}
RouteOptions.Tolerances = null;
}
if (Waypoints == null)
{
throw new Exception("Waypoints not specified.");
}
else if (Waypoints.Count < 2)
{
throw new Exception("Not enough Waypoints specified.");
}
else if (Waypoints[0].IsViaPoint || Waypoints[Waypoints.Count - 1].IsViaPoint)
{
throw new Exception("Start and end waypoints must not be ViaWaypoints.");
}
int startIdx = 0;
int endIdx = 0;
var requestUrls = new List<string>();
while (endIdx < Waypoints.Count - 1)
{
requestUrls.Add(GetRequestUrl(startIdx, out endIdx));
startIdx = endIdx - 1;
}
var routes = new Route[requestUrls.Count];
Response response = null;
Response errorResponse = null;
Parallel.For(0, requestUrls.Count, (i) =>
{
try
{
//Make the call synchronously as we are in a parrallel for loop and need this to block, otherwise the for loop will exist before the async code has completed.
using (var responseStream = ServiceHelper.GetStreamAsync(new Uri(requestUrls[i])).GetAwaiter().GetResult())
{
var r = ServiceHelper.DeserializeStream<Response>(responseStream);
if (r != null)
{
if (r.ErrorDetails != null && r.ErrorDetails.Length > 0)
{
errorResponse = r;
}
else if (r.ResourceSets != null && r.ResourceSets.Length > 0 &&
r.ResourceSets[0].Resources != null && r.ResourceSets[0].Resources.Length > 0)
{
routes[i] = r.ResourceSets[0].Resources[0] as Route;
}
}
if (i == 0)
{
response = r;
}
}
}
catch (Exception ex)
{
errorResponse = new Response()
{
ErrorDetails = new string[]
{
ex.Message
}
};
}
});
//If any of the responses failed to process, do not merge results, return the error info.
if(errorResponse != null)
{
return errorResponse;
}
response.ResourceSets[0].Resources[0] = await MergeRoutes(routes);
return response;
}
/// <summary>
/// Gets the request URL to perform a query for route directions. This method will only generate a URL that includes the first batchSize waypoints in the request.
/// </summary>
/// <returns>A request URL to perform a query for route directions.</returns>
public override string GetRequestUrl()
@ -74,6 +207,23 @@ namespace BingMapsRESTToolkit
throw new Exception("Start and end waypoints must not be ViaWaypoints.");
}
int endIdx;
return GetRequestUrl(0, out endIdx);
}
#endregion
/// <summary>
/// Generates a route REST request
/// </summary>
/// <param name="startIdx"></param>
/// <param name="endIdx"></param>
/// <returns></returns>
private string GetRequestUrl(int startIdx, out int endIdx)
{
endIdx = Waypoints.Count;
var sb = new StringBuilder(this.Domain);
var TravelMode = (RouteOptions != null) ? RouteOptions.TravelMode : TravelModeType.Driving;
@ -82,11 +232,11 @@ namespace BingMapsRESTToolkit
int wayCnt = 0, viaCnt = 0;
for (int i = 0; i < Waypoints.Count; i++)
for (int i = startIdx; i < Waypoints.Count; i++)
{
if (Waypoints[i].IsViaPoint)
{
sb.AppendFormat("&vwp.{0}=", i);
sb.AppendFormat("&vwp.{0}=", i - startIdx);
viaCnt++;
if (TravelMode == TravelModeType.Transit)
@ -96,7 +246,7 @@ namespace BingMapsRESTToolkit
}
else
{
sb.AppendFormat("&wp.{0}=", i);
sb.AppendFormat("&wp.{0}=", i - startIdx);
if (viaCnt > 10)
{
@ -115,23 +265,115 @@ namespace BingMapsRESTToolkit
{
sb.AppendFormat("{0}", Uri.EscapeDataString(Waypoints[i].Address));
}
}
if (wayCnt > 25)
{
throw new Exception("More than 25 waypoints in route request.");
//Only allow up to the batchSize waypoints in a request.
if (wayCnt == batchSize)
{
endIdx = i;
break;
}
}
if (RouteOptions != null)
{
sb.Append(RouteOptions.GetUrlParam());
}
sb.Append(RouteOptions.GetUrlParam(startIdx));
}
sb.Append(GetBaseRequestUrl());
return sb.ToString();
}
#endregion
/// <summary>
/// Merges an array of Route objects together into a single route object.
/// </summary>
/// <param name="routes">Array of Route objects to merge.</param>
/// <returns>A single route that consists of all the merged routes.</returns>
private Task<Route> MergeRoutes(Route[] routes)
{
return Task<Route>.Run(() =>
{
if (routes.Length > 0)
{
var mergedRoute = routes[0];
for (var i = 1; i < routes.Length; i++)
{
if (routes[i] != null)
{
mergedRoute.Id = null;
if (mergedRoute.BoundingBox != null)
{
if (routes[i].BoundingBox != null)
{
mergedRoute.BoundingBox[0] = Math.Min(mergedRoute.BoundingBox[0], routes[i].BoundingBox[0]);
mergedRoute.BoundingBox[1] = Math.Min(mergedRoute.BoundingBox[1], routes[i].BoundingBox[1]);
mergedRoute.BoundingBox[2] = Math.Max(mergedRoute.BoundingBox[2], routes[i].BoundingBox[2]);
mergedRoute.BoundingBox[3] = Math.Max(mergedRoute.BoundingBox[3], routes[i].BoundingBox[3]);
}
}
else
{
mergedRoute.BoundingBox = routes[i].BoundingBox;
}
int routePathOffset = (mergedRoute.RoutePath != null) ? mergedRoute.RoutePath.Line.Coordinates.Length : 0;
//Merge the route legs.
var legs = mergedRoute.RouteLegs;
//Loop through each leg and offset path indicies to align with merged path.
for (var j = 0; j < routes[i].RouteLegs.Length; j++)
{
for (var k = 0; k < routes[i].RouteLegs[j].ItineraryItems.Length; k++)
{
for (var l = 0; l < routes[i].RouteLegs[j].ItineraryItems[k].Details.Length; l++)
{
for (var m = 0; m < routes[i].RouteLegs[j].ItineraryItems[k].Details[l].EndPathIndices.Length; m++)
{
routes[i].RouteLegs[j].ItineraryItems[k].Details[l].EndPathIndices[m] += routePathOffset;
}
for (var m = 0; m < routes[i].RouteLegs[j].ItineraryItems[k].Details[l].StartPathIndices.Length; m++)
{
routes[i].RouteLegs[j].ItineraryItems[k].Details[l].StartPathIndices[m] += routePathOffset;
}
}
}
for (var k = 0; k < routes[i].RouteLegs[j].RouteSubLegs.Length; k++)
{
routes[i].RouteLegs[j].RouteSubLegs[k].EndWaypoint.RoutePathIndex += routePathOffset;
routes[i].RouteLegs[j].RouteSubLegs[k].StartWaypoint.RoutePathIndex += routePathOffset;
}
}
mergedRoute.RouteLegs = mergedRoute.RouteLegs.Concat(routes[i].RouteLegs).ToArray();
if (mergedRoute.RoutePath != null)
{
if (routes[i].RoutePath != null)
{
mergedRoute.RoutePath.Line.Coordinates[0] = mergedRoute.RoutePath.Line.Coordinates[0].Concat(routes[i].RoutePath.Line.Coordinates[0]).ToArray();
}
}
else
{
mergedRoute.RoutePath = routes[i].RoutePath;
}
mergedRoute.TravelDistance += routes[i].TravelDistance;
mergedRoute.TravelDuration += routes[i].TravelDuration;
mergedRoute.TravelDurationTraffic += routes[i].TravelDurationTraffic;
}
}
return mergedRoute;
}
return null;
});
}
}
}