How to trigger a Tile Server to dynamically update tiles

4,216 views
Skip to first unread message

David

unread,
Oct 17, 2011, 12:32:06 PM10/17/11
to Google Maps JavaScript API v3
Hello,

I have a database with thousands of (latitude,longitude) points stored
in it. Those points are plotted into a map using a tile server. Such
tile server first computes, for each tile, the points that fall inside
the tile, and then draws every point(after coordinates conversion). My
Javascript looks as follows:

var overlayMaps = [{
getTileUrl: function(coord, zoom) {
return "tileserver.php?x="+coord.x+"&y="+coord.y+"&z="+zoom
+"&g=1";
},

tileSize: new google.maps.Size(256, 256),
isPng: true,
opacity: 1.0
}];

var overlayMap = new google.maps.ImageMapType(overlayMaps[0]);
map.overlayMapTypes.setAt(0,overlayMap);


Now, I am dynamically adding new records to the database every 5
seconds, and I want the respective tiles to be updated (re-computed
and re-displayed) every 5 seconds.

How could I trigger my tile server to re-compute all tiles (or at
least the ones in the viewport) every 5 seconds ?

Thanks in advance...

Rossko

unread,
Oct 17, 2011, 8:49:14 PM10/17/11
to Google Maps JavaScript API v3
> How could I trigger my tile server to re-compute all tiles (or at
> least the ones in the viewport) every 5 seconds ?

That doesn't seem to be a question about the maps API, but about
whatever you are using for a tile server. One approach might be just
to have it calculate new tiles for every request it gets. That could
be streamlined a bit by only creating anew if the cached version is
more than 5 seconds old.

Your real problem is that the API won't re-fetch custom map tiles
every five seconds, I think you will need to code your own tile
management for that. Take steps to circumvent browser caching.

David

unread,
Oct 17, 2011, 11:18:28 PM10/17/11
to Google Maps JavaScript API v3
Rossko, thank you very much for your reply.

Yes, I am actually getting my tiles updated by the tile server every
5s,
but as you said the problem is re-fetching map tiles that have already
been fetched.

I have no idea on how to make a custom tile management in JavaScript,
could you please show me any reference / sample that I could use a
starting point?

Thanks...

Martin™

unread,
Oct 18, 2011, 2:49:39 AM10/18/11
to Google Maps JavaScript API v3
Hi.

Have you tried simply removing your overlay map and then re-adding it?

map.overlayMapTypes.removeAt(0);
map.overlayMapTypes.setAt(0,overlayMap);

You'll still have to watch for problems with the browser caching
tiles, but if you add a bogus parameter to each tile URL then it
should force a reload of all visible tiles.

A bogus parameter based on the current time is a good idea:

var overlayMaps = [{
getTileUrl: function(coord, zoom) {
return "tileserver.php?x="+coord.x+"&y="+coord.y+"&z="+zoom
+"&g=1&bogus="+new Date().getTime();
},

tileSize: new google.maps.Size(256, 256),
isPng: true,
opacity: 1.0

}];


Martin.

David

unread,
Oct 18, 2011, 3:56:14 AM10/18/11
to Google Maps JavaScript API v3
Martin, thanks for your reply!

I see, using the date as a bogus parameter works very well !
However, the problem is that there is a bit of flashing when removing
and adding the tiles. I guess it is not possible to completely avoid
that, right ?

Martin™

unread,
Oct 18, 2011, 5:32:21 AM10/18/11
to Google Maps JavaScript API v3
Hi.

I nearly mentioned in my previous post that you might get a flicker as
you remove and re-add the tiles.
That's unavoidable with the code i posted i'm pretty sure of that.

To get the tiles to refresh without that flicker would require a
different approach - nowhere near as straightforward as the code i
posted.

I think you'd have to save a reference to each requested tile in your
getTile() method.
Save a reference so that you can change each tile image source at any
time.

Now to refresh your tile layer you'd iterate through all the tile
references and change the image source - with a bogus parameter this
would work.
The only problem would be knowing which tiles are still being
displayed by the map so that you don't refresh each and every tile
that has been created since the tiles were last refreshed.

Maybe you can implement a releaseTile() method in your overlay map?

http://code.google.com/apis/maps/documentation/javascript/reference.html#MapType

So getTile() would function as it does now - it would also save a
reference to each tile requested.

