openlayers icon indicating copy to clipboard operation
openlayers copied to clipboard

Dashed line performance

Open M393 opened this issue 5 years ago • 12 comments

Describe the bug Long dashed lines reduce render performance by a lot. See also #5520 and #5645

To Reproduce

  1. Go to https://codesandbox.io/s/flamboyant-paper-sjb3m
  • Checkbox changes styles from dashed to continuous line
  • Select input switches different features
  1. Compare zooming in / out with the different options
  • Non-dashed lines are fine.
  • Subdividing the line into multiple segments doesn't help.
  • Using a MultiLineString (as suggested in one of the linked issues) with multiple short line segments instead of a LineString with multiple segments doesn't seem to help either.
  • Using multiple Features with short lines helps, though the dashes are no longer continuous, which looks bad especially on low zoom levels.

Does openlayers simplify the subdivided geometry again?

M393 avatar Oct 17 '19 15:10 M393

I checked for the case of the subdivided lines and does get simplified again to a two-ponts line. I it possible to prevent this? Would a subdivided LineString actually improve render performance?

Maybe not related but... When zooming in further away from the line start, the dash offset of the two styles seems to get out of sync.

M393 avatar Oct 17 '19 16:10 M393

whoa i,m new here

queenstar2233 avatar Oct 19 '19 03:10 queenstar2233

queenstar

queenstar2233 avatar Oct 19 '19 03:10 queenstar2233

I updated the example so one can also change the cap style and dash pattern and log the time for the render after a style changes.

With this I did some tests with the 2-point LineString ...

  • The time needed to draw scales somewhat linearly with the amount of dashes needed for the whole line.

Here's a quick test at Zoom 17 with 5,5 pattern and 2-point LineString: Repeated a few times and only noted the best.

browser square butt round line
Firefox 29 ms 34 ms 32 ms 4 ms
Chromium 1253 ms 951 ms 1671 ms 2 ms

Chromium is a lot slower than Firefox and falls back to non-dashed line at a certain amount of dashes (in the above case at zoom level 18).

To recap:

  • Don't use dashes for very long lines.
  • Use longer dash patterns if possible.
  • Prefer butt over square and square over round unless you know all users are using Firefox.

M393 avatar Oct 30 '19 10:10 M393

We are also experiencing slow performance (zooming) when showing many dashed lines. I have not yet performed any detailed analysis as you have.

wflemingnz-retired avatar Nov 20 '19 17:11 wflemingnz-retired

When testing with:

  • 75 line strings, each with an average of 5 vertices per line
  • solid line style, overlaid with either a dashed or solid narrower line

I found:

  • performance (smoothness of rapid in/out zoom and pan) is significantly worse when using dashed lines rather than solid lines.

  • segmenting the lines had no apparent effect

  • I performed the majority of testing in Chrome. In the limited testing I did in Firefox, I did not notice any significant difference ie: performance of dashed lines was still inferior

  • Using only a single line string with 2 vertices, panning becomes very bad at high zoom levels (17-19) when using dashed lines.

Currently our application design uses dashed lines so we would rather retain them if the performance issues can be overcome but as it stands it seems the only sure fire way of avoiding the issues, is to not use dashed lines.

It would be good to know if this is an inherent browser limitation, or if it is something possibly address within openlayers.

wflemingnz-retired avatar Nov 27 '19 19:11 wflemingnz-retired

This is a browser limitation, but you can address it in your application by using ol/layer/VectorImage instad of ol/layer/Vector.

ahocevar avatar Nov 28 '19 09:11 ahocevar

Thanks @ahocevar, changing to VectorImage layer improved responsiveness, however I still have some performance issues which are detailed in #10350.

wflemingnz-retired avatar Nov 28 '19 17:11 wflemingnz-retired

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Jan 27 '20 17:01 stale[bot]

I just wanted to add to the discussion if more people are having problems with this. We could not get the desired performance even if we switched to VectorImageLayer. But we managed to create a work around that we are considering to use where we crop the feature within the styling function. For this we needed to use JSTS and the performance is still not great, but it is a lot better than not applying it.

I tried to extract our code to a bite sized example that can be overviewed, its bad... But this should at least give you an idea:

