The Garmin FR60 is a versatile heart rate monitor that can measure speed and distance while running or biking.
The Garmin FR60 is a versatile heart rate monitor that can measure speed and distance while running or biking.
I attempted to create an animated weather map in Bing Maps by using a custom tilelayer that pointed to a set of dynamically-generated animated GIF image tiles. Note that the tilesource itself never changed – it was the tile images which the tilelayer pointed to that were animated, rather than the tilesource itself. There are a number of limitations with this approach, since there is no way in the client application to control the animation – it just repeats in a fixed loop and, because each tile is loaded and animated independently, there is a risk for the animations frames to become out-of-sync.
In order to have control over the animation, we don’t want the frames to be hard-baked into an animated GIF, but rather we want to be able to refresh a tile layer programmatically, updating the underlying image source based on some client event or after a specified time interval. This is what I’ll look at it this post.
To change the properties of an existing tile layer, you use the setOptions() method of the tile layer, passing in the same TileLayerOptions structure as you do when constructing a new layer:
tilelayer.setOptions(tileLayerOptions);
The problem is that, although the TileLayerOptions object defines a tilesource property that can be passed to the setOptions() method (and there is nothing in the documentation to suggest otherwise), it seems that you can’t change the tilesource of an existing tilelayer – any tilesource specified in the TileLayerOptions supplied to setOptions() is simply ignored. You can verify this quite easily using the following code (which also demonstrates the undocumented getVisible() method of the TileLayer class):
// Declare a set of tile layer options var tileLayerOptions = { mercator: new Microsoft.Maps.TileSource({ uriConstructor: 'http://mapsys.info/wp-content/uploads/2011/05/25b2588fcbtile1.jpg.jpg' }), opacity: 0.1, visible: false, zIndex: 20 } // Declare another set of tile layer options var tileLayerOptions2 = { mercator: new Microsoft.Maps.TileSource({ uriConstructor: 'http://mapsys.info/wp-content/uploads/2011/05/940347c14ftile2.jpg.jpg' }), opacity: 0.2, visible: true, zIndex: 25 }; // Create a tile layer based on the first options var tilelayer = new Microsoft.Maps.TileLayer(tileLayerOptions); // Change the options of the tile layer tilelayer.setOptions(tileLayerOptions2); // Check the current options of the tile layer alert( 'TileSource: ' + tilelayer.getTileSource('mercator').getUriConstructor() + 'n' + 'Opacity: ' + tilelayer.getOpacity() + 'n' + 'Visible: ' + tilelayer.getVisible() + 'n' + 'ZIndex: ' + tilelayer.getZIndex() );
The results of the previous code listing will demonstrate that the opacity, visibility, and z-index properties of the tilelayer have all been updated to reflect the changed options passed to the setOptions() method, but the tilesource property is still the original, unchanged rand_tile1.jpg file.
So, if the tilesource can only be defined at the point the tile layer is first created, we can’t animate an existing tilelayer. Instead, we have to create a new tile layer every time we want to display a new frame in the animation.
A first approach at animating between frames might simply drop the existing tilelayer and then immediately create a new tilelayer in its place, advancing the source image to the next frame of the animation, something like this:
var frames = ['frame1.png', 'frame2.png', 'frame3.png', 'frame4.png']; for(var i = 0; i < frames.length; i++) { DisplayFrame(frames[i]); } function DisplayFrame(frame) { // Remove the existing frame map.entities.clear(); // Create a tilelayer showing the next frame var tilelayer = new Microsoft.Maps.TileLayer({ mercator: new Microsoft.Maps.TileSource({ uriConstructor: frame }) }); // Display the new frame on the map map.entities.push(tilelayer); }
The problem here is that, because the current frame is removed before the next frame is generated, there will be a gap and/or probably some fairly horrible flickering between frames.
You might be tempted to go to the other extreme and preload all of the frames into separate tilelayers before starting the animation. Suppose you had a 30 frame animation – you could create 30 different tile layers when the map first loads, with the tile source of each layer pointing to the tile images for each individual frames of the animation. Once all the layers had loaded, you could selectively show only one layer (visible: true) while hiding the others (by setting visible: false) to create a smooth animation between the frames. Something like this:
// Add tilelayers for each frame in the animation var frames = ['frame1.png', 'frame2.png', 'frame3.png', 'frame4.png']; for (var i = 0; i < frames.length; i++) { var tilelayer = new Microsoft.Maps.TileLayer({ mercator: new Microsoft.Maps.TileSource({ uriConstructor: frames[i] }), visible: false }); map.entities.push(tilelayer); } // Make chosen frame visible function DisplayFrame(frame) { for (var i = 0; i < map.entities.getLength(); i++) { if (i == frame) { map.entities.get(i).setOptions({ visible: true }); } else { map.entities.get(i).setOptions({ visible: false }); } } }
The problem with this second approach is that each new tile layer leads to many additional elements being placed in the DOM, many more image tiles being simultaneously downloaded, and having that many tile layers created at the same time would almost certainly choke the browser (as confirmed in a recent post on the MSDN forums, in which 10 simultaneous tile layers ceased a network).
The solution I went for was to try to create a “happy medium” somewhere in the middle of the previous two approaches – by creating a buffer of tile layers that queued up the next one or two frames of animation in advance, but didn’t preload all the frames. Assuming the animation speed was kept relatively constant, I should be able to animate the frames that had been queued more smoothly than simply dropping and recreating a single layer, yet without requiring the resources to preload every frame in a separate layer.
To create my queue, rather than simply create an array of tilelayers, I decided to use aMicrosoft.Maps.EntityCollection structure. The first tilelayer in the entity collection would be set to visible: true and represent the frame of animation currently shown on the map. Subsequent visible:false tilelayers would be added for the immediate upcoming frames and pushed onto the end of the collection. When I wanted to display the next frame, I’d remove the first element from the collection and shuffle any other frames down the queue, setting the visible:true property on the new lowest frame. So my queue structure looked a bit like this:
var frames = [ 'frame1.png' , 'frame2.png' , 'frame3.png' , 'frame4.png' ]; |
var currentFrame = 2; |
// Create the tile queue |
var tileQueue = new Microsoft.Maps.EntityCollection(); |
// Add the current tile as the first (visible) entity in the queue |
var currentTileLayer = new Microsoft.Maps.TileLayer({ |
mercator: new Microsoft.Maps.TileSource({ |
uriConstructor: frames[i] |
}), |
visible: true |
}); |
map.entities.push(currentTileLayer); |
// Queue up the next few tile layers |
for ( var i = 1; i < 3; i++) { |
var nextTileLayer = new Microsoft.Maps.TileLayer({ |
mercator: new Microsoft.Maps.TileSource({ |
uriConstructor: frames[currentFrame + i] |
}), |
visible: false |
}); |
map.entities.push(tilelayer); |
} |
Obviously, preloading subsequent frames onto the queue implies that the frames of animation will generally be being played in consistent, sequential order and I’m able to predict what the frame displayed in 2 or 3 frames time will be, in order to add it to the queue. If the user decided to jump to arbitrary points along the timeline, I wouldn’t have been able to queue the necessary frame in advance, and you’d get much the same behaviour from the queue as you would with a single tilelayer. Still, I decided that, for the most part, the queue would probably offer a benefit for the general use case of “playing” an animation.
I hoped that, by using the EntityCollection object rather than a generic array, I’d be able to make use of some of the Bing Maps events and methods to help with my animation handling, but more on that in a minute.
The javascript setInterval() method repeatedly calls a specified function at a chosen interval in time, specified in milliseconds. So to automatically animate through my queue of frames my first thought was to use setInterval(x) to schedule a function to execute every x milliseconds, which would remove the current tile layer from the queue (by tileQueue.removeAt(0); ), and immediately display the next element from the queue in its place. I’d also create and push a new tilelayer onto the end of the queue, to maintain a minimum number of ‘lookahead’ frames – ready-created tilelayers that were ready to display.
However, simply because the tilelayers pointing at the next frames of animation had been created and added onto the entitycollection, it doesn’t necessarily mean that the tile images for that frame had finished downloading. If you set a fast frame rate, it’s perfectly possible that the function called by setInterval would churn through the tilelayers in the queue faster than they were ready to display. Although you’d still get some benefit from the fact that the tiles had been requested slightly earlier than they were required to be displayed, it would be nice to only advance to the next frame if we knew it was fully ready to be displayed.
In order to find out if the next frame in the queue was ready, I thought I could make use of the tiledownloadcomplete event, as described at http://msdn.microsoft.com/en-us/library/gg427609.aspx. My idea was that, rather than simply wait for a predefined time to have elapsed before setInterval() would display the next frame, I would also monitor the tiledownloadcomplete event to know that the images for the next frame were completely ready. That would mean that animating between frames should be seamless, requiring nothing more than to swap the opacity of the current foreground tilelayer and its replacement. This sounded good on paper but, after considerable time spent wrangling this into a working solution, I encountered a number of practical problems with the implementation:
Reluctantly, I couldn’t find a way to make tiledownloadcomplete work for my purposes to reliably tell me that the next frame in my queue was ready, so I decided to go back to Plan A and make do with simply waiting a chosen amount of time before changing frames, in the hope that the time spent on the queue will have provided enough opportunity for the needed tile images to be fully downloaded.
The 2011 Tour de France has recently gotten underway, and Cycling the Alps has built some fun tools to help you see the conditions that the riders are up against.
We’ve shown you some of their work in the past, such as the great games that they added to the site earlier this year. Now they’ve combined that technology with the Tour de France and the result is quite cool.
They’ve gone through and created 3D tours, Streetview tours, profiles and games for every leg of the race. It’s quite an impressive list. Here are a few of the highlights to look for:
There are two stages in the Massif Central which are going to be very challenging, including the last two climbs in this stage: Col de la croix Saint Robert and Super-Besse Sancy.
In the Pyrenees and the climbs of this stage are epic. The Tourmalet is the most famous one but Luz-Ardiden gets a lot of attention in the media as well.
Stage 14 in the Pyrenees is probably the most difficult stage in the race, with six significant climbs.
This years tour is celebrating 100 years of high mountain stages, and every race featured a climb of the Col du Galibier. This year they’ll be climbing the pass two times; once from each side. In stage 18 they will even finish on the Galibier. This is the highest stage finish in the history of the Tour de France.
The next day they will climb the Col du Galibier from the other side, and all three climbs on the 19th stage are legendary.
There’s an amazing amount of info on this site about the Tour, and the games make it fun to ride around on each stage. Congrats to the CTA team for putting this all together.