The map events and setting pins

We have maps, and we have the logic to save points of interest to databases and to move them in memory. The one thing we don't have is the code to handle the user actually creating and managing the pins from the map itself. It's time to rectify that situation and add in a MapEvents class that will handle this for us. Just like the MapGeocode, PinModel, and PinsModel classes, this class is a standalone implementation. Let's start by adding the following code:

export class MapEvents {
private readonly geocode: MapGeocode;
private infoBox: Microsoft.Maps.Infobox;

constructor(private map: Microsoft.Maps.Map, private pinsModel: PinsModel, private poi: PointsOfInterestService) {

}
}

Infobox is the box that appears when we add a point of interest to the screen. We could add a new one when each point of interest is added, but this would be a waste of resources. Instead, we are going to add a single Infobox and reuse it when we add new points on the screen. To do this, we are going to add a helper method that checks whether the Infobox has been set or not previously. If it has not been set before, we will instantiate a new instance of the Infobox, taking in the pin location, the title, and description. We will be supplying the name of the point as the description. We need to set the map instance that this will appear on using setMap. When we reuse this Infobox, all we need to do is set the same values in the options and then set the visibility to true:

private SetInfoBox(title: string, description: string, pin: Microsoft.Maps.Pushpin): void {
if (!this.infoBox) {
this.infoBox = new Microsoft.Maps.Infobox(pin.getLocation(), { title: title, description: description });
this.infoBox.setMap(this.map);
return;
}
this.infoBox.setOptions({
title: title,
description: description,
location: pin.getLocation(),
visible: true
});
}

There are a couple of helper methods we still need to add to this class before we add the ability to select points from the map. The first one we are going to add takes the points of interest from the Local Insights search and adds them to the map. Here, we can see that the way that we add a pin is to create a green Pushpin, which then gets added onto our Bing map at the correct Location. We also add an event handler that reacts to a click on the pin and shows the Infobox using the method we just added:

AddPoi(pois: PoiPoint[]): void {
pois.forEach(poi => {
const pin: Microsoft.Maps.Pushpin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(poi.lat, poi.long), {
color: Microsoft.Maps.Color.fromHex('#00ff00')
});
this.map.entities.push(pin);
Microsoft.Maps.Events.addHandler(pin, 'click', (x) => {
this.SetInfoBox('Point of interest', poi.name, pin);
});
})
}

The next helper method is more complicated, so we will add it in stages. The AddPushPin code is going to be called when the user clicks on the map. The signature looks as follows:

AddPushPin(e: any): void {
}

The first thing that we are going to do in this method is create a Guid to use when we add a PinsModel entry and add a draggable Pushpin at the click location:

const guid: Guid = Guid.create();
const pin: Microsoft.Maps.Pushpin = new Microsoft.Maps.Pushpin(e.location, {
draggable: true
});

With this in place, we are going to call the ReverseGeocode method we wrote earlier. When we get the result from this, we will add our PinsModel entry and push the Pushpin onto the map before we show the Infobox:

this.geocode.GeoCode(e.location).then((geocode) => {
this.pinsModel.Add(guid.toString(), geocode, e.location.latitude, e.location.longitude);
this.map.entities.push(pin);
this.SetInfoBox('User location', geocode, pin);
});

We haven't finished with this method yet. As well as adding a Pushpin, we also have to be able to drag it so that the user can choose a new location for it when they drag the pin. We are going to use the dragend event to move the pin. Again, the hard work we put in earlier pays dividends, because we have a simple mechanism to Move the PinsModel and display our Infobox:

const dragHandler = Microsoft.Maps.Events.addHandler(pin, 'dragend', (args: any) => {
this.geocode.GeoCode(args.location).then((geocode) => {
this.pinsModel.Move(guid.toString(), geocode, args.location.latitude, args.location.longitude);
this.SetInfoBox('User location (Moved)', geocode, pin);
});
});

Finally, when the user clicks a pin, we want to remove the pin from the PinsModel and the map. When we added the event handlers for dragend and click, we saved the handlers to variables so that we can use them to remove the event handlers from the map events. Tidying up after ourselves is good practice, especially when dealing with things like event handlers:

const handler = Microsoft.Maps.Events.addHandler(pin, 'click', () => {
this.pinsModel.Remove(guid.toString());
this.map.entities.remove(pin);

// Tidy up our stray event handlers.
Microsoft.Maps.Events.removeHandler(handler);
Microsoft.Maps.Events.removeHandler(dragHandler);
});

Well, that's our helper methods in place. All we need to do now is update the constructor to add the ability to click on the map to set a point of interest and search for Local Insights when the viewport that the user is looking at changes. Let's start with responding to the user clicking on the map:

this.geocode = new MapGeocode(this.map);
Microsoft.Maps.Events.addHandler(map, 'click', (e: any) => {
this.AddPushPin(e);
});
We don't need to store the handler as a variable here because we are associating it with something that won't be removed at any stage while the application is live in the browser; namely, the map itself.

When the user moves the map around so that they can see other areas, we need to perform the Local Insights search and, based on the results that come back, add the points of interest. We attach an event handler to the map viewchangeend event to trigger this search:

Microsoft.Maps.Events.addHandler(map, 'viewchangeend', () => {
const center = map.getCenter();
this.poi.Search([center.latitude, center.longitude]).then(pointsOfInterest => {
if (pointsOfInterest && pointsOfInterest.length > 0) {
this.AddPoi(pointsOfInterest);
}
})
})

We keep seeing that preparing methods beforehand can save us so much time later on. We are simply leveraging the PointsOfInterestService.Search method to do our Local Insights search for us, and then pumping the results into our AddPoi method if we get any back. If we don't want to perform the Local Insights search, we can simply remove this event handler and won't need to do any searching.

The only thing that we have left to do is handle the loading in of our pins from the database. The code here is a variation of the code we have seen already for adding the click and dragend handlers, but we don't need to perform the geocoding, since we already have the name of each point of interest. Therefore, we aren't going to reuse the AddPushPin method. Instead, we will opt to do this whole section inline. The load subscription looks as follows:

const subscription = this.pinsModel.Load().subscribe((data: PinModelData[]) => {
data.forEach(pinData => {
const pin: Microsoft.Maps.Pushpin = new Microsoft.Maps.Pushpin(new Microsoft.Maps.Location(pinData.lat, pinData.long), {
draggable: true
});
this.map.entities.push(pin);
const handler = Microsoft.Maps.Events.addHandler(pin, 'click', () => {
this.pinsModel.Remove(pinData.id);
this.map.entities.remove(pin);
Microsoft.Maps.Events.removeHandler(handler);
Microsoft.Maps.Events.removeHandler(dragHandler);
});
const dragHandler = Microsoft.Maps.Events.addHandler(pin, 'dragend', (args: any) => {
this.geocode.GeoCode(args.location).then((geocode) => {
this.pinsModel.Move(pinData.id, geocode, args.location.latitude, args.location.longitude);
this.map.entities.push(pin);
this.SetInfoBox('User location (moved)', geocode, pin);
});
});
});
subscription.unsubscribe();
this.pinsModel.AddFromStore(data);
});

The point to note with this code snippet is that, since we are dealing with a subscription, once we have completed the subscription, we unsubscribe from it. The subscription should return an array of PinModelData items that we iterate over, adding in the elements as needed.

That's it. We now have a working mapping solution in place. This was one of the chapters I was looking forward to writing the most because I love mapping applications. I hope you have as much fun with this as I have. Before we leave this chapter, though, if you want to prevent people from getting unsecured access to the data, you can apply that knowledge in the next section.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.114.142