Two new examples show how custom renderers can be used to render text along paths, and to declutter labels using 3rd party libraries.
178 lines
5.4 KiB
JavaScript
178 lines
5.4 KiB
JavaScript
// NOCOMPILE
|
|
goog.require('ol.Map');
|
|
goog.require('ol.View');
|
|
goog.require('ol.extent');
|
|
goog.require('ol.format.OSMXML');
|
|
goog.require('ol.geom.LineString');
|
|
goog.require('ol.layer.Tile');
|
|
goog.require('ol.layer.Vector');
|
|
goog.require('ol.source.BingMaps');
|
|
goog.require('ol.source.Vector');
|
|
goog.require('ol.style.Style');
|
|
|
|
/* global labelgun */
|
|
var labelEngine = new labelgun['default'](function() {}, function() {});
|
|
|
|
function segmentSort(a, b) {
|
|
return a.length - b.length;
|
|
}
|
|
|
|
function dist2D(p1, p2) {
|
|
var dx = p2[0] - p1[0];
|
|
var dy = p2[1] - p1[1];
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
// Modified from https://github.com/Viglino/ol3-ext/blob/7d17eef5720970fd36798ebc889ea44d9f04b059/style/settextpathstyle.js
|
|
function textPath(ctx, text, path, fid, pixelRatio) {
|
|
var di, dpos = 0;
|
|
var pos = 1;
|
|
var letterPadding = ctx.measureText(' ').width * pixelRatio * 0.25;
|
|
var d = 0;
|
|
for (var i = 1; i < path.length; ++i) {
|
|
d += dist2D(path[i - 1], path[i]);
|
|
}
|
|
var nbspace = text.split(' ').length - 1;
|
|
var start = (d - ctx.measureText(text).width * pixelRatio - (text.length + nbspace) * letterPadding) / 2;
|
|
var extent = ol.extent.createEmpty();
|
|
var letters = [];
|
|
for (var t = 0; t < text.length; t++) {
|
|
var letter = text[t];
|
|
var wl = ctx.measureText(letter).width * pixelRatio;
|
|
var dl = start + wl / 2;
|
|
if (!di || dpos + di < dl) {
|
|
for (; pos < path.length;) {
|
|
di = dist2D(path[pos - 1], path[pos]);
|
|
if (dpos + di > dl) {
|
|
break;
|
|
}
|
|
pos += 1;
|
|
if (pos >= path.length) {
|
|
break;
|
|
}
|
|
dpos += di;
|
|
}
|
|
}
|
|
var x, y, a, dt = dl - dpos;
|
|
if (pos >= path.length) {
|
|
pos = path.length - 1;
|
|
}
|
|
a = Math.atan2(path[pos][1] - path[pos - 1][1], path[pos][0] - path[pos - 1][0]);
|
|
if (!dt) {
|
|
x = path[pos - 1][0];
|
|
y = path[pos - 1][1];
|
|
} else {
|
|
x = path[pos - 1][0] + (path[pos][0] - path[pos - 1][0]) * dt / di;
|
|
y = path[pos - 1][1] + (path[pos][1] - path[pos - 1][1]) * dt / di;
|
|
}
|
|
ol.extent.extendCoordinate(extent, [x, y]);
|
|
letters.push([x, y, a, letter]);
|
|
start += wl + letterPadding * (letter == ' ' ? 2 : 1);
|
|
}
|
|
ol.extent.buffer(extent, 5 * pixelRatio, extent);
|
|
var bounds = {
|
|
bottomLeft: ol.extent.getBottomLeft(extent),
|
|
topRight: ol.extent.getTopRight(extent)
|
|
};
|
|
labelEngine.ingestLabel(bounds, fid, 1, letters, text, false);
|
|
}
|
|
|
|
var style = new ol.style.Style({
|
|
geometry: function(feature) {
|
|
// Use the longest, straight enough segment of the geometry
|
|
var coords = feature.getGeometry().getCoordinates();
|
|
/* global linelabel */
|
|
var segment = linelabel(coords, Math.PI / 8).sort(segmentSort)[0];
|
|
return new ol.geom.LineString(coords.slice(segment.beginIndex, segment.endIndex));
|
|
},
|
|
renderer: function(coords, geometry, feature, state) {
|
|
var context = state.context;
|
|
var pixelRatio = state.pixelRatio;
|
|
var text = feature.get('name');
|
|
if (text) {
|
|
// Only consider label when the segment is long enough
|
|
var labelLength = context.measureText(text).width * pixelRatio;
|
|
var pathLength = 0;
|
|
for (var i = 1, ii = coords.length; i < ii; ++i) {
|
|
pathLength += dist2D(coords[i - 1], coords[i]);
|
|
if (pathLength >= labelLength) {
|
|
if (coords[0][0] > coords[coords.length - 1][0]) {
|
|
// Attempt to make text upright
|
|
coords.reverse();
|
|
}
|
|
textPath(context, text, coords, feature.getId(), pixelRatio);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
var rasterLayer = new ol.layer.Tile({
|
|
source: new ol.source.BingMaps({
|
|
key: 'As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5',
|
|
imagerySet: 'Aerial'
|
|
})
|
|
});
|
|
|
|
var source = new ol.source.Vector();
|
|
// Request streets from OSM, using the Overpass API
|
|
var client = new XMLHttpRequest();
|
|
client.open('POST', 'https://overpass-api.de/api/interpreter');
|
|
client.addEventListener('load', function() {
|
|
var features = new ol.format.OSMXML().readFeatures(client.responseText, {
|
|
featureProjection: 'EPSG:3857'
|
|
});
|
|
source.addFeatures(features);
|
|
});
|
|
client.send('(way["highway"](48.19642,16.32580,48.22050,16.41986));(._;>;);out meta;');
|
|
|
|
var vectorLayer = new ol.layer.Vector({
|
|
source: source,
|
|
style: function(feature) {
|
|
if (feature.getGeometry().getType() == 'LineString') {
|
|
return style;
|
|
}
|
|
}
|
|
});
|
|
|
|
var extent = [1817379, 6139595, 1827851, 6143616];
|
|
var map = new ol.Map({
|
|
layers: [rasterLayer, vectorLayer],
|
|
target: 'map',
|
|
view: new ol.View({
|
|
extent: extent,
|
|
center: ol.extent.getCenter(extent),
|
|
zoom: 17,
|
|
minZoom: 14
|
|
})
|
|
});
|
|
|
|
vectorLayer.on('precompose', function() {
|
|
labelEngine.destroy();
|
|
});
|
|
vectorLayer.on('postcompose', function(e) {
|
|
var context = e.context;
|
|
var pixelRatio = e.frameState.pixelRatio;
|
|
context.save();
|
|
context.font = 'normal 11px "Open Sans", "Arial Unicode MS"';
|
|
context.fillStyle = 'white';
|
|
context.textBaseline = 'middle';
|
|
context.textAlign = 'center';
|
|
var labels = labelEngine.getShown();
|
|
for (var i = 0, ii = labels.length; i < ii; ++i) {
|
|
// Render label letter by letter
|
|
var letters = labels[i].labelObject;
|
|
for (var j = 0, jj = letters.length; j < jj; ++j) {
|
|
var labelData = letters[j];
|
|
context.save();
|
|
context.translate(labelData[0], labelData[1]);
|
|
context.rotate(labelData[2]);
|
|
context.scale(pixelRatio, pixelRatio);
|
|
context.fillText(labelData[3], 0, 0);
|
|
context.restore();
|
|
}
|
|
}
|
|
context.restore();
|
|
});
|