-
Notifications
You must be signed in to change notification settings - Fork 2
Changing pixel ratio for improved clarity on hi resolution displays
This sample relies on tiles from a private tile source. If the server is not up, you will not get anything. You may also want to check out Nanomaps Server for a simple tile server that, among other things, knows how to deal with this.
For best results, view the above link on an iPhone with a retina display. Switch between 1X and 2X pixel ratios to see the difference. If you make the switch on a desktop browser, you will notice that the display is assembled from twice the number of half size tiles.
When Apple released the Retina display on the iPhone they created a lot of extra work for us. Now all of a sudden, graphics that looked perfectly fine before didn't just look like they did but looked a lot worse unless if mitigating steps were taken.
WebKit by default munges everything so that the physical size of a CSS pixel remains the same regardless of whether running on Retina or not. Under the covers, however, if a raster image actually has a physical size that matches the double pixel density of the Retina display, no scaling will be done and the image will display pixel for pixel on the display. Since maps have a lot of line work, text and anti-aliasing, the artifacts from scaling these images is particularly ugly, often making it look like a child drew the map with a crayon.
It is easy to munge the math to make tiles display pixel for pixel at native resolution, but the physical sizes of the text and the line work is typically not ideal. Also, doing this effectively biases the tiles by one zoom level if using a standard log2 zoom breakdown. For imagery drawn from vector sources (ie. street maps), this can be corrected with some help from the map server. For true raster imagery, it is probably best to just take it as-is and let the iPhone do what its going to do.
Nanomaps has four mechanisms for solving this problem:
- When creating a TileLayer, you can pass an explicit "pixelRatio" value. This is the ratio of tile pixels / css pixels. For a retina display, it would be 2. For "normal" displays it would be 1.
- Alternatively, you can pass the parameter "autoPixelRatio: true". If the browse reports a pixelRatio it will be used, otherwise 1.0 will be assumed.
- In the tile URL, you can use the replaceable parameter "${pixelRatio}" to pass the actual pixelRatio in use to the tile server in the hopes that it knows what to do with it.
- You can pass a "zoomBias" parameter to the map constructor. This value will be added to any values that set the zoom and subtracted from any values that return it. If you are clever, you can probably get your hi-res map to mostly stylistically represent its low-res cousins on a zoom-for-zoom basis.
This demo will illustrate the use of the first three mechanisms.
This sample is built on the Full Screen Map sample. The code is not duplicated on this page, but you can see it all in the source of the Try It Live link. The key parts are below.
var currentTileLayer, currentPixelRatio;
var tileSrc='http://nike.rcode.net:7666/map/mqstreet/${level}/${tileX}/${tileY}?pixelRatio=${pixelRatio}';
function switchPixelRatio(ratio) {
var a1x=document.getElementById('switch1X'),
a2x=document.getElementById('switch2X'),
aauto=document.getElementById('switchAuto'),
tileLayer;
if (ratio===currentPixelRatio) return;
a1x.className='';
a2x.className='';
aauto.className='';
if (ratio==='auto') {
tileLayer=new nanomaps.TileLayer({
tileSrc: tileSrc,
autoPixelRatio: true
});
aauto.className='selected';
} else if (ratio==1) {
tileLayer=new nanomaps.TileLayer({
tileSrc: tileSrc,
pixelRatio: 1.0
});
a1x.className='selected';
} else if (ratio==2) {
tileLayer=new nanomaps.TileLayer({
tileSrc: tileSrc,
pixelRatio: 2.0
});
a2x.className='selected';
}
if (currentTileLayer) {
map.detach(currentTileLayer);
}
currentTileLayer=tileLayer;
map.attach(tileLayer);
}
If you have a tile server that interprets the ${pixelRatio} properly, you can just use the following type of code when creating your TileLayer to have it do the "right thing".
tileLayer=new nanomaps.TileLayer({
tileSrc: tileSrc,
autoPixelRatio: true
});
The sample tile server is taking the pixelRatio URL parameter and passing it through to Mapnik's AGG Renderer as the scaleFactor. This multiplier is applied to all style symbolizers with the result being that text and line work is rendered at an increased pixel density resulting in roughly the same visual presentation as if you had scaled the image with a raster operation. However, since no raster scaling is done, anti-aliasing and fine line work is not disrupted, resulting in a clearer, sharper image on hi resolution displays.