QGIS2 web – fixing labelling issues in OpenLayers map
In the last article about the OpenLayers output from the QGIS2Web plugin, I would like to give up the time for labelling issues, which I believe is another important element of our map, without which it can’t be displayed properly. Sometimes labelling is of the same importance as key, when we want to see where some unique elements of our infrastructure are located and so forth. Here I would like to show you how to fix easily any labelling mismatches, occurring just after our map is generated by the QGIS2web plugin.
We can basically encounter 3 major labelling errors when opening our OpenLayers map for the first time.
The first one is:
– Label is shown as “undefined”
– Label not visible at all
– Label partially visible only
1. LABEL SHOWN AS UNDEFINED
This is a quite common situation when we are opening our map for the first time and see the situation below (Pic. 1).
The issue is predominantly generated by the limit of the field name in our .shp files in QGIS we are working on. In turn, instead of the full name, which we define every time when launching our QGIS project, the label name is cut down to 10 characters only at the stage of exporting it to the interactive map by QGIS2web. Despite this inconvenience, we can fix it shortly. You must point out the layer, that causes this problem, then open it from your /styles/ folder. If you do so, a whole code making the stylization of your layer will be visible. The stylization covers the labelling issues also. Just in case, you can open the javascript layer file from the /layers/ folder, which includes the parsed .geojson file, which was widely discussed in this tutorial. Below you can see an example of the code from my layer, which is seen as “undefined” in my map.
if (feature.get("Building N") !== null) { labelText = String(feature.get("Building N")); }
The code above comes from the layer style. For the comparison and finding the culprit at once, we should take a look at the code below, which includes our parsed geoJSON data from the layer concerned.
var json_A5K_PropertyInformation2_35 = {
"type": "FeatureCollection",
"name": "A5K_PropertyInformation2_35",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "S. No.": "1", "UPRN": "1xxxx", "X": "511111", "Y": "211111", "Building Name": "9", "Street\/Road": "High Street", "Town": "Dxxxx", "Postcode": "XXXX", "Building Type": "SDU", "Cabinet Name": "Dxxxx", "Splitter Name": "x1:16)", "DP Number": "23 DP - 12F DP", "DP Info": "on P1" }, "geometry": { "type": "Point", "coordinates": [ -0.11111111111111, 52.1111111111111 ] } },
...
The error has been marked red and emboldened. The “undefined” text appearance is caused by the discrepancy between the label text name, which should fetch the data from the file, where our layer information is stored (the code below). In order to see off this situation, we just need to change the name of our feature in the layer style file (upper code), as per below.
if (feature.get("Building_Number") !== null) { labelText = String(feature.get("Building_Number")); }
Providing the name of our feature, which matches the name defined in our source layer code will result in a proper information appearance in our map (Pic. 2).
2. LABEL NOT VISIBLE OR PARTIALLY VISIBLE
This problem usually applies to all other layers, which appear physically in the QGIS map. Unlike, the “undefined” objects discussed previously, which in our QGIS project are label-based only, here we are dealing with any other objects being a subject of categorization. Regardless of the difference, our code will look exactly the same. Below I have the image, which displays 4 different groups of objects, which were my point of interest. Three of them are not labelled at all. Only one group of items has the description provided next to it (Pic. 3,4). Moreover, we are sure, that they are visible in our QGIS project.
To fix this issue, we must be aware of precisely the same process as described above. Firstly I will show you this part of the code, which lets our label come out:
if ("" !== null) { labelText = String(""); }
as we can see, there is nothing, the code could fetch from. We should go to the main layer file and find, which feature (column header in our QGIS data attribute table) should be displayed next to our item in the map. It’s good to confront it with our layer’s data attribute table in QGIS at once (Pic. 5).
The column header defined as the information about our labels assigned to every single object is called “Pole No.“. Now, we should copy this name into our code shown above. Just in case, we can go, as hinted above, to the main layer file and see if the same name exists or not.
var json_Area5A_Pole_10 = {
"type": "FeatureCollection",
"name": "Area5A_Pole_10",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "Pole No.": "P0", "X": "588888", "Y": "299999", "Address": "Opp 7a, High St", "Pole Type": "xx", "Wayleave": "N", "No. of exi": "7", "No. of dro": "6", "Type of cr": "Radial", "Hedge Cutt": "Yes", "Sufficent ": "NA", "Enclosure": "No", "Comments": "NA", "Splicing Info": "No splicing required ", "Properties to be served from this Enclosure": "NA" }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ ...
The column name should be rendered accurately in the main layer file. It is, as seen marked red. Now, we must copy this name into our style code snippet:
if ("Pole No." !== null) { labelText = String("Pole No."); }
however, we won’t see the result, as there is still missing stuff here. We need the feature.get property in our snippet also. Our final code should look like this:
if (feature.get("Pole No.") !== null) { labelText = String(feature.get("Pole No.")); }
bringing finally the label next to our element in the OpenLayers map.
A similar exercise should be done with other layers, in which the labels are missing. In the other case, instead of:
if ("" !== null) { labelText = String(""); }
it should be:
if (feature.get("Chamber No") !== null) { labelText = String(feature.get("Chamber No")); }
giving us the labels (Pic. 6).
However, some elements are still irritating here. The labels are too small to be easily readable, although I will fix them later. The worst thing is the lack of a label next to the red rectangle. The only reason, why it happens is our map scale. The QGIS2web plugin defines our map zoom range between 1 and 28 initially. If you don’t change it, the problem already described won’t apply to you. I set my scale range between 7 and 22, so some of the label descriptions were not visible, as the string capacity is scale-dependent. It means, that at a certain map scale, the label string won’t be displayed when exceeds an exact number of symbols. Alternatively, you can reduce it and make it visible. It can be done by using the substring property.
if (feature.get("Chamber No") !== null) {
labelText = String(feature.get("Chamber No").substring(0,3));
}
In turn, we should see the appropriate part of our string, limited to the number of characters defined in the substring brackets (Pic. 7).
If you wish to have the label with full string defined yet in QGIS, I would advise you to open the qgis2web.js file and at the 94th line of code find the code defining our main map canvas, where you are able to set the maxZoom and minZoom properties.
var map = new ol.Map({
controls: ol.control.defaults({attribution:false}).extend([
expandedAttribution,new measureControl(),new geolocateControl()
]),
target: document.getElementById('map'),
renderer: 'canvas',
overlays: [overlayPopup],
layers: layersList,
view: new ol.View({
maxZoom: 28, minZoom: 14
})
});
3. CUSTOMIZING OUR LABEL
The last part of my tutorial shows the example ways of label corrections. In a practical sense, the smallish and black numbers are not the best for reading. We need to make them smarter at some point, which this section is about. I can admit, that everything is going to be played around the font predominantly because our label is expressed by text string mostly.
Let’s have a look at our code and see what can we do.
function categories_Area5B_Chamber_9(feature, value, size, resolution, labelText,
labelFont, labelFill, bufferColor, bufferWidth,
placement) {
switch(value.toString()) {case 'XT':
return [ new ol.style.Style({
stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0}),fill: new ol.style.Fill({color: 'rgba(0,0,0,1.0)'}),
text: createTextStyle(feature, resolution, labelText, labelFont,
labelFill, placement, bufferColor,
bufferWidth)
})];
break;
case 'XXX':
return [ new ol.style.Style({
stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0}),fill: new ol.style.Fill({color: 'rgba(227,26,28,1.0)'}),
text: createTextStyle(feature, resolution, labelText, labelFont,
labelFill, placement, bufferColor,
bufferWidth)
})];
break;}};
var style_Area5B_Chamber_9 = function(feature, resolution){
var context = {
feature: feature,
variables: {}
};
var value = feature.get("Chamber Ty");
var labelText = "";
size = 0;
var labelFont = "10.4px \'MS Shell Dlg 2\', sans-serif";
var labelFill = "#000000";
var bufferColor = "";
var bufferWidth = 0;
var textAlign = "left";
var offsetX = 8;
var offsetY = 3;
var placement = 'point';
if (feature.get("Chamber No") !== null) {
labelText = String(feature.get("Chamber No"));
}
var style = categories_Area5chamber_14(feature, value, size, resolution, labelText,
labelFont, labelFill, bufferColor,
bufferWidth, placement);
return style;
};
First of all, we should notice the feature.get property in this code snippet. It’s often mistaken with the current situation in QGIS. It has been told, that the shapefiles have 10 characters limit. In this event, every time we open our QGIS project we should right-click on our layer from the sidebar and choose the “Properties” and next “Fields” (Pic. 8).
Every time if we enter the QGIS project we need to change our field names again and again. Once the tool is closed, the names are automatically reduced to 10 characters again. If you are planning to omit this limit, you should generate the interactive map just afterwards. If you do it later, your changes will be lost. This situation often causes mismatches between the label name cut by QGIS and the label name appearing in our code. In the case of these discrepancies, our labels will simply not work. It’s a quote-offtopic issue by now, but it’s good to know about it when considering the labelling of our final map.
The full code includes all the variables set throughout any cases defined. Because we have both black and red rectangles we have 2 separate cases effectively. Each case must include the defined variables. The emboldened variables apply to our labelText. We have there labelFont variable, which defines our font size and type and labelFill, which corresponds to the colour of our text, described by the HEX code. We can play mostly around these 2 because they are the most important in our situation.
If we make some changes to our code regarding the text:
var style_Area5B_Chamber_9 = function(feature, resolution){ var context = { feature: feature, variables: {} }; var value = feature.get("Chamber Ty"); var labelText = ""; size = 6; var labelFont = "18px \'MS Shell Dlg 2\', sans-serif"; var labelFill = "#999999"; var bufferColor = ""; var bufferWidth = 0; var textAlign = "left"; var offsetX = 8; var offsetY = 3; var placement = 'point'; if (feature.get("Chamber No") !== null) { labelText = String(feature.get("Chamber No")); }
then we can expect some nice results (Pic. 9).
Sometimes, the text variable might not be working properly. In this case, we can implement an alternative solution, by defining the font attributes in our labelText variable, as follows:
if (feature.get("Chamber No") !== null) {
labelText = String(feature.get("Chamber No")); labelFont = "18px \'MS Shell Dlg 2\', sans-serif";
}
Another thing, we would need to change is our font buffer. It’s also a straightforward thing, which is to change the code slightly as shown below:
var style_Area5B_Chamber_9 = function(feature, resolution){ var context = { feature: feature, variables: {} }; var value = feature.get("Chamber Ty"); var labelText = ""; size = "2"; var labelFont = "18px \'MS Shell Dlg 2\', sans-serif"; var labelFill = "#999999"; var bufferColor = "#888888"; var bufferWidth = 2; var textAlign = "left"; var offsetX = 0; var offsetY = 0; var placement = 'point'; if (feature.get("Chamber No") !== null) { labelText = String(feature.get("Chamber No")); } var style = categories_Area5B_Chamber_9(feature, value, size, resolution, labelText, labelFont, labelFill, bufferColor, bufferWidth, placement); return style; };
with a nice result (Pic. 10).
Our text can be in the vertical, instead of horizontal orientation. We just need to change the placement property from ‘point’ to ‘line‘:
var style_Area5B_Chamber_9 = function(feature, resolution){
var context = {
feature: feature,
variables: {}
};
var value = feature.get("Chamber Ty");
var labelText = "";
size = "2";
var labelFont = "18px \'MS Shell Dlg 2\', sans-serif";
var labelFill = "#999999";
var bufferColor = "#888888";
var bufferWidth = 2;
var textAlign = "left";
var offsetX = 0;
var offsetY = 0;
var placement = 'line';
if (feature.get("Chamber No") !== null) {
labelText = String(feature.get("Chamber No"));
}
and the result is below (Pic. 11).
The two last things to change are the font position and offsets. The font position (at least in my point of view) means only a little when we can change its general position against our object. The thing is not difficult if we have the layer with one category only. Then our changes are confined to the code based on our layer style file.
var size = 0; var placement = 'point'; var style_Area5C_NewPole_13 = function(feature, resolution){ var context = { feature: feature, variables: {} }; var value = "" var labelText = ""; size = 0; var labelFont = "17.0px \'MS Shell Dlg 2\', sans-serif"; var labelFill = "#e62323"; var bufferColor = "#e62323"; var bufferWidth = 2; var textAlign = "left"; var offsetX = "8"; var offsetY = "3"; var placement = 'point'; if (feature.get("Pole No.") !== null) { labelText = String(feature.get("Pole No.")); } var style = [ new ol.style.Style({ stroke: new ol.style.Stroke({color: 'rgba(230,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 1}),fill: new ol.style.Fill({color: 'rgba(255,255,255,1.0)'}), text: createTextStyle(feature, resolution, labelText, labelFont, labelFill, placement, offsetX, offsetY, bufferColor, bufferWidth) })]; return style; };
Before we change the offsets variable, we should include them in our createTextStyle function, as shown in the code. This is the only way, in which we can make them work properly (Pic. 12).
For categorized layers, the situation might be a bit complicated. We have here the dependency of our layer style file from another file. As a result, some variables won’t be changeable unlike in previous examples provided. We should here deal with a few issues, which have been marked in the code below:
var size = 0; var placement = 'point'; function categories_Area5B_Chamber_9(feature, value, size, resolution, labelText, labelFont, labelFill, bufferColor, bufferWidth, textAlign, offsetX, offsetY, placement) { switch(value.toString()) {case 'X': return [ new ol.style.Style({ stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0}),fill: new ol.style.Fill({color: 'rgba(0,0,0,1.0)'}), text: createTextStyle(feature, resolution, labelText, labelFont, labelFill, placement, bufferColor, bufferWidth, textAlign, offsetX, offsetY) })]; break; case 'Y': return [ new ol.style.Style({ stroke: new ol.style.Stroke({color: 'rgba(35,35,35,1.0)', lineDash: null, lineCap: 'butt', lineJoin: 'miter', width: 0}),fill: new ol.style.Fill({color: 'rgba(227,26,28,1.0)'}), text: createTextStyle(feature, resolution, labelText, labelFont, labelFill, placement, bufferColor, bufferWidth, textAlign, offsetX, offsetY) })]; break;}}; var style_Area5B_Chamber_9 = function(feature, resolution){ var context = { feature: feature, variables: {} }; var value = feature.get("Chamber Ty"); var labelText = ""; size = "2"; var labelFont = "18px \'MS Shell Dlg 2\', sans-serif"; var labelFill = "#999999"; var bufferColor = "#888888"; var bufferWidth = 2; var textAlign = "top"; var offsetX = -45; var offsetY = -30; var placement = 'point'; if (feature.get("Chamber No") !== null) { labelText = String(feature.get("Chamber No")); } var style = categories_Area5B_Chamber_9(feature, value, size, resolution, labelText, labelFont, labelFill, bufferColor, bufferWidth, textAlign, offsetX, offsetY, placement); return style; };
The red-marked variables are missing in the initial code and should be mentioned in the variables considered. However, it’s not enough to just put them there and set their values. They are usually reliant on the main file function.js, which defines them as an overhead option.
In the function.js file, which we can find in the /resources/ folder we should change some things. First of all, we should find the textStyle variable and proceed with it slightly as shown below.
Instead of:
var textStyle = new ol.style.Text({
font: labelFont,
text: labelText,
textBaseline: "middle",
textAlign: "left",
offsetX: 8,
offsetY: 3,
placement: placement,
maxAngle: 0,
fill: new ol.style.Fill({
color: labelFill
}),
stroke: bufferStyle
});
return textStyle;
};
it should be:
var textStyle = new ol.style.Text({ font: labelFont, text: labelText, textBaseline: "middle", textAlign: "right", offsetX: offsetX, offsetY: offsetY, placement: placement, maxAngle: 0, fill: new ol.style.Fill({ color: labelFill }), stroke: bufferStyle }); return textStyle; };
As you can see, in the text variable there are basically 2 groups of properties. These ones, which are fetched from variables like font: labelFont or text: labelText are fairly editable in our layer style file, which you can see above. Unlike the other ones, which have defined values in advance. They cannot be changed, as they are not “connected” with the variables defined in our layer file. The offsetX and offsetY variables had fixed values before, so we couldn’t play with them in our layer style file. If we provide the “connections” which you can see marked red, then they will start work. The same situation applies to textAlign property, but in this case, I decided to keep the fixed overhead argument. Anyhow, the result should make us happy (Pic. 13).
Concluding, it’s good to know how to deal with these few steps. Predominantly any small alterations of our labels are required for the map produced by QGIS2web. However, quite frequent upgrades of this plugin might mean, that all these exercises won’t be needed in the future.
Mariusz Krukar
Links:
- https://openlayers.org/en/latest/apidoc/module-ol_Feature-Feature.html#get
- Openlayers – Interacting with features
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring
- https://htmlcolorcodes.com/
- https://openlayers.org/en/latest/apidoc/module-ol_style_Text.html
Forums:
- https://gis.stackexchange.com/questions/319398/labeling-when-using-qgis2web
- https://gis.stackexchange.com/questions/15784/bypassing-10-character-limit-of-field-name-in-shapefiles
- https://gis.stackexchange.com/questions/157860/change-label-size-in-openlayers-3-from-qgis-plugin-output
- Labels not retaining buffers in OpenLayers 3
- OL colors
- https://gis.stackexchange.com/questions/374510/how-to-make-labels-be-center-justified-in-an-openlayers-web-map
- OpenLayers font marker clashing with label
- https://gis.stackexchange.com/questions/251504/mantain-text-style-qgis2web-ol3
- https://gis.stackexchange.com/questions/337223/qgis2web-initial-view-problem
My questions:
- stackoverflow.com/questions/64803229/labels-in-qgis2web-are-not-editable
- https://gis.stackexchange.com/questions/378985/qgis2web-struggling-with-labelling