Your new releaseTile() method would remove references to tiles that
are no longer loaded/visible.

And every time you want to refresh the tiles, you'd get the references
to loaded/visible tiles and update each one's image source.

Martin.

Michael Geary

unread,
Oct 18, 2011, 2:05:05 PM10/18/11
to google-map...@googlegroups.com
I have some code that does a lot of what you mentioned - it could probably be adapted or provide some examples for the OP.

The code is the TileMapType in this file:

http://code.google.com/p/claslite/source/browse/web/app/static/js/scriptino-map.js

(currently lines 369-414 in the file, but of course that may change)

In particular, it implements a setOpacity function that loops through the tiles and changes their opacity - very similar to your idea of switching the tile URLs. And of course to do this it has to keep track of which tiles are active as you mentioned.

It also implements a loading spinner for the tiles (this is part of an app with very slow-loading tiles), but you can ignore the opt.spinner stuff.

OTOH, do pay attention to the img.onload code, because this is what will allow you to prevent flicker. If you merely set a new src URL on an image element, it will probably blank out the original image before loading the new one. Instead, this code creates a new Image object (without displaying it) and sets its src URL. Then when the onload event fires, we know the image is cached and can update the tile. (I happen to use 'tile' elements - essentially DIVs - and background images to make it easy to do the spinner, but the same trick would work if you use IMG tags - set their src after the onload fires.)

On another note, the idea of updating tiles every five seconds seems a bit extreme. If it's just a bunch of markers on the map, I would wonder if it might be better to generate JSON data that represents the currently visible markers. Don't know without seeing more of the app, so I'm just speculating here.

-Mike

--
You received this message because you are subscribed to the Google Groups "Google Maps JavaScript API v3" group.
To post to this group, send email to google-map...@googlegroups.com.
To unsubscribe from this group, send email to google-maps-js-a...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/google-maps-js-api-v3?hl=en.


David

unread,
Oct 19, 2011, 12:57:32 AM10/19/11
to google-map...@googlegroups.com
Michael and Martin:

Thank you very much for your replies!
I will try it as soon as I have time, I am quite busy with other stuff at the moment...

@Michael:  I get ~3000 points (lat,lng) stored into my db every hour (around 1~2 points per second). In order to get a "real-time" feel I have chosen to refresh the currently visible tiles every 5 seconds. In every refresh, the data accumulated during a 30 min. window (points added between 30 minutes before and "now") is displayed... I also wonder what is the best way to do it, but it works more or less right now...

Thanks again!


Message has been deleted

David

unread,
Oct 19, 2011, 10:25:30 PM10/19/11
to google-map...@googlegroups.com
Hi,

I have tried the opacity code suggested by Michael. Now the tiles get displayed and get reloaded, but the flickering is still happening.. There must be some problem with the code, could someone please help me find the errors? (I am sorry for any huge mistake, I have just started learning gmaps)

What I exactly do is:

1) javascript for maps part:

                                var tiles = {};

   function CoordMapType1(tileSize) {
  this.tileSize = tileSize;
}

function getURI(coord,zoom){
return "tile.php?x="+coord.x+"&y="+coord.y+"&z="+zoom+"&t="+(new Date().getTime());

}

CoordMapType1.prototype.getTile = function(coord, zoom, ownerDocument) {
   var opacity = this.opacity == null ? 1 : this.opacity;
                                           var url = getURI( coord, zoom );
                                           var img = new Image;
                                           img.src = url;
                                           img.onload = function() {
                                                        tile.style.backgroundImage = 'url(' + url + ')';
                                                        tile.style.opacity = opacity;
                                                        img.onload = null;
                                                        img = null;
                                                };
              var id = url.replace( /\W+/g, '-' );
              var tile = ownerDocument.createElement('div');
              tile.id = id;
              tile.style.width = this.tileSize.width + 'px';
              tile.style.height = this.tileSize.height + 'px';
              tile.style.backgroundRepeat = 'no-repeat';
              tile.style.backgroundPosition = 'center center';
              tiles[id] = tile;
    return tile;
};

  CoordMapType1.prototype.releaseTile = function(tile ) {
                                             delete tiles[ tile.id ];
                                };

CoordMapType1.prototype.setOpacity = function( _opacity ) {
                        opacity = _opacity;
                        for( var id in tiles ) {
                              var tile = tiles[id];
                               tile.style.opacity = opacity;
                        }
                                 };

                                 map.overlayMapTypes.insertAt(0, new CoordMapType1(new google.maps.Size(256, 256)));

