Pallavi Anderson

data-driven and context-aware

Realtime Maps With Meteor and Leaflet - Part Two

| Comments

this is a Leaflet map with DivIcon markers

Recap

In the last post, I initialized a Leaflet map to work with Stamen Design’s toner themed map tiles and Bootstrap’s responsive layout. I then set up a double-click event handler to gather additional details about the new party, and hooked up the dialog’s save button to pass those details to a Meteor.methods() call to save the party into a server-side mongo collection. Finally, I hooked up a cursor.observe() added() callback to the client-side minimongo collection and set up the callback to automatically add a circular DivIcon marker at the specified coordinates.

Updating Party Details in the Database

A party document looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  _id: "22dQwpajD64LCv4QW",
  title: "1871",
  description: "Party like it's 1871!",
  latlng: {
    lat: 41.88298161317542,
    lng:  -87.63811111450194
  },
  public: false,
  owner: "52xdsNjprquesL2tQ",
  invited: ["52xdsNjprquesL2tQ", "ci7bzkJCpH9R7HCZK", "5qhRdKFcsmPnxZKBr"]
  rsvps: [
    {
      rsvp: "yes",
      user: "52xdsNjprquesL2tQ"
    },
    {
      rsvp: "maybe",
      user: "ci7bzkJCpH9R7HCZK"
    }
  ]
}

Each party contains an array of RSVP objects, which must be updated when any user adds or updates their RSVP to the party. In addition, private parties contain a set of invited users’ ids; the party owner can invite additional users at any time. So rsvps and invited are the two mutable party attributes in our example. The owner, title, description, coordinates or public/private setting cannot be changed, but a party’s owner can delete the party if no user is RSVPd as Yes.

The code to update and delete parties in the server-side mongo collection is virtually unchanged from the original. The invite() and rsvp() template event handlers are hooked to Meteor.methods() calls that perform the necessary checks before updating the mongo collection on the server. As usual, behind the scenes, Meteor synchronizes the client-side minimongo collection with the server collection.

Updating and Removing Map Markers in Realtime

I hooked up the cursor.observe() changed() callback to update the party’s icon, and removed() callback to delete the marker from the map and the local markers hash.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var map, markers = {};

Template.map.created = function() {
  Parties.find({}).observe({
    added: function(party) {/* see previous post */},
    changed: function(party) {
      var marker = markers[party._id];
      if (marker) marker.setIcon(createIcon(party));
    },
    removed: function(party) {
      var marker = markers[party._id];
      if (map.hasLayer(marker)) {
        map.removeLayer(marker);
        delete markers[party._id];
      }
    }
  });
}

Using a Halo Marker to Indicate Which Party Is Selected

a selected party

Up to this point, there’s been no visual indication on the map as to which party is currently selected. Like in the original Parties example, I solved this by creating a 50px x 50px transparent grey circular marker and making it concentric with the currently selected party’s marker such that it formed a 20px halo around the selected party. The halo marker is purely a UI artefact that does not need to be saved on the server.

1
2
3
4
L.divIcon({
  iconSize: [50, 50], // set to 50px x 50px
  className: 'leaflet-animated-div-icon'
}
1
2
3
4
5
6
.leaflet-animated-div-icon {
  border-radius: 50%;
  border: none;
  opacity: .2;
  background: black;
}

Animating the Halo Marker

For a final flourish, I used the AnimatedMarker Leaflet plugin from OpenPlans to animate the halo’s movement on the map when a user selects different parties rather than simply making it reappear at a different location. AnimatedMarker takes a Leaflet polyline object as the first argument to its initialize function, and draws a marker at the beginning of the polyline, which it then animates along the polyline at a speed (in meters/ms) that’s configurable via a second argument.

I needed to make a minor tweak to the plugin’s source code to support my needs: AnimatedMarker does not allow setting the animation polyline after the marker is initialized. In other words, it requires the animation path to be known before creating the marker. I wanted to create the marker around the currently selected party without knowledge of it’s future animation path, and to set the animation path dynamically as soon as a user selected a different marker – the path would be a segment from the current location to the center of the selected marker. To accomplish this, all I needed to do was reset the animation index in the marker’s setLine method. This modification is available at my fork on github.

And ta-da! This is the end result: http://www.chicago-parties.meteor.com with source code for the complete application. You need to log in with a github account to create or RSVP to parties.

Comments