I'm building an application for mapping features onto an image layer depicting a floor plan (using OL's ImageStatic layer). Each feature has an svg icon as style and may have additional svg icons as "badges" around the edges.
I've set up a simplified version of the relevant parts of the code in this jsfiddle.
var map = new ol.Map({
layers: [],
interactions: ol.interaction.defaults({}),
target: "map"
});
var pixelProjection = new ol.proj.Projection({
code: 'pixel',
units: 'pixels',
extent: [0, 0, 4097, 1596]
}),
// create layer
floorMapLayer = new ol.layer.Image({
source: new ol.source.ImageStatic({
url: "https://sunriverassistedliving.com/wp-content/uploads/Main-Floor-Plan.jpg",
imageSize: [4097, 1596],
projection: pixelProjection,
imageExtent: pixelProjection.getExtent()
})
}),
// create view
floorMapView = new ol.View({
projection: pixelProjection,
center: [2000, 750] || ol.extent.getCenter(pixelProjection.getExtent()),
zoom: 1
}),
poiSource = new ol.source.Vector({
features: []
}),
vectorLayer = new ol.layer.Vector({
source: poiSource
}),
layerGroup = new ol.layer.Group({
layers: [floorMapLayer, vectorLayer]
});
map.setView(floorMapView);
map.setLayerGroup(layerGroup);
var iconStyle = new ol.style.Icon( /** @type {olx.style.IconOptions} */ ({
anchor: [0.5, 1],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
size: [25, 25],
imageSize: [25, 25],
src: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIGlkPSJzdmczNjQ4Ij4KICA8ZGVmcyBpZD0iZGVmczM2NTAiLz4KICA8cGF0aCBkPSJNIDEyLjUxNDksNC45NmUtMDA3IEMgNS42MTE3NCw0Ljk2ZS0wMDcgMCw1LjU4MTk0MDUgMCwxMi40ODUxIEMgMCwxOS4zODgyNiA1LjYxMTc0LDI1IDEyLjUxNDksMjUgQyAxNS4yNzY5NCwyNSAxNy44MDQ4LDI0LjA3NDg5IDE5Ljg3NDg1LDIyLjU1NjYyIEwgMTIuNTQ0NywxNy4xOTMwOSBMIDUuMjE0NTQsMjIuNTg2NDEgTCA4LjA0NTI5LDEzLjk0NTE3IEwgMC42NTU1NCw4LjY3MTA0MDUgTCA5Ljc0MzczOTUsOC42NzEwNDA1IEwgMTIuNDg1MSwwLjAyOTgwMDUgTCAxNS4yODYwNSw4LjY3MTA0MDUgTCAyNC4zNDQ0NTksOC42MTE0NDA1IEwgMTcuMDE0MywxMy45MTUzOCBMIDE5Ljg3NDg1LDIyLjU1NjYyIEMgMjIuOTc4NDc5LDIwLjI4MDI4IDI1LDE2LjYyNjIyIDI1LDEyLjQ4NTEgQyAyNSw1LjU4MTk0MDUgMTkuNDE4MDYsNC45NmUtMDA3IDEyLjUxNDksNC45NmUtMDA3IHogIi8+Cjwvc3ZnPg=="
}));
var styleCache = {};
var customStyleFunctions = [
function(resolution) {
var style = new ol.style.Style({
image: new ol.style.Icon(({
anchor: [1, 2],
anchorXUnits: 'fraction',
anchorYUnits: 'fraction',
size: [15, 15],
imageSize: [15, 15],
src: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgo8c3ZnIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmVyc2lvbj0iMS4wIiB3aWR0aD0iMjUiIGhlaWdodD0iMjUiIGlkPSJzdmczNjQ4Ij4KICA8ZGVmcyBpZD0iZGVmczM2NTAiLz4KICA8cGF0aCBkPSJNIDEyLjUxNDksNC45NmUtMDA3IEMgNS42MTE3NCw0Ljk2ZS0wMDcgMCw1LjU4MTk0MDUgMCwxMi40ODUxIEMgMCwxOS4zODgyNiA1LjYxMTc0LDI1IDEyLjUxNDksMjUgQyAxNS4yNzY5NCwyNSAxNy44MDQ4LDI0LjA3NDg5IDE5Ljg3NDg1LDIyLjU1NjYyIEwgMTIuNTQ0NywxNy4xOTMwOSBMIDUuMjE0NTQsMjIuNTg2NDEgTCA4LjA0NTI5LDEzLjk0NTE3IEwgMC42NTU1NCw4LjY3MTA0MDUgTCA5Ljc0MzczOTUsOC42NzEwNDA1IEwgMTIuNDg1MSwwLjAyOTgwMDUgTCAxNS4yODYwNSw4LjY3MTA0MDUgTCAyNC4zNDQ0NTksOC42MTE0NDA1IEwgMTcuMDE0MywxMy45MTUzOCBMIDE5Ljg3NDg1LDIyLjU1NjYyIEMgMjIuOTc4NDc5LDIwLjI4MDI4IDI1LDE2LjYyNjIyIDI1LDEyLjQ4NTEgQyAyNSw1LjU4MTk0MDUgMTkuNDE4MDYsNC45NmUtMDA3IDEyLjUxNDksNC45NmUtMDA3IHogIi8+Cjwvc3ZnPg=="
}))
});
return [style];
}
];
var defaultStyleFunction = function(resolution) {
var feature = this;
this.set('manuallyHidden', false);
if (!feature.get('selected') && (feature.get('hidden') || feature.get('manuallyHidden'))) {
// use hidden marker style
if (!styleCache.hidden) {
styleCache.hidden = new ol.style.Style({});
}
return [styleCache.hidden];
}
// draw marker normally
var iconSrc = iconStyle.getSrc();
if (!styleCache[iconSrc]) {
styleCache[iconSrc] = new ol.style.Style({
image: iconStyle,
});
}
var styles = [styleCache[iconSrc]];
// add styles from registered overlay style functions
for (var i = 0; i < customStyleFunctions.length; i++) {
//console.log(customStyleFunctions[i]);
styles = styles.concat(customStyleFunctions[i](resolution));
}
return styles;
};
for (var i = 0; i < 500; i++) {
var posX = Math.random() * 4097;
var posY = Math.random() * 1596;
var feature = new ol.Feature({
geometry: new ol.geom.Point([posX, posY]),
});
feature.setStyle(defaultStyleFunction);
poiSource.addFeature(feature);
}
There's 500 features with one badge each. Panning the map feels jerky and the Timeline in Chrome notices that the frame rate drops to around 5 fps.
This is still sort of usable, but in my real application the situation is far worse, with total freeze-ups even when using only around 100 features with 1-2 badges each. I haven't been able to narrow down the reason why my real application is less responsive than this demo, but the profiler doesn't take note of any other code being run than the rendering in OpenLayers and the GPU being busy panting. The bottom layer image in my real app tests is much bigger, though. Around 10000x7000px (the jsfiddle one is around 4000x1600px). This will of course render a huge canvas which will require resources to be re-painted.
I'm wondering if there are any other performance boosts in OpenLayers that I could employ to make panning of the map more responsive? I'm using version 3.15.1 on my late 2011 13-inch MacBook Pro with newest Google Chrome / Firefox.
The static image layer should not be a problem. But you are using style functions for the vector layer in a very inefficient way. Do not create a new style instance with every call of the style function. Instead, create the style outside and only have your style function return it. Also, it is better to set a single style function on the layer, instead of setting one on every feature.