8.2. Rendering MapsNow that you know about views, map styles, and how to get a map image for both Windows and web applications, in this section of the chapter, let's look at how to render places, addresses, pushpins, and routes. 8.2.1. Rendering Places and AddressesRendering a place or address on a map starts with some type of Find calleither a Find call for a place or a FindAddress call. Once you successfully find a place or an address, use the found location's best map view to get the map using the RenderServiceSoap.GetMap method. For example, to render New York City on a map, start with the Find call and pass the found location's best map view to the GetMap method: //Find New York, NY FindServiceSoap findService = new FindServiceSoap( ); //Assign credentials . . . //Define find specification FindSpecification findSpec = new FindSpecification( ); //Assign data source findSpec.DataSourceName = "MapPoint.NA"; //Assign input place findSpec.InputPlace = "New York, NY"; //Find place FindResults foundResults = findService.Find(findSpec); //Get the best map view ViewByHeightWidth view = foundResults.Results[0].FoundLocation.BestMapView.ByHeightWidth; //Get Render Service Soap RenderServiceSoap renderService = new RenderServiceSoap( ); //Assign credentials . . . //Define map specification MapSpecification mapSpec = new MapSpecification( ); //Assign data source mapSpec.DataSourceName = "MapPoint.NA"; //Assign the view mapSpec.Views = new MapView[] {view} //Get the map image MapImage[] mapImages = renderService.GetMap(mapSpec); //Get the bitmap image and assign it to a picture box System.IO.Stream streamImage = new System.IO.MemoryStream(mapImages[0].MimeData.Bits); Bitmap bitmapImage = new Bitmap(streamImage); //Assign it to the picture box pictureBox1.Image = bitmapImage; When this code is executed, MapPoint Web Service renders the map shown in Figure 8-5. Figure 8-5. Place map with default map optionsThis map was rendered using all default map options, such as style, zoom level, and map image size; with the default settings, the map image size was 240 pixels high and 296 pixels wide. What if you need a map 400 pixels high and 600 pixels wide? In order to render a map image with different dimensions, use the MapOptions.Format property of the MapOptions object. The Format property is of type ImageFormat object, and it holds the definition for the map image settings , such as the height, width, and Mime type of the image. To get a 400 x 600 map image, set the MapOptions as follows: //Create MapSpecification object MapSpecification mapSpec = new MapSpecification( ); //Assign views and data source . . . //Create MapOptions mapSpec.Options = new MapOptions( ); //Set Map Image Format Settings mapSpec.Options.Format = new ImageFormat( ); //Set height mapSpec.Options.Format.Height = 400; //Set width mapSpec.Options.Format.Width = 600; //Get map MapImage[] mapImages = renderService.GetMap(mapSpec); Once you add the map image specifications for width and height, the map is rendered using the desired settings, as shown in Figure 8-6. Figure 8-6. A 400 x 600 map rendered for New York, NYThe map is now the desired size, but there is still a problemeven though we know the map center is New York, there is no clear indication of which place you were looking for. To work around this issue, you can place a pushpin on New York City as a visual indication using the MapSpecifications.Pushpins property. 8.2.2. Rendering PushpinsTo render pushpins on a map, you need to define them and assign them to the corresponding MapSpecification.Pushpins property. This property takes an array of Pushpin objects that define the exact locations (as latitude/longitude coordinates) to be marked with pushpins. To add a pushpin to the map in Figure 8-6 to show that exactly where New York City is, add a Pushpin to the MapSpecification object: //Create a pushpin Pushpin pin = new Pushpin( ); //Assign data source pin.IconDataSource = "MapPoint.Icons"; //Assign icon name pin.IconName = "1"; //Assign label pin.Label = this.inputPlace.Text; //Assign location pin.LatLong = foundLocation.LatLong; //Add pushpin to map specificiation mapSpec.Pushpins = new Pushpin[] {pin}; Adding this code renders a map with a pushpin as shown in Figure 8-7. Figure 8-7. Map rendered with a pushpinThe MapSpecifications.Pushpins property is an array of pushpins, so of course you can draw more than one pushpin. However, the maximum limit to the number of pushpins that can be rendered on one map is 100. If you have more than 100 pushpins to be displayed on one map, the best solution is to implement pagination without cluttering the map with too many pushpins. If you look at how each pushpin is defined, it has a data source (standard data source is MapPoint.Icons, which has number of pushpins that you can use), a label, and a latitude/longitude.
Of course, you can also upload your own icons to the MapPoint Web Service servers using the Customer Services site to use them with your render calls. Since you know how to render one pushpin, let's learn how to add more pushpins. 8.2.2.1. Rendering points of interestOne of the most frequently used scenarios for rendering maps and pushpins is to find a place and render points of interest around it. For example, first find New York City, and then render all coffee shops within five miles: //Define a find nearby specification FindNearbySpecification fnbSpec = new FindNearbySpecification( ); //Assign data source fnbSpec.DataSourceName = "MapPoint.FourthCoffeeSample"; //Assign original location fnbSpec.LatLong = foundLocation.LatLong; //Assign distance fnbSpec.Distance = 5.0; //Assign entity type fnbSpec.Filter = new FindFilter( ); fnbSpec.Filter.EntityTypeName = "FourthCoffeeShops"; //Find nearby coffeeshops FindResults findResults = findService.FindNearby(fnbSpec); //Add all locations to an array list System.Collections.ArrayList pinList = new ArrayList( ); foreach(FindResult findResult in findResults.Results) { //Create a pushpin Pushpin pin = new Pushpin( ); pin.IconDataSource = "MapPoint.Icons"; pin.IconName = "CoffeeShopIcon"; pin.LatLong = findResult.FoundLocation.LatLong; pin.Label = findResult.FoundLocation.Entity.DisplayName; pinList.Add(pin); } //Add the original location pin Pushpin originalLoc = new Pushpin( ); originalLoc.IconDataSource = "MapPoint.Icons"; originalLoc.IconName = "33"; originalLoc.LatLong = foundLocation.LatLong; originalLoc.Label = "New York, NY"; pinList.Add(originalLoc); //Assign pins to the map specification mapSpec.Pushpins = pinList.ToArray(typeof(Pushpin)) as Pushpin[]; //Get map After finding coffee shops around the input place, I added them to an ArrayList so that I can assign all the coffee shop locations, along with the original location, to the MapSpecification.Pushpins property to render on a map. When this code is executed, a map is rendered as shown in Figure 8-8. Figure 8-8. Rendering multiple pushpins on a mapThe map is not usable because the original map view is optimized to display the input location (New York, NY) but not the points of interest around it. You need to recalculate the map view to be optimized for all of these locations before rendering it. There are two methods you can use to perform the recalculation of the map view with all the pushpins around:
Either way, you will get a better-looking map. In the following code, I have added each coffee shop to another array list that holds all location objects to be used for ViewByBoundingLocations view, which will be defined later using those locations: //Add all locations to an array list System.Collections.ArrayList locationList = new ArrayList( ); //Add all pushpins to an array list System.Collections.ArrayList pinList = new ArrayList( ); foreach(FindResult findResult in findResults.Results) { //Create a pushpin Pushpin pin = new Pushpin( ); pin.IconDataSource = "MapPoint.Icons"; pin.IconName = "CoffeeShopIcon"; pin.LatLong = findResult.FoundLocation.LatLong; //pin.Label = findResult.FoundLocation.Entity.DisplayName; pinList.Add(pin); //Add location locationList.Add(findResult.FoundLocation); } //Add the original location pin Pushpin originalLoc = new Pushpin( ); originalLoc.IconDataSource = "MapPoint.Icons"; originalLoc.IconName = "33"; originalLoc.LatLong = foundLocation.LatLong; originalLoc.Label = "New York, NY"; pinList.Add(originalLoc); //Define view ViewByBoundingLocations vbl = new ViewByBoundingLocations( ); //View by Locations vbl.Locations = locationList.ToArray(typeof(Location)) as Location[]; //Assign pins to the map specification mapSpec.Pushpins = pinList.ToArray(typeof(Pushpin)) as Pushpin[]; //Assign view mapSpec.Views = new MapView[] {vbl}; //Get map When this code is executed, MapPoint Web Service recalculates the map view to fit all locations within an optimized view for all the encompassing locations. The map rendered for the new view is shown in Figure 8-9. The map clearly shows all the coffee shops without much clutter, but some coffee shops overlap each other. How can you avoid this issue? Figure 8-9. Map rendered with recalculated view8.2.2.2. Avoiding icon collisionMapPoint Web Service allows you to render pushpins without icon collisions using the MapOptions.PreventIconCollisions property: //Prevent Icon Collisions mapSpec.Options.PreventIconCollisions = true; When this flag is set to true, the map is rendered as shown in Figure 8-10 in "icons on stick" mode to prevent icon collisions. As you can see, the map is now free of colliding icons and is much more readable. 8.2.2.3. Suppressing standard entity typesTo improve the readability, you can also suppress standard entities from the map. For example, in the map shown in Figure 8-10, there are two subway stations rendered on the map along with the coffee shops. For improved readability, you can suppress that extraneous information using the MapSpecification.HideEntityTypes property. This property takes an array of standard entity type names that needs to be eliminated from rendering; the map in Figure 8-11 is rendered when you chose to eliminate the MetroStation entity type from rendering: //Hide entity types mapSpec.HideEntityTypes = new string[] {"MetroStation"}; The map no longer renders the subway stations. Figure 8-10. Rendering icons without collisions
8.2.2.4. Converting pushpins to pixel coordinatesSometimes, you may need to know the pixel coordinates of the pushpins rendered on the map or the latitude and longitude coordinates of the pushpin pixel coordinates. Render Service offers two methods, ConvertToPoint and ConvertToLatLong, to calculate pixel position from a LatLong object and to obtain LatLong objects from pixel coordinates on a rendered map. The key to converting location to pixel and pixel to location on a map is the map view, which MapPoint Web Service uses to perform these calculations. If you have a requirement to build an application to obtain latitude and longitude when a user clicks on a map, it can be accomplished as follows: //Define a pixel coordinate array PixelCoord[] pixels = new PixelCoord[1]; pixels[0] = new PixelCoord( ); //Trap OnMouseDown event and X, Y coordinates pixels[0].X = e.X; pixels[0].Y = e.Y; Figure 8-11. Route rendered on a map//Get the latitude longitude from the point LatLong[] latlongs = renderService.ConvertToLatLong(pixels, mapSpec.Views[0], 400, 600); The method ConvertToLatLong takes the pixel position, current map view, and the height and width of the map image (not the map distance covered on the ground) and returns the latitude and longitude coordinates for the corresponding pixel positions. 8.2.3. Rendering RoutesAlthough we have talked enough about rendering points, we have not yet discussed rendering routes on a map. As it turns out, the same MapSpecification object and the GetMap method can be used to render routes. The only difference is that while rendering routes, in addition to the map view, you also need to assign the route to be rendered on the map. The assigned Route object must have either the Route.Specification or the Route.CalculatedRepresentation present. Here is an example of rendering a route: //Route between two locations LatLong[] latLongs = new LatLong[2]; latLongs[0] = new LatLong( ); latLongs[1] = new LatLong( ); //Define start location latLongs[0].Latitude = 40; latLongs[0].Longitude = -120; //Define stop location latLongs[1].Latitude = 41; latLongs[1].Longitude = -121; //Create a route service RouteServiceSoap routeService = new RouteServiceSoap( ); //Add credentials . . . //Calculate route Route route = routeService.CalculateSimpleRoute(latLongs, "MapPoint.NA", SegmentPreference.Quickest); //Get a map view of the route ViewByHeightWidth[] views = new ViewByHeightWidth[1]; views[0] = route.Itinerary.View.ByHeightWidth; //Create Start Pushpin Pushpin start = new Pushpin( ); start.IconName = "1"; start.IconDataSource = "MapPoint.Icons"; start.Label = "Start"; start.LatLong = latLongs[0]; //Create Stop Pushpin Pushpin stop = new Pushpin( ); stop.IconName = "1"; stop.IconDataSource = "MapPoint.Icons"; stop.Label = "Stop"; stop.LatLong = latLongs[1]; //Define map specification MapSpecification mapSpec = new MapSpecification( ); mapSpec.DataSourceName = "MapPoint.NA"; //Assign view mapSpec.Views = views; //Assign route mapSpec.Route = route; //Assign start and stop locations mapSpec.Pushpins = new Pushpin[] {start, stop}; //Assign map options mapSpec.Options = new MapOptions( ); mapSpec.Options.Format = new ImageFormat( ); mapSpec.Options.Format.Height = 400; mapSpec.Options.Format.Width = 600; //Get map . . . When this code is executed, the map in Figure 8-12 is rendered. Figure 8-12. Standard route mapIf you pass only the route and not the view, the entire route is rendered on the map. So, if you have a long route and want to render only a section of it, you can use the views; in case of a missing view specification, GetMap renders the entire route on the map. Also, keep in mind that any itinerary present in the Route object is ignored during the GetMap method. You render routes using the standard GetMap method, but if you want to render LineDrive route maps, use the GetLineDriveMap method. 8.2.4. Rendering LineDrive MapsBefore we get into rendering LineDrive maps , let's look at what exactly they are. LineDrive maps are route maps that show the map of a route in an intuitive format, much as you might draw directions for a friend on a piece of paper. LineDrive provides the essential information about a route in an easy-to-use format that includes start and end points, names of streets and cross-streets, and mileage information. The core difference between a regular route map and a LineDrive map is that a LineDrive map gives more emphasis to starting/ending points and turns. For example, if you are driving from Redmond, WA to Portland, OR, a regular route map may look like Figure 8-13. Figure 8-13. Regular route mapRendering the same route on a LineDrive map gives you the map shown in Figure 8-14. More emphasis is given to the turns and the starting and end points in the LineDrive map. It is important to note that a LineDrive route is not always identical to the route created using the standard map style. LineDrive maps also have some limitations in that they:
To render a LineDrive map, you still need a valid Route object (after all, a LineDrive map is just a variation in rendering a route). Call the GetLineDriveMap method using a valid LineDriveMapSpecification object. The LineDriveSpecification object specifies the size of the map, the route to be rendered, and the palette to be used for rendering the LineDrive map. You can pick a palette style using the PaletteType enumeration. The available options for the PaletteType enumeration are shown in Table 8-4. Figure 8-14. LineDrive Map for the same route as in Figure 8-13
The following code shows how to use the LineDriveSpecification object to render a LineDrive map displaying a route: //Create an instance of RenderServiceSoap and //Assign Credentials RenderServiceSoap renderSoap = new RenderServiceSoap( ); //Create a LineDriveMapSpecification object LineDriveMapSpecification lineDriveSpec = new LineDriveMapSpecification( ); //Assign the route to be rendered //Assumes that the route is pre-calculated. lineDriveSpec.Route = myroute; //Set the LineDriveMapOptions such as Palette LineDriveMapOptions lineDriveOptions = new LineDriveMapOptions( ); //Return Map Url lineDriveOptions.ReturnType = MapReturnType.ReturnUrl; //Set palette type as Color map lineDriveOptions.PaletteType = PaletteType.Color; //Assign options to the specification lineDriveSpec.Options = lineDriveOptions; //Get LineDrive maps LineDriveMapImage[] lineDriveMaps = renderSoap.GetLineDriveMap(lineDriveSpec); //Display the LineDrive Maps . . . The GetLineDriveMap returns the LineDriveMapImage array. Depending on the driving directions, the LineDrive map may be split into many map images, so you have to check for the image array length and display them accordingly. Also, to display the driving directions along with each LineDrive map image, match each LineDriveMapImage instance with the corresponding driving directions using the FirstDirectionID and LastDirectionID properties. The following code shows how to display LineDriveMapImage array along with the matching driving directions: //Get LineDrive Maps LineDriveMapImage[] lineDriveMapImages = renderSoap.GetLineDriveMap(lineDriveSpec); //Now process them to display the map and the driving directions foreach(LineDriveMapImage lineDriveImage in lineDriveMapImages) { //Display the map image //Something like: Assign the url to your img tag . . . //Display the matching driving directions FormatDirectionsForLineDriveImage(lineDriveImage, lineDriveSpec.Route); } The helper function FormatDirectionsForLineDriveImage is shown in the following code: //Returns the matching driving directions for a LineDriveImage private string FormatDirectionsForLineDriveImage( LineDriveMapImage lineDriveMapImage, Route route) { if(lineDriveMapImage == null || route == null) return string.Empty; System.Text.StringBuilder stringBuffer = new System.Text.StringBuilder( ); //For each route segment foreach(Segment segment in route.Itinerary.Segments) { //For each direction entry foreach(Direction direction in segment.Directions) { //See if the id of the direction falls in the range for the current //LineDrive image if(direction.ID >= lineDriveMapImage.FirstDirectionID || direction.ID <= lineDriveMapImage.LastDirectionID) { //If so display it stringBuffer.Append(direction.Instruction); //Append a line break for display formatting purposes stringBuffer.Append("<BR>"); } } } //Return the matching directions return stringBuffer.ToString( ); } Use the LineDriveMapImage.FirstDirectionID and LineDriveMapImage.LastDirectionID properties to match the driving directions. Now that you have seen rendering of both points and routes, let's look at rendering polygons . 8.2.5. Rendering PolygonsIn Chapter 6, we looked at finding polygons depending on various spatial filters; in this section, we'll learn how to render the polygons using render service. Rendering polygons in MapPoint Web Service is fairly straightforward; just like rendering roads and pushpins, you use the RenderServiceSoap.GetMap method to render polygons by specifying which polygons to render using the MapSpecification object. Before getting into the rendering details, let's look at how polygons are represented programmatically in MapPoint Web Service. In MapPoint Web Service, polygons are programmatically represented by Polygon class instances. Each Polygon class exposes a set of fields listed in Table 8-5 that define the identity and style of the polygon.
The BorderColor and FillColor use the ElementColor object to set the colors and transparency of the polygon. The ElementColor class defines the Red, Blue, and Green components of the color, along with the Alpha value, which defines transparency. Valid values range from 0 to 255. The fields exposed by the ElementColor class are shown in Table 8-6.
Let's look at the following code, which defines a polygon using the Polygon class: //Define a polygon instance Polygon polygon = new Polygon( ); //Assign data source name and id polygon.DataSourceName = "your poly data source"; polygon.EntityID = 23354; //Define border color ElementColor borderColor = new ElementColor( ); borderColor.A=221; borderColor.B=255; borderColor.G=255; borderColor.R=128; //Define fill color ElementColor fillColor = new ElementColor( ); fillColor.A=90; fillColor.B=255; fillColor.G=255; fillColor.R=128; //Assign border and fill colors polygon.BorderColor=borderColor; polygon.FillColor=fillColor; Once you have a valid polygon, it is easy to render it using the GetMap method: //Define a RenderServiceSoap instance RenderServiceSoap renderSoap = new RenderServiceSoap( ); //Assign credentials . . . //Define a MapSpecification object MapSpecification mapSpec = new MapSpecification( ); //Assign rendering data source spec.DataSourceName = "MapPoint.NA"; //Assign views and map options . . . //Create a polygon //Define a polygon instance Polygon polygon = new Polygon( ); //Assign data source name and id polygon.DataSourceName = "your poly data source"; polygon.EntityID = 23354; //Define border color ElementColor borderColor = new ElementColor( ); borderColor.A=221; borderColor.B=255; borderColor.G=255; borderColor.R=128; //Define fill color ElementColor fillColor = new ElementColor( ); fillColor.A=90; fillColor.B=255; fillColor.G=255; fillColor.R=128; //Assign border and fill colors polygon.BorderColor=borderColor; polygon.FillColor=fillColor; //Assign to Polygons array mapSpec.Polygons = new Polygon[1] {polygon}; //Render them on a map MapImage[] mapImages = renderSoap.GetMap(mapSpec); Creating Polygon instances and assigning them to the MapSpecification.Polygons field is all it takes to render polygons using the GetMap method. Now that you know how to render points, routes, and polygons, let's look at map interaction, such as panning and zooming, in the context of rendering. |