City Label Tiles - Multiple LODs Break Text

1. A concise explanation of the problem you’re experiencing.

I am currently using a custom Mapbox Vector Tile implementation within Cesium. This implementation uses a MVT ImageryProvider with the Protobuf data being styled and then generated on a canvas using OpenLayers’ Canvas ReplayGroup.

As you know, Ceisum’s 3D viewer requires multiple imagery LODs to display a scene. The multiple LODs lead to label display issues, as shown below.

2. A minimal code example. If you’ve found a bug, this helps us reproduce and repair it.

Here is my custom MVT ImageryProvider code that uses OL to render city labels, boundaries and other features.

MapboxVectorTile.prototype.requestImage = function(x, y, level, request) {

var cacheTile = findTileInQueue(x, y, level, this._tileQueue);

if(!defined(cacheTile)) {

var that = this;

var url = this._url;

var index = (x + y + level) % this._subdomains.length;

url = url.replace(’{x}’, x).replace(’{y}’, y).replace(’{z}’, level).replace(’{k}’, this._key).replace(’{s}’, this._subdomains[index]);

return Resource.fetchArrayBuffer(url).then(function(arrayBuffer) {

var canvas = document.createElement(‘canvas’);

canvas.width = that._tileWidth;

canvas.height = that._tileHeight;

var vectorContext = canvas.getContext(‘2d’);

var features = that._mvtParser.readFeatures(arrayBuffer);

var styleFun = that._styleFun();

var extent = [0, 0, 4096, 4096];

var _replayGroup = new that._ol.render.canvas.ReplayGroup(0, extent, 8, true, 100);

for(var i = 0; i < features.length; i++){

var feature = features[i];

var styles = styleFun(features[i], that._resolutions[level]);

for(var j=0;j<styles.length;j++)

{

that.ol.renderer.vector.renderFeature(_replayGroup, feature, styles[j], 16);

}

}

_replayGroup.finish();

_replayGroup.replay(vectorContext, that._pixelRatio, that._transform, 0, {}, that._replays, true);

if(that._tileQueue.count > that._cacheSize){

trimTiles(that._tileQueue, that._cacheSize / 2);

}

canvas.xMvt = x;

canvas.yMvt = y;

canvas.zMvt = level;

that._tileQueue.markTileRendered(canvas);

_replayGroup = null;

return canvas;

});

}

return cacheTile;

};

``

Here’s the “styleFunction.”

styleFunction: function() {

var fill = new ol.style.Fill({color: ‘’});

var stroke = new ol.style.Stroke({color: ‘’, width: 1});

var polygon = new ol.style.Style({fill: fill});

var strokedPolygon = new ol.style.Style({fill: fill, stroke: stroke});

var line = new ol.style.Style({stroke: stroke});

var text = new ol.style.Style({text: new ol.style.Text({

text: ‘’,

fill: fill,

stroke: stroke

})});

var styles = ;

return function(feature, resolution) {

var length = 0;

var layer = feature.get(‘layer’);

if (layer != ‘transportation’ && layer != ‘transportation_name’) {

return ;

}

var cls = feature.get(‘class’);

var type = feature.get(‘type’);

var scalerank = feature.get(‘scalerank’);

var labelrank = feature.get(‘labelrank’);

var adminLevel = feature.get(‘admin_level’);

var maritime = feature.get(‘maritime’);

var disputed = feature.get(‘disputed’);

var maki = feature.get(‘maki’);

var geom = feature.getGeometry().getType();

var interstate_color = Colors.hexToRgb(that.app.preferences.interstate_color);

interstate_color[3] = that.app.preferences.interstate_opacity;

if (layer == ‘transportation’ && cls == ‘motorway_link’) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘service’) {

stroke.setColor(’#cfcdca’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ &&

(cls == ‘street’ || cls == ‘street_limited’)) {

stroke.setColor(’#cfcdca’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘main’ &&

resolution <= 1222.99245256282) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘motorway’) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘path’) {

stroke.setColor(’#cba’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘major_rail’) {

stroke.setColor(’#bbb’);

stroke.setWidth(2);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘motorway_link’) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && (cls == ‘street’ ||

cls == ‘street_limited’) && geom == ‘LineString’) {

stroke.setColor(’#cfcdca’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘main’ &&

resolution <= 1222.99245256282) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘motorway’ &&

resolution <= 4891.96981025128) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘path’) {

stroke.setColor(’#cba’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘major_rail’) {

stroke.setColor(’#bbb’);

stroke.setWidth(2);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘motorway_link’) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘motorway’) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘service’) {

stroke.setColor(’#cfcdca’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ &&

(cls == ‘street’ || cls == ‘street_limited’)) {

stroke.setColor(’#cfcdca’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘main’ &&

resolution <= 1222.99245256282) {

stroke.setColor(interstate_color);

stroke.setWidth(that.app.preferences.interstate_width);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘path’) {

stroke.setColor(’#cba’);

stroke.setWidth(1);

styles[length++] = line;

} else if (layer == ‘transportation’ && cls == ‘major_rail’) {

stroke.setColor(’#bbb’);

stroke.setWidth(2);

styles[length++] = line;

}

styles.length = length;

return styles;

};

},

``

3. Context. Why do you need to do this? We might know a better way to accomplish your goal.

I previously used a GeoJSON file for cities. This produced the most aesthetically-pleasing city labels because there wasn’t any jagged/cut-off text and it gave the added benefit of Earth-tangent city labels for roll != 0.0.

My Protobuf MVT output contains much more information and is tiled, reducing the bandwidth necessary to render multiple features (city labels, state boundaries, county boundaries, roads, etc. etc.)

I’m looking for ANY advice on how to improve this without returning to a GeoJSON city database. Is it possible to render the MVTs using a single zoom level instead of compositing multiple zoom levels, which****always leads to broken text?

4. The Cesium version you’re using, your operating system and browser.

Latest master/trunk.

Hi there,

So the issue here is that MVT are built for a view that is not really 3D. They expect a uniform zoom level (like in the picture on the left), but Cesium requests different zoom levels depending on the tile’s distance from the camera, canvas resolution, and other factors, resulting in multiple zoom level in one view (like the image on the right) which will result in loading the optimum quality tile.

However, since you are implementing the requestImagery function here, you can chose to render a different x, y, or zoom level depending on the functionality you want. For instance, you could configure one uniform zoomLevel property on the imagery provider that changes depending on the needs of you app, and always use that rather than the parameters passed to the function.

Thanks,

Gabby

Gabby,

Thanks so much for the feedback. I’ll see what I can conjure up in the requestImagery function.

Much appreciated.

Hello: I’ve also met the same problem. Please ask about resoulution. When the value is very small, it collapses

for(var i = 0; i < features.length; i++){

var feature = features[i];

var styles = styleFun(features[i], that._resolutions[level]);

for(var j=0;j<styles.length;j++)

{

that.ol.renderer.vector.renderFeature(_replayGroup, feature, styles[j], 16);

}

}

that._resolutions[level]

How is this value set?

Hello: I’ve also met the same problem. Please ask about resoulution. When the value is very small, it collapses

for(var i = 0; i < features.length; i++){

var feature = features[i];

var styles = styleFun(features[i], that._resolutions[level]);

for(var j=0;j<styles.length;j++)

{

that.ol.renderer.vector.renderFeature(_replayGroup, feature, styles[j], 16);

}

}

that._resolutions[level]

How is this value set? thanks your reply