I do not know how can I create add event, or attach a listener to an event when a feature is added to the source. At this moment I have a bunch of other events, like:
draw.on("drawend", function (e) {
//....
});
I thought that drawend event is what I need, but as it turned out, when this event happens, the feature is not yet added to the source.
Alternatively you can use 'addfeature' event:
source.on( 'addfeature', function (ft) {
// ft - feature being added
});
try adding it manually to the source:
you add the draw interaction to an overlay
var features = new ol.Collection();
var featureOverlay = new ol.layer.Vector({
source: new ol.source.Vector({features: features}),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: 'rgba(255, 255, 255, 0.2)'
}),
stroke: new ol.style.Stroke({
color: '#ffcc33',
width: 2
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: '#ffcc33'
})
})
})
});
featureOverlay.setMap(map);
var draw = new ol.interaction.Draw({
features: features, // we set the newly drawn feature on the overlay declared previously
type: /** #type {ol.geom.GeometryType} */ ('Polygon') // for example polygon
});
in the drawend event you push the feature to the source layer you want
draw.on('drawend', function(event) {
yourSource.addFeature(event.feature);
}
Related
The objective
I'm trying to replicate the "singleselect-hover" feature in this example from the OpenLayers site.
The issue
When I tried to use this implementation, the hit detection was very poor with vtLayer.getFeatures(event.pixel). The documentation for the function states:
The hit detection algorithm used for this method is optimized for performance, but is less accurate than the one used in map.getFeaturesAtPixel()
Indeed, when I switched to map.getFeaturesAtPixel, the performance increased, but the features still does not work entirely as expected.
When I move my pointer over a vector boundary from the outside, it (usually) behaves as expected:
However, when I move to an adjacent boundary and then back, the feature no longer works:
My code:
proj4.defs(
'EPSG:6931',
'+proj=laea +lat_0=90 +lon_0=0 +x_0=0 +y_0=0 +ellps=WGS84 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs'
);
register(proj4);
const projection = get('EPSG:6931');
const osm = new OSM();
const map = new Map({
target: something,
layers: [osm],
view: new View({
projection: projection,
zoom: 5,
}),
})
// Vector Styles
const unselectedStyle = new Style({
stroke: new Stroke({
color: 'rgba(50,50,50,0.9)',
width: 1.2,
}),
});
const selectedStyle = new Style({
stroke: new Stroke({
color: 'white',
width: 2,
}),
fill: new Fill({
color: 'rgba(0,0,0,0.3)',
}),
});
const createVtLayer = () => {
const vtSource = new VectorTileSource({
tileGrid: new TileGrid({
extent: [
-9009964.761231285, -9009964.761231285, 9009964.761231285,
9009964.761231285,
],
tileSize: 256,
resolutions: [70390.34969711941],
}),
projection: projection,
format: new MVT({ idProperty: 'some_id' }),
url:
geoserverUrl +
'/gwc/service/tms/1.0.0/' +
mvtLayerName +
'#EPSG%3A' +
projection.getCode().split(':')[1] + // EPSG number of current projection
'#pbf/{z}/{x}/{-y}.pbf',
});
return new VectorTileLayer({
zIndex: 1,
source: vtSource,
style: unselectedStyle,
});
};
const vtLayer = createVtLayer();
// Local lookup for highlighted features
let selection = null;
const selectionLayer = new VectorTileLayer({
zIndex: 2,
source: vtLayer.getSource(),
style: feature => feature === selection && selectedStyle,
});
if (map && vtLayer && selectionLayer) {
// Add layers to map once loaded into state
map.addLayer(vtLayer);
map.addLayer(selectionLayer);
// Update styling of selectionLayer on mouse hover
map.on(['pointermove', 'click'], event => {
// Send vector metadata to parent component on mouse click
if (event.type === 'click' && selection) {
onFeatureSelect(selection);
}
map.forEachFeatureAtPixel(
event.pixel,
feature => {
selection = feature;
}, {
layerFilter: layer => layer === vtLayer,
hitTolerance: 1,
}
);
selectionLayer.changed()
});
}
What I've tried so far
I've tried adjusting the renderBuffer and renderMode parameters in the VectorTile layer, as well as adjusting the hitTolerance option in map.forEachFeatureAtPixel, and I haven't had any luck yet. When I logged the feature id from the feature parameter in forEachFeatureAtPixel, I noticed something strange--when the unexpected behavior occurs, after dragging the pointer over an adjacent boundary line, the selected variable rapidly switches between the two features and assigns the undesired one, until I touch the pointer to a non-adjacent boundary line. Modifying the hitTolerance only causes the selected feature to switch more frequently.
Theories and questions
I'm thinking that maybe my adjacent vectors are overlapping each others boundaries? Or maybe there is a problem with the way the vectors are loaded as MVTs?
Adding an invisible Fill() to the unselectedStyle allowed the layer to be hit-detected and solved my issue!
// Vector Styles
const unselectedStyle = new Style({
stroke: new Stroke({
color: 'rgba(50,50,50,0.9)',
width: 1.2,
}),
fill: new Fill({
color: 'rgba(0,0,0,0)',
}),
});
Thanks Mike!
I'm looking for a way to render a hover feature vector only over that feature, to essentially render the hover vector over its feature without rendering over other features (specifically, LineString hovers not rendering over proximate markers)
I have hovering set up without issue as per the vector example, the features all rendering in the order I would like (LineStrings are below markers). The issue is that when a LineString is hovered over its hover feature is rendered not only over that LineString but also any marker that the LineString crosses under, and so there is a big streak of the LineString's hover over that marker. I can see how this makes sense just considering how the feature-overlaying vector works, but I can't see how to adjust it as I would like.
I've found several posts that seem to offer incongruous advice on setting a zIndex in the styles or using a renderOrder function, though none have seemed exactly applicable to this situation, or are beyond my n00b capacities. I have tried setting a zIndex on all concerned Styles as well as something along the lines of this renderOrder function, but not to the desired effect. Specifically with the latter, I couldn't figure out where to put the function so that the hover might render according to it.
Any advice is greatly appreciated!
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: 'map',
view: new ol.View({
center: ol.proj.fromLonLat([132.4903, 34.0024]),
zoom: 4
})
});
var vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: '../static/gpx/summer3.kml',
format: new ol.format.KML({
extractStyles: false
})
}),
style: function(feature) {
return routeStyle;
},
});
vectorLayer.getSource().on('addfeature', function(event) {
event.feature.set('hoverstyle', 'route');
});
map.addLayer(vectorLayer);
var vectorLayer = new ol.layer.Vector({
source: new ol.source.Vector({
url: '../static/gpx/testmoo3.kml',
format: new ol.format.KML({
extractStyles: false,
}),
}),
style: function(feature) {
return iconStyle;
},
});
vectorLayer.getSource().on('addfeature', function(event) {
event.feature.set('hoverstyle', 'route');
});
map.addLayer(vectorLayer);
var hoverStyles = {
'moo': new ol.style.Icon({
anchor: [0.5, 30],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '../static/GPX/icon/mooinb.png',
}),
'route': new ol.style.Stroke({
color: 'rgba(236, 26, 201, 0.5)',
width: 5
})
};
var routeStyle = new ol.style.Style({
stroke: new ol.style.Stroke({
color: 'rgba(209,14,14,.6)',
width: 4
})
});
var iconStyle = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 30],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '../static/GPX/icon/moo7.png',
}),
});
var hoverStyleCache = {}
function hoverStyler(feature, resolution) {
var hover = feature.get('hoverstyle');
var geometry = feature.getGeometry().getType();
console.log(hover);
while (geometry === 'Point') {
if (!hoverStyleCache[hover]) {
hoverStyleCache[hover] = new ol.style.Style({
image: hoverStyles[hover],
})
}
return [hoverStyleCache[hover]];
}
while (geometry === 'LineString') {
if (!hoverStyleCache[hover]) {
hoverStyleCache[hover] = new ol.style.Style({
stroke: hoverStyles[hover]
})
}
return [hoverStyleCache[hover]];
}
}
var featureOverlay = new ol.layer.Vector({
source: new ol.source.Vector(),
map: map,
style: hoverStyler
});
var highlight;
var hover = function(pixel) {
var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
return feature;
});
if (feature !== highlight) {
if (highlight) {
featureOverlay.getSource().removeFeature(highlight);
}
if (feature) {
featureOverlay.getSource().addFeature(feature);
}
highlight = feature;
}
};
map.on('pointermove', function(evt) {
if (evt.dragging) {
return;
}
var pixel = map.getEventPixel(evt.originalEvent);
hover(pixel);
});
***** EDIT 1 *****
Here is a plunkr that at least illustrates what I mean (with the hover vector overlapping the marker).
I continued fiddling around and did get manage to achieve what I wanted, by separating the point and linestring 'featureOverlay' vector into two separate vectors, though to get that to work I also then had to duplicate everything that it implies (the hoverStyler function, the hover feature, etc).
As well, when I referred above to incongruous advice that came especially to the fore as I was able to set the zIndex on that any vector layer, but not in the Styles as I read in just about every other question I found but to the vector itself. Thus,
var featureOverlay = new ol.layer.Vector({
source: new ol.source.Vector(),
map: map,
style: hoverStyler,
zIndex: 1,
});
worked (with zIndex similarly applied to ever other vector layer), but because points and linestrings share the feature Overlay that also meant that the marker's hover vector image was below the marker it was meant to hover over: applied in this way both linestring and markers or neither hover above.
I thus tried to come up with a function similar to the hoverStyler that differentiates them:
function zIndexer(feature, resolution) {
var geometry = feature.getGeometry().getType();
if (geometry === 'Point') {
return 5
}
if (geometry === 'LineString') {
return 1
}
}
but to no avail (I was unsure what exactly should be returned and tried various inputs; this as others did not work).
So I could settle for duplicated featureOverlay apparatuses, but a handy zIndex function would be of course be preferable :)
(*note: I could not get the zIndex to work in the plunkr as described above, in or out of Styles, hence it does not illustrate my later fiddling but only the original question)
***** EDIT 2 *****
I noted in a comment below on the provided answer that, while the answer works wonderfully, it did make cluster styles styled by function (not mentioned in the original post0 not work. I overcame this with the following vector, which worked but that I am not qualified to say worked well:
var clusterCache = {}
var CLM = new ol.layer.Vector({
source: new ol.source.Vector(),
style: function(feature) {
var size = feature.get('features').length;
var style = clusterCache[size]
if (size === 1) {
return [hoverStyles['moo']]
}
else if (size > 1) {
if (!style) {
style = new ol.style.Style({
image:new ol.style.Icon({
anchor: [0.5, 30],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '../static/GPX/icon/moo3Hover.png',
}),
text: new ol.style.Text({
text: size.toString(),
font: '12px Calibri,sans-serif',
fill: new ol.style.Fill({
color: '#fff'
}),
stroke: new ol.style.Stroke({
color: '#000',
width: 5
}),
offsetX: 22,
offsetY: 6,
})
}),
clusterCache[size] = style;
}
return style;
}
}
});
hoverLayers['clustermoo'] = CLM;
Approach
The basic idea is you need separate hover layers for the route and the icons (top to bottom):
Icon hover layer
Icon layer
Route hover layer
Route layer
This will mean that say an unhovered icon will draw over the top of a hovered route.
ZIndex
You can't use the style zIndex as it applies only to features within a layer. That is, the layer zIndex has a higher precedence than the style zIndex. In your plunkr I couldn't get the layer zIndex to work at all, but it is implicit in the order the layers are added.
So, you need to:
create separate hover layers
setup the layers in the order I gave above
when hovering a feature, move it to the appropriate hover layer
when unhovering a feature, remove it from the appropriate hover layer
Style Cache
Another thing, your style cache implementation was rather dubious. In fact you do not need a style cache as your are using layer styles and they are only assigned/created once.
Plunkr
Here is an updated plunkr with the above changes: https://plnkr.co/edit/gpswRq3tjTTy9O0L
var createHoverLayer = function(style) {
return new ol.layer.Vector({
source: new ol.source.Vector(),
style: style,
});
}
var hoverLayers = {};
hoverLayers['route'] = createHoverLayer([hoverStyles['route']]);
hoverLayers['moo'] = createHoverLayer([hoverStyles['moo']]);
map.addLayer(routeLayer);
map.addLayer(hoverLayers['route']);
map.addLayer(mooLayer);
map.addLayer(hoverLayers['moo']);
var highlight;
var hover = function(pixel) {
var feature = map.forEachFeatureAtPixel(pixel, function(feature) {
return feature;
});
if (feature !== highlight) {
if (highlight) {
var highlightType = highlight.get('type');
hoverLayers[highlightType].getSource().removeFeature(highlight);
}
if (feature) {
var featureType = feature.get('type');
hoverLayers[featureType].getSource().addFeature(feature);
}
highlight = feature;
}
};
Alternative Approach
do not use separate hover layers at all
use per feature styling via style function
when the feature is hovered/unhovered, set a 'hovered' property on it
to true/false
in the style function, check this property and use the appropriate
style
you will need a style cache with this method
I have a openlayers map which has draw interaction. When user starts drawing a linestring on the map, the portion of linestring drawn should look different than what the user would draw next.
In brief when user drops a point on the map, the line till that point should be block, without dash or any other styling options.
To illustrate what I am trying to do here goes the screenshot -
We can see user is about to add another point on the line so line till that point should turn block in blue color.
I have maintained the collection of points being added to the map, when user opts to delete the last point, it is being removed from the map but the last segment should also disappear from map. Can't find anything to achieve that.
Also I have added the ol.style.RegularShape() to display a square but it is displaying a circle instead don't know what I am doing wrong.
Here is jsbin link to my code - Draw interaction line style problem
You would need to use a style function for your draw style and divide the geometry into two parts for styling:
var drawStyle = function(feature) {
var styles = [];
if (feature.getGeometry().getType() == 'LineString') {
var coordinates = feature.getGeometry().getCoordinates();
styles.push(new ol.style.Style({
geometry: new ol.geom.LineString(coordinates.slice(-2)),
stroke: new ol.style.Stroke({
color: '#0000FF',
lineDash: [1, 20],
width: 5,
lineCap:'square',
lineJoin:'round',
lineDashOffset:10
})
}));
styles.push(new ol.style.Style({
geometry: new ol.geom.MultiPoint(coordinates),
image: new ol.style.RegularShape({
points: 4,
radius: 6,
angle: Math.PI / 4,
fill: new ol.style.Fill({
color: 'blue'
}),
stroke: new ol.style.Stroke({
color: 'blue'
}),
})
}));
if (coordinates.length > 2) {
styles.push(new ol.style.Style({
geometry: new ol.geom.LineString(coordinates.slice(0,-1)),
stroke: new ol.style.Stroke({
color: '#0000FF',
width: 2,
lineCap:'square',
lineJoin:'round'
})
}));
}
}
return styles;
}
To remove the last point from the line interaction simply use
line.removeLastPoint();
making sure var line; is declared so it is in the scope of the button click function
I have a click event attached to my map. On this click event I trigger a function which is supposed to add a feature to the map, but now nothing happens. I tried it like so:
function boo (map, layer){
var source = layer.getSource();
var thing = new ol.geom.Polygon( [[
ol.proj.transform([-16,-22], 'EPSG:4326', 'EPSG:3857'),
ol.proj.transform([-44,-55], 'EPSG:4326', 'EPSG:3857'),
ol.proj.transform([-88,75], 'EPSG:4326', 'EPSG:3857')
]]);
var featurething = new ol.Feature({
name: "Thing",
geometry: thing,
style: function() {
console.log("Never see this text");
return new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(192,192,192,1)"
}),
stroke: new ol.style.Stroke({
color: "rgba(192,192,192,1)",
width: 10
})
})
}
});
source.addFeature( featurething );
// see no error messages, but still no feature is added to the map
}
It is a OL3 bug
Not so fast.
The first argument of your function should be the click event. Another error: there's no style parameter in ol.Feature constructor.
Set the feature style after its creation. So:
featurething.setStyle(some_style_or_a_function);
I'm loading a GPX file into my OL3 code. Now i'd like to whole line that the GPX makes to be clickable with some extra information. Now I can't for the life of me find a way to click the line drawn for the route. What listener can I use?
I don't want to click on the whole map but just the line.
I've tried attaching click/singleclick to vector to no avail.
Any ideas on how to do so?
My code:
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var style = {
'LineString': new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#000',
width: 3
})
}),
'MultiLineString': new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#000',
width: 3
})
})
};
var vector = new ol.layer.Vector({
source: new ol.source.Vector({
url: 'kust.gpx',
format: new ol.format.GPX()
}),
style: function(feature) {
return style[feature.getGeometry().getType()];
}
});
var map = new ol.Map({
layers: [raster, vector],
target: document.getElementById('map'),
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
Try adding the click on the map, and in the handler you check on wich feature you clicked. For example:
map.on('click', displayFeatureInfo);
function displayFeatureInfo( evt ){
//get pixel position of click event
var pixel = evt.pixel;
var features = [];
//loop through all features under this pixel coordinate
//and save them in array
map.forEachFeatureAtPixel(pixel, function(feature, layer) {
features.push(feature)
});
//the top most feature
var target = features[0];
//...rest of code
target.get('customProp')
}
EDIT
You can put some extra juice in your feature by inserting extra properies to the passed object. for example:
var myFeature = new ol.Feature({
geometry: ...,
labelPoint: ..,
name:...,
customProp1: ...,
anothercustomProp: ...
})