2) Javscript for reloading every 5 seconds

                               var intervalID = setInterval(function(){ 
map.overlayMapTypes.removeAt(0);
map.overlayMapTypes.insertAt(0, new CoordMapType1(new google.maps.Size(256, 256)));
 
}, 5000);

Thanks in advance and sorry for so much trouble...

Martin™

unread,
Oct 20, 2011, 12:45:23 AM10/20/11
to Google Maps JavaScript API v3
Hi again.

I think you need to abandon the old method - removing the overlay map
type and re-adding it to the map.
You will always get a flicker with that method i believe.

Try this for a basic overlay map:

[code]
function MyOverlayMap(tileSize) {
this.loadedTiles = {};
this.tileSize = tileSize;
}
MyOverlayMap.prototype.getTile = function (coord, zoom, ownerDocument)
{
function getURI(coord, zoom) {
return 'tile.php?x=' + coord.x + '&y=' + coord.y + '&z=' + zoom;
}
var tileUrl = getURI(coord, zoom);
var tileId = 'x_' + coord.x + '_y_' + coord.y + '_zoom_' + zoom;
var tile = ownerDocument.createElement('div');
tile.style.backgroundPosition = 'center center';
tile.style.backgroundRepeat = 'no-repeat';
tile.style.height = this.tileSize.height + 'px';
tile.style.width = this.tileSize.width + 'px';
tile.tileId = tileId; // do not use 'id' as new custom property as
it's a native property of all HTML elements
tile.tileURL = tileUrl;
this.tiles[tileId] = tile;
tileUrl += '&timestamp=' + new Date().getTime();
var img = new Image();
img.onload = function () {
tile.style.backgroundImage = 'url(' + tileUrl + ')';
img.onload = null;
img = null;
};
img.src = tileUrl;
return tile;
};
MyOverlayMap.prototype.refreshTiles = function () {
for (var tile in this.tiles) {
var tileUrl = tile.tileUrl + '&timestamp=' + new Date().getTime();
var img = new Image();
img.onload = function () {
tile.style.backgroundImage = 'url(' + tileUrl + ')';
img.onload = null;
img = null;
};
img.src = tileUrl;
}
};
MyOverlayMap.prototype.releaseTile = function (tile) {
delete this.tiles[tile.tileId];
tile = null;
};
[/code]

You can get a better formatted copy of the code here:
http://code.martinpearman.co.uk/deleteme/MyOverlayMap.js

I've removed all opacity code and left just the basic stuff.

So you create an instance of the overlay map type, add it to your
map's overlayMapTypes and refresh it every 5 seconds:

[code]
var my myOverlayMap=new MyOverlayMap(new google.maps.Size(256, 256));

myMap.overlayMapTypes.insertAt(0, myOverlayMap);

setInterval(function(){
myOverlayMap.refreshTiles();
};, 5000);
[/code]

I'd be keen to see it working if you get that far?

Martin.

Martin™

unread,
Oct 20, 2011, 12:54:02 AM10/20/11
to Google Maps JavaScript API v3
Oooops!

Spotted a few errors in that code after i posted it.

I've uploaded a corrected version to the same URL as before:

http://code.martinpearman.co.uk/deleteme/MyOverlayMap.js

And a code dump:

[code]
function MyOverlayMap(tileSize) {
this.loadedTiles = {};
this.tileSize = tileSize;
}
MyOverlayMap.prototype.getTile = function (coord, zoom, ownerDocument)
{
function getURI(coord, zoom) {
return 'tile.php?x=' + coord.x + '&y=' + coord.y + '&z=' + zoom;
}
var tileUrl = getURI(coord, zoom);
var tileId = 'x_' + coord.x + '_y_' + coord.y + '_zoom_' + zoom;
var tile = ownerDocument.createElement('div');
tile.style.backgroundPosition = 'center center';
tile.style.backgroundRepeat = 'no-repeat';
tile.style.height = this.tileSize.height + 'px';
tile.style.width = this.tileSize.width + 'px';
tile.tileId = tileId; // do not use 'id' as new custom property as
it's a native property of all HTML elements
tile.tileURL = tileUrl;
this.loadedTiles[tileId] = tile;
tileUrl += '&timestamp=' + new Date().getTime();
var img = new Image();
img.onload = function () {
tile.style.backgroundImage = 'url(' + tileUrl + ')';
img.onload = null;
img = null;
};
img.src = tileUrl;
return tile;
};
MyOverlayMap.prototype.refreshTiles = function () {
for (var tileId in this.loadedTiles) {
var tile = this.loadedTiles[tileId];
var tileUrl = tile.tileUrl + '&timestamp=' + new Date().getTime();
var img = new Image();
img.onload = function () {
tile.style.backgroundImage = 'url(' + tileUrl + ')';
img.onload = null;
img = null;
};
img.src = tileUrl;
}
};
MyOverlayMap.prototype.releaseTile = function (tile) {
delete this.loadedTiles[tile.tileId];
tile = null;
};
[/code]

