Address review comments
This commit is contained in:
@@ -3,18 +3,15 @@ layout: example.html
|
|||||||
title: Street Labels
|
title: Street Labels
|
||||||
shortdesc: Render street names with a custom render.
|
shortdesc: Render street names with a custom render.
|
||||||
docs: >
|
docs: >
|
||||||
Example showing the use of a custom renderer to render text along a path. [Labelgun](https://github.com/Geovation/labelgun) is used to avoid label collisions, and [linelabel](https://github.com/naturalatlas/linelabel) makes sure that labels are placed on suitable street segments. The data is fetched from OSM using the [Overpass API](https://overpass-api.de).
|
Example showing the use of a custom renderer to render text along a path. [Labelgun](https://github.com/Geovation/labelgun) is used to avoid label collisions. [label-segment](https://github.com/ahocevar/label-segment) makes sure that labels are placed on suitable street segments. [textpath](https://github.com/ahocevar/textpath) arranges the letters of a label along the geometry. The data is fetched from OSM using the [Overpass API](https://overpass-api.de).
|
||||||
tags: "vector, label, collision detection, labelgun, linelabel, overpass"
|
tags: "vector, label, collision detection, labelgun, linelabel, overpass"
|
||||||
resources:
|
resources:
|
||||||
- https://cdn.polyfill.io/v2/polyfill.min.js?features=Set"
|
- https://cdn.polyfill.io/v2/polyfill.min.js?features=Set"
|
||||||
- https://unpkg.com/rbush@2.0.1/rbush.min.js
|
- https://unpkg.com/rbush@2.0.1/rbush.min.js
|
||||||
- https://unpkg.com/labelgun@0.1.1/lib/labelgun.min.js
|
- https://unpkg.com/labelgun@0.1.1/lib/labelgun.min.js
|
||||||
|
- https://unpkg.com/textpath@1.0.1/dist/textpath.js
|
||||||
|
- https://unpkg.com/label-segment@1.0.0/dist/label-segment.js
|
||||||
cloak:
|
cloak:
|
||||||
As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5: Your Bing Maps Key from http://www.bingmapsportal.com/ here
|
As1HiMj1PvLPlqc_gtM7AqZfBL8ZL3VrjaS3zIb22Uvb9WKhuJObROC-qUpa81U5: Your Bing Maps Key from http://www.bingmapsportal.com/ here
|
||||||
---
|
---
|
||||||
<!-- Wrap https://npmjs.com/package/linelabel -->
|
|
||||||
<script>var module = {};</script>
|
|
||||||
<script src="https://unpkg.com/linelabel@0.1.1"></script>
|
|
||||||
<script>var linelabel = module.exports;</script>
|
|
||||||
|
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
|
|||||||
@@ -1,108 +1,45 @@
|
|||||||
// NOCOMPILE
|
// NOCOMPILE
|
||||||
|
/* global labelgun, labelSegment, textPath */
|
||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.View');
|
goog.require('ol.View');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
goog.require('ol.format.OSMXML');
|
goog.require('ol.format.OSMXML');
|
||||||
goog.require('ol.geom.LineString');
|
|
||||||
goog.require('ol.layer.Tile');
|
goog.require('ol.layer.Tile');
|
||||||
goog.require('ol.layer.Vector');
|
goog.require('ol.layer.Vector');
|
||||||
goog.require('ol.source.BingMaps');
|
goog.require('ol.source.BingMaps');
|
||||||
goog.require('ol.source.Vector');
|
goog.require('ol.source.Vector');
|
||||||
goog.require('ol.style.Style');
|
goog.require('ol.style.Style');
|
||||||
|
|
||||||
/* global labelgun */
|
var emptyFn = function() {};
|
||||||
var labelEngine = new labelgun['default'](function() {}, function() {});
|
var labelEngine = new labelgun['default'](emptyFn, emptyFn);
|
||||||
|
|
||||||
function segmentSort(a, b) {
|
var context, pixelRatio; // Will be set in the map's postcompose listener
|
||||||
return a.length - b.length;
|
function measureText(text) {
|
||||||
|
return context.measureText(text).width * pixelRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
function dist2D(p1, p2) {
|
var extent, letters; // Will be set in the style's renderer function
|
||||||
var dx = p2[0] - p1[0];
|
function collectDrawData(letter, x, y, angle) {
|
||||||
var dy = p2[1] - p1[1];
|
ol.extent.extendCoordinate(extent, [x, y]);
|
||||||
return Math.sqrt(dx * dx + dy * dy);
|
letters.push([x, y, angle, letter]);
|
||||||
}
|
|
||||||
|
|
||||||
// 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({
|
var style = new ol.style.Style({
|
||||||
geometry: function(feature) {
|
renderer: function(coords, geometry, 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');
|
var text = feature.get('name');
|
||||||
if (text) {
|
if (text) {
|
||||||
// Only consider label when the segment is long enough
|
// Only create label when geometry has a long and straight segment
|
||||||
var labelLength = context.measureText(text).width * pixelRatio;
|
var path = labelSegment(coords, Math.PI / 8, measureText(text));
|
||||||
var pathLength = 0;
|
if (path) {
|
||||||
for (var i = 1, ii = coords.length; i < ii; ++i) {
|
extent = ol.extent.createEmpty();
|
||||||
pathLength += dist2D(coords[i - 1], coords[i]);
|
letters = [];
|
||||||
if (pathLength >= labelLength) {
|
textPath(text, path, measureText, collectDrawData);
|
||||||
if (coords[0][0] > coords[coords.length - 1][0]) {
|
ol.extent.buffer(extent, 5 * pixelRatio, extent);
|
||||||
// Attempt to make text upright
|
var bounds = {
|
||||||
coords.reverse();
|
bottomLeft: ol.extent.getBottomLeft(extent),
|
||||||
}
|
topRight: ol.extent.getTopRight(extent)
|
||||||
textPath(context, text, coords, feature.getId(), pixelRatio);
|
};
|
||||||
break;
|
labelEngine.ingestLabel(bounds, feature.getId(), 1, letters, text, false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,15 +54,17 @@ var rasterLayer = new ol.layer.Tile({
|
|||||||
|
|
||||||
var source = new ol.source.Vector();
|
var source = new ol.source.Vector();
|
||||||
// Request streets from OSM, using the Overpass API
|
// Request streets from OSM, using the Overpass API
|
||||||
var client = new XMLHttpRequest();
|
fetch('https://overpass-api.de/api/interpreter', {
|
||||||
client.open('POST', 'https://overpass-api.de/api/interpreter');
|
method: 'POST',
|
||||||
client.addEventListener('load', function() {
|
body: '(way["highway"](48.19642,16.32580,48.22050,16.41986));(._;>;);out meta;'
|
||||||
var features = new ol.format.OSMXML().readFeatures(client.responseText, {
|
}).then(function(response) {
|
||||||
|
return response.text();
|
||||||
|
}).then(function(responseText) {
|
||||||
|
var features = new ol.format.OSMXML().readFeatures(responseText, {
|
||||||
featureProjection: 'EPSG:3857'
|
featureProjection: 'EPSG:3857'
|
||||||
});
|
});
|
||||||
source.addFeatures(features);
|
source.addFeatures(features);
|
||||||
});
|
});
|
||||||
client.send('(way["highway"](48.19642,16.32580,48.22050,16.41986));(._;>;);out meta;');
|
|
||||||
|
|
||||||
var vectorLayer = new ol.layer.Vector({
|
var vectorLayer = new ol.layer.Vector({
|
||||||
source: source,
|
source: source,
|
||||||
@@ -136,13 +75,13 @@ var vectorLayer = new ol.layer.Vector({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var extent = [1817379, 6139595, 1827851, 6143616];
|
var viewExtent = [1817379, 6139595, 1827851, 6143616];
|
||||||
var map = new ol.Map({
|
var map = new ol.Map({
|
||||||
layers: [rasterLayer, vectorLayer],
|
layers: [rasterLayer, vectorLayer],
|
||||||
target: 'map',
|
target: 'map',
|
||||||
view: new ol.View({
|
view: new ol.View({
|
||||||
extent: extent,
|
extent: viewExtent,
|
||||||
center: ol.extent.getCenter(extent),
|
center: ol.extent.getCenter(viewExtent),
|
||||||
zoom: 17,
|
zoom: 17,
|
||||||
minZoom: 14
|
minZoom: 14
|
||||||
})
|
})
|
||||||
@@ -152,8 +91,8 @@ vectorLayer.on('precompose', function() {
|
|||||||
labelEngine.destroy();
|
labelEngine.destroy();
|
||||||
});
|
});
|
||||||
vectorLayer.on('postcompose', function(e) {
|
vectorLayer.on('postcompose', function(e) {
|
||||||
var context = e.context;
|
context = e.context;
|
||||||
var pixelRatio = e.frameState.pixelRatio;
|
pixelRatio = e.frameState.pixelRatio;
|
||||||
context.save();
|
context.save();
|
||||||
context.font = 'normal 11px "Open Sans", "Arial Unicode MS"';
|
context.font = 'normal 11px "Open Sans", "Arial Unicode MS"';
|
||||||
context.fillStyle = 'white';
|
context.fillStyle = 'white';
|
||||||
|
|||||||
@@ -11,4 +11,3 @@ docs: >
|
|||||||
tags: "vector, renderer, labelgun, label"
|
tags: "vector, renderer, labelgun, label"
|
||||||
---
|
---
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<div id="info"> </div>
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
// NOCOMPILE
|
// NOCOMPILE
|
||||||
|
/* global labelgun */
|
||||||
goog.require('ol.Map');
|
goog.require('ol.Map');
|
||||||
goog.require('ol.View');
|
goog.require('ol.View');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
@@ -38,8 +39,8 @@ var map = new ol.Map({
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
/*global labelgun*/
|
var emptyFn = function() {};
|
||||||
var labelEngine = new labelgun['default'](function() {}, function() {});
|
var labelEngine = new labelgun['default'](emptyFn, emptyFn);
|
||||||
|
|
||||||
function createLabel(canvas, text, coord) {
|
function createLabel(canvas, text, coord) {
|
||||||
var halfWidth = canvas.width / 2;
|
var halfWidth = canvas.width / 2;
|
||||||
@@ -57,7 +58,7 @@ function sortByWidth(a, b) {
|
|||||||
return ol.extent.getWidth(b.getExtent()) - ol.extent.getWidth(a.getExtent());
|
return ol.extent.getWidth(b.getExtent()) - ol.extent.getWidth(a.getExtent());
|
||||||
}
|
}
|
||||||
|
|
||||||
var resolution;
|
var resolution; // This is set by the map's precompose listener
|
||||||
var styles = [
|
var styles = [
|
||||||
new ol.style.Style({
|
new ol.style.Style({
|
||||||
fill: new ol.style.Fill({
|
fill: new ol.style.Fill({
|
||||||
@@ -85,6 +86,7 @@ var styles = [
|
|||||||
context.strokeText(text, 0, 0);
|
context.strokeText(text, 0, 0);
|
||||||
context.fillText(text, 0, 0);
|
context.fillText(text, 0, 0);
|
||||||
}
|
}
|
||||||
|
// The 3rd value of the coordinate is the measure of the extent width
|
||||||
var extentWidth = geometry.getCoordinates()[2] / resolution * pixelRatio;
|
var extentWidth = geometry.getCoordinates()[2] / resolution * pixelRatio;
|
||||||
if (extentWidth > canvas.width) {
|
if (extentWidth > canvas.width) {
|
||||||
// Only consider labels not wider than their country's bounding box
|
// Only consider labels not wider than their country's bounding box
|
||||||
@@ -100,6 +102,7 @@ var styles = [
|
|||||||
}
|
}
|
||||||
var coordinates = geometry.getInteriorPoint().getCoordinates();
|
var coordinates = geometry.getInteriorPoint().getCoordinates();
|
||||||
var extentWidth = ol.extent.getWidth(geometry.getExtent());
|
var extentWidth = ol.extent.getWidth(geometry.getExtent());
|
||||||
|
// We are using the extentWidth as measure value of the geometry
|
||||||
coordinates.push(extentWidth);
|
coordinates.push(extentWidth);
|
||||||
return new ol.geom.Point(coordinates, 'XYM');
|
return new ol.geom.Point(coordinates, 'XYM');
|
||||||
}
|
}
|
||||||
@@ -126,5 +129,4 @@ vectorLayer.on('postcompose', function(e) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
map.addLayer(vectorLayer);
|
map.addLayer(vectorLayer);
|
||||||
|
|||||||
@@ -4426,7 +4426,7 @@ olx.render.State.prototype.pixelRatio;
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolution that the render batch was created and optiized for. This is
|
* Resolution that the render batch was created and optimized for. This is
|
||||||
* not the view's resolution that is being rendered.
|
* not the view's resolution that is being rendered.
|
||||||
* @type {number}
|
* @type {number}
|
||||||
* @api
|
* @api
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
/**
|
/**
|
||||||
* @type {olx.render.State}
|
* @type {olx.render.State}
|
||||||
*/
|
*/
|
||||||
var replayState = {
|
var state = {
|
||||||
context: context,
|
context: context,
|
||||||
pixelRatio: pixelRatio,
|
pixelRatio: pixelRatio,
|
||||||
resolution: this.resolution,
|
resolution: this.resolution,
|
||||||
@@ -397,7 +397,7 @@ ol.render.canvas.Replay.prototype.replay_ = function(
|
|||||||
} else {
|
} else {
|
||||||
coords = pixelCoordinates.slice(d, dd);
|
coords = pixelCoordinates.slice(d, dd);
|
||||||
}
|
}
|
||||||
renderer(coords, geometry, feature, replayState);
|
renderer(coords, geometry, feature, state);
|
||||||
++i;
|
++i;
|
||||||
break;
|
break;
|
||||||
case ol.render.canvas.Instruction.DRAW_IMAGE:
|
case ol.render.canvas.Instruction.DRAW_IMAGE:
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ ol.style.Style.prototype.getRenderer = function() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets a custom renderer function for this style. When set, `fill`, `stroke`
|
* Sets a custom renderer function for this style. When set, `fill`, `stroke`
|
||||||
* and `image` optins of the style will be ignored.
|
* and `image` options of the style will be ignored.
|
||||||
* @param {ol.StyleRenderFunction|null} renderer Custom renderer function.
|
* @param {ol.StyleRenderFunction|null} renderer Custom renderer function.
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user