Reputation: 41
I'm using OpenLayers V4 and I'm trying to see if it's possible to allow a user to click on a feature's vector label and move/drag it to a location of their choice. My initial thought was to capture when a user clicked on the label, and then dynamically calculate and set the offsetX and offsetY properties of the label (ol.style.Text) as the user's mouse pointer moved around. To achieve this, I need to capture when the user clicks on the label and not the feature itself. The main problem is that I can't find a way to distinguish this. It appears as though the label is part of the vector feature because clicking on the feature highlights both the feature and the label and vice versa.
In summary, my question is two fold:
Note: I'm familiar with overlays and realize they might be easier to work with since they have a setPosition property, but the way my web map is constructed I need to display vector labels for each feature and not overlays
Upvotes: 0
Views: 1035
Reputation: 17897
It is possible using vector labels in OpenLayers 6 where the modify interaction has access to the features being modified in its style function and can use hit detection of offset labels, but that is not available in version 4. In this example labels move with their features, but can also be moved independently of the features. Clones are needed to avoid changing feature geometries while moving the labels. The labels geometries are then restored and replaced with offsets for styling. When a feature is moved its label clone is kept in sync.
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/css/ol.css" type="text/css">
<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.5.0/build/ol.js"></script>
<style>
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<script>
var white = [255, 255, 255, 1];
var blue = [0, 153, 255, 1];
var width = 3;
var modifyStyle = new ol.style.Style({
image: new ol.style.Circle({
radius: width * 2,
fill: new ol.style.Fill({
color: blue
}),
stroke: new ol.style.Stroke({
color: white,
width: width / 2
})
}),
zIndex: Infinity
});
var labelStyle = new ol.style.Style({
text: new ol.style.Text({
offsetY: 10,
font: '12px Calibri,sans-serif',
fill: new ol.style.Fill({
color: '#000',
}),
stroke: new ol.style.Stroke({
color: '#fff',
width: 3,
}),
backgroundFill: new ol.style.Fill({
color: 'rgba(0 ,0, 0, 0)',
}),
}),
});
var featureLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: 'https://mikenunn.net/data/world_cities.geojson',
format: new ol.format.GeoJSON(),
}),
});
var labelLayer = new ol.layer.Vector({
source: new ol.source.Vector(),
renderBuffer: 1e3,
style: function (feature) {
labelStyle.getText().setOffsetX(feature.get('offsetX') || 0);
labelStyle.getText().setOffsetY((feature.get('offsetY') || 0) - 10);
labelStyle.getText().setText(feature.get('CITY_NAME'));
return labelStyle;
},
});
featureLayer.getSource().on('addfeature', function(event) {
var id = event.feature.getId();
var feature = event.feature.clone();
feature.setId(id);
labelLayer.getSource().addFeature(feature);
});
featureLayer.getSource().on('removefeature', function(event) {
var id = event.feature.getId();
var source = labelLayer.getSource();
source.removeFeature(source.getFeatureById(id));
});
var defaultStyle = new ol.interaction.Modify({
source: featureLayer.getSource()
}).getOverlay().getStyleFunction();
var featureModify = new ol.interaction.Modify({
source: featureLayer.getSource(),
style: function(feature) {
feature.get('features').forEach( function(modifyFeature) {
var id = modifyFeature.getId();
var geometry = feature.getGeometry().clone();
labelLayer.getSource().getFeatureById(id).setGeometry(geometry);
});
return defaultStyle(feature);
}
});
var labelModify = new ol.interaction.Modify({
source: labelLayer.getSource(),
hitDetection: labelLayer,
style: function(feature) {
var styleFeature;
feature.get('features').forEach( function(modifyFeature) {
var id = modifyFeature.getId();
styleGeometry = featureLayer.getSource().getFeatureById(id).getGeometry();
});
modifyStyle.setGeometry(styleGeometry);
return modifyStyle;
}
});
labelModify.on('modifyend', function(event) {
event.features.forEach( function(feature) {
var id = feature.getId();
var labelCoordinates = feature.getGeometry().getCoordinates();
var geometry = featureLayer.getSource().getFeatureById(id).getGeometry().clone();
var featureCoordinates = geometry.getCoordinates();
var resolution = map.getView().getResolution();
var offsetX = (labelCoordinates[0] - featureCoordinates[0]) / resolution + (feature.get('offsetX') || 0);
var offsetY = (featureCoordinates[1] - labelCoordinates[1]) / resolution + (feature.get('offsetY') || 0);
feature.set('offsetX', offsetX, true);
feature.set('offsetY', offsetY, true);
feature.setGeometry(geometry);
});
});
var map = new ol.Map({
layers: [featureLayer, labelLayer],
interactions: ol.interaction.defaults().extend([labelModify, featureModify]),
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat([5, 51]),
zoom: 8
})
});
</script>
</body>
</html>
Upvotes: 2