Martin.

David

unread,
Oct 20, 2011, 6:51:54 AM10/20/11
to google-map...@googlegroups.com
Martin:

Thank you very much for your time and patience.

Currently your code loads the tiles initially, but still doesn't seem to refresh the tiles after the 5 seconds...

(By the way, I changed:
var tileUrl = tile.tileUrl 
to
var tileUrl = tile.tileURL )

I wonder why is not working, it looks ok to me, and I think I checked with firebug all variables... 

Martin™

unread,
Oct 20, 2011, 7:40:42 AM10/20/11
to Google Maps JavaScript API v3
David.

You'll have to let me have a look at your map code i'm sure to enable
me to help debugging it.
Email me a link if you can and you want to keep the URL out of this
thread.

Otherwise i'd suggest Firefox and Firebug.

Choose the Net tab then the Images sub tab.
Look for any requests being made for new images.
If you see requests look at the timestamp - are new tiles being
requested with a new or the original timestamp?

If new tiles are being requested but nothing updates on the map then
look at your tiles.php script.
Is it setting any cache directive headers?
Are new tile requests being mad ebut even with the timestamp the
browser is using cached tiles for new requests?

Back in Firebug click the 'inspect an element' button top left corner.
Click on one of your tiles and and look for it CSS background-image
property in the Style window to the right.
Can you see old or new tile URL value - can you copy the URL to a new
browser window and open that tile in a new window?
If so then do you see the same tile in the new window as is displayed
on the map or is the map displaying an old tile?

On Firebug select the Script tab and then MyOverlayMap.js from the
list of javascript resources.
Scroll to the refreshTiles() method and put a breakpoint on the line:

var tile = this.loadedTiles[tileId];

(Left click the line number to set the breakpoint).

Now wait for the timeout to execute - does the script pause on that
line or is the refreshTiles() method not even being executed?

Martin.

David

unread,
Oct 20, 2011, 8:13:16 AM10/20/11
to google-map...@googlegroups.com
Martin:

Thanks again for your reply!

1) I will send you the code by mail so that you can take a look, after some minutes.

2) New files are being requested with new timestamps...

3) In my server I am using as header directives:
header('content-type:image/png;');
header("Cache-Control: no-cache, must-revalidate");
Either having them or not does not seem to make any difference...

4) inspection of a tile:
I cannot find any background image property... Strange?
<div style="width: 256px; height: 256px; overflow: hidden; position: absolute; left: 529px; top: 277px;"></div>

5) Refresh method is being called...

Michael Geary

unread,
Oct 20, 2011, 12:52:26 PM10/20/11
to google-map...@googlegroups.com
That's right. I'm afraid I didn't make my original suggestion clear enough: the code I linked to wasn't a way to make the remove-add sequence work better, it was pointing to a way to avoid the remove and add entirely, by adding a refresh method as you've done in your code here.

Also, David, I noticed one unrelated problem in the most recent code you posted. It looked like you took my code and converted it to define its methods using the constructor prototype instead of nesting the methods inside the constructor. That's fine - both approaches are reasonable. But when you converted the code to use the prototype, you made the 'tiles' variable a global. Not good - if you ever had more than one instance of this object it would break. Instead, if you prefer the prototype approach, the tiles variable should be something like 'this.tiles', as shown in Martin's version.

-Mike

--
You received this message because you are subscribed to the Google Groups "Google Maps JavaScript API v3" group.

Martin™

unread,
Oct 20, 2011, 3:02:55 PM10/20/11
to Google Maps JavaScript API v3
Just to keep this thread updated we have got my code working.