import Coordinate from 'jsts/org/locationtech/jts/geom/Coordinate.js';
import Feature from '../src/ol/Feature.js';
import GeometryFactory from 'jsts/org/locationtech/jts/geom/GeometryFactory.js';
import Map from '../src/ol/Map.js';
import OverlayOp from 'jsts/org/locationtech/jts/operation/overlay/OverlayOp.js';
import Polygon from 'jsts/org/locationtech/jts/geom/Polygon.js';
import VectorLayer from '../src/ol/layer/Vector.js';
import VectorSource from '../src/ol/source/Vector.js';
import View from '../src/ol/View.js';
import WKT from '../src/ol/format/WKT.js';
import WKTReader from 'jsts/org/locationtech/jts/io/WKTReader.js';
import WKTWriter from 'jsts/org/locationtech/jts/io/WKTWriter.js';
import {Fill, Stroke, Style} from '../src/ol/style.js';
import {LineString} from '../src/ol/geom.js';
import {fromLonLat} from '../src/ol/proj.js';

/**
 * Crops a geometry given an extent
 *
 * @param {Geometry} geometry - the geometry to be cropped
 * @param {{left:number,bottom:number,right:number, top:number}} extent - the extent to crop the geometry with
 */
const cropGeometry = (geometry, extent) => {
  // Convert to JSTS geometry
  const wkt = new WKT().writeGeometry(geometry);
  const jstsGeometry = new WKTReader(new GeometryFactory()).read(wkt);

  // Create a geometry of the extent
  const geometryFactory = new GeometryFactory();
  const linearRing = geometryFactory.createLinearRing([
    new Coordinate(extent[0], extent[1]),
    new Coordinate(extent[0], extent[3]),
    new Coordinate(extent[2], extent[3]),
    new Coordinate(extent[2], extent[1]),
    new Coordinate(extent[0], extent[1]),
  ]);
  const jstsExtentGeometry = new Polygon(linearRing, null, geometryFactory);

  // Crop the geometry using the extent
  const jstsCroppedGeometry = OverlayOp.intersection(
    jstsGeometry,
    jstsExtentGeometry
  );

  // Convert cropped geometry back to openlayers geometry
  if (jstsCroppedGeometry.getNumPoints() > 0) {
    const newWkt = new WKTWriter(new GeometryFactory())
      .write(jstsCroppedGeometry)
      .replace(/EMPTY/, '()');
    return new WKT().readGeometry(newWkt);
  }
  return geometry;
};

/**
 * Our custom styling function that also crops the geometry
 *
 * @param {Feature} feature
 * @return {Style}
 */
const customStyling = (feature) => {
  const viewExtent = getMapExtent();
  const croppedGeometry = cropGeometry(feature.getGeometry(), viewExtent);

  return new Style({
    geometry: croppedGeometry,
    fill: new Fill({
      color: 'white',
    }),
    stroke: new Stroke({
      color: 'black',
      width: 3,
      lineDash: [10, 10],
    }),
  });
};


const vectorLayer = new VectorLayer({
  source: new VectorSource(),
  style: customStyling,
});

for (let i = 0; i < 10; i++) {
  const x1 = Math.random() * 360 - 180;
  const y1 = Math.random() * 180 - 90;
  const x2 = Math.random() * 360 - 180;
  const y2 = Math.random() * 180 - 90;
  const lineString = new LineString([fromLonLat([x1, y1]), fromLonLat([x2, y2])]);
  const feature = new Feature(lineString);
  vectorLayer.getSource().addFeature(feature);
}

const map = new Map({
  target: 'map',
  view: new View({
    center: fromLonLat([0, 0]),
    zoom: 2,
  }),
  layers: [vectorLayer],
});

const getMapExtent = () => {
  return map.getView().calculateExtent(map.getSize());
};

breiler avatar Jun 23 '21 09:06 breiler

This issue is still happening with OpenLayers 8.2.0 and should be reopened. Performance is extremely slow when drawing long dashed lines and it somehow gets even worse when zooming in on them. Using VectorLayers are unbearably slow. Using VectorImageLayers are much better, but still not as performant as it should be.

urbenlegend avatar Feb 21 '24 10:02 urbenlegend

To fix this in the canvas renderer, we need to crop lines as described in https://github.com/openlayers/openlayers/issues/10139#issuecomment-866673610, but with a more efficient cropping algorithm.

@urbenlegend To get good dashed line performance right now, you can use the WebGL renderer.

ahocevar avatar Feb 21 '24 10:02 ahocevar