The MyOverlayMap code i posted before this thread did not properly
refresh the tile layer.
It didn't get closure on the image onload event listener in the
tileRefresh() method.

Here's the working code:

[code]
function MyOverlayMap(tileSize) {
this.loadedTiles = {};
this.tileSize = tileSize;
}
MyOverlayMap.prototype.getTile = function (coord, zoom, ownerDocument)
{
function getURI(coord, zoom) {
return 'tile.php?x=' + coord.x + '&y=' + coord.y + '&z=' + zoom;
}
var tileUrl = getURI(coord, zoom);
var tileId = 'x_' + coord.x + '_y_' + coord.y + '_zoom_' + zoom;
var tile = ownerDocument.createElement('div');
tile.style.backgroundPosition = 'center center';
tile.style.backgroundRepeat = 'no-repeat';
tile.style.height = this.tileSize.height + 'px';
tile.style.width = this.tileSize.width + 'px';
tile.tileId = tileId; // do not use 'id' as new custom property as
it's a native property of all HTML elements
tile.tileURL = tileUrl;
this.loadedTiles[tileId] = tile;
tileUrl += '&timestamp=' + new Date().getTime();
var img = new Image();
img.onload = function () {
tile.style.backgroundImage = 'url(' + tileUrl + ')';
img.onload = null;
img = null;
};
img.src = tileUrl;
return tile;
};
MyOverlayMap.prototype.refreshTiles = function () {
function onloadCallback(tile2, tileUrl2){
return function(){
tile2.style.backgroundImage = 'url(' + tileUrl2 + ')';
};
}
for (var tileId in this.loadedTiles) {
var tile = this.loadedTiles[tileId];
var tileUrl = tile.tileUrl + '&timestamp=' + new Date().getTime();
var img = new Image();
img.onload = onloadCallback(tile, tileUrl);
img.src = tileUrl;
}
};
MyOverlayMap.prototype.releaseTile = function (tile) {
delete this.loadedTiles[tile.tileId];
tile = null;
};
[/code]

Now a call to refreshTiles() successfully and SMOOTHLY updates the
tiles.

Martin.

Kesuke

unread,
Oct 20, 2011, 7:46:10 PM10/20/11
to Google Maps JavaScript API v3
How does it perform in the end? I'd love to see a link as am thinking
of implementing something similar to smooth out transitions in my
custom overlays.

I was wondering whether the need for 5 second updates couldn't be
changed to perhaps 10 or even 15+? At 5 seconds isn't there a danger
than tile refreshes will clip on each other if there is a lag spike or
the internet connection is generally slow? I guess you could always
include a toggle somewhere to disable live updates if necessasry.

Also, have you specified a no-cache or no-store HTTP header in your
tileserver.php?
> ...
>
> read more »

David

unread,
Oct 21, 2011, 1:55:41 AM10/21/11
to google-map...@googlegroups.com
Thanks Michael, I see...

David

unread,
Oct 21, 2011, 2:01:06 AM10/21/11
to google-map...@googlegroups.com
Thanks again Martin, your code works flawlessly.

kesuke:
I am refreshing the tiles every 5~10 seconds and I have no problems at all...
'Cache-Control: no-cache, must-revalidate' is specified in the server php file.

Thanks again to everyone!

Martin™

unread,
Oct 21, 2011, 2:39:33 AM10/21/11
to Google Maps JavaScript API v3
Hi Kesuke.

Take a look at this:

http://code.martinpearman.co.uk/deleteme/my_overlay_map/index.html

That's a working example that updates every 10 seconds.

The get_tile.php script simply creates a blank PNG tile and draws the
timestamp from the tile request onto the tile:

<?php
header('Content-type: image/png');
header('Cache-Control: no-cache, must-revalidate');

$tile=imagecreatefromstring(base64_decode('iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAB3RJTUUH2gceBB0d5vTBTAAAAAlwSFlzAAALEgAACxIB0t1+/
AAAAARnQU1BAACxjwv8YQUAAAEVSURBVHja7cExAQAAAMKg9U/
tawigAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4AwE8AAHYKUMEAAAAAElFTkSuQmCC'));
imagesavealpha($tile, true);
$textcolor = imagecolorallocate($tile, 0, 0, 255);
imagestring($tile, 4, 10, 10, $_GET['timestamp'], $textcolor);

imagepng($tile);
?>

Martin.
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages