vue-leaflet/Vue2Leaflet

Getting error when using renderer in Vue2Leaflet LCircleMarker

Koutena opened this issue · 6 comments

In the vue2-leaflet package, when I use the l-circle-marker component with v-for and renderer together, I get the following error:

RangeError: Maximum call stack size exceeded

notice that:

  1. I checked using of the renderer without v-for and it works correctly.
  2. The renderer is a computed property and I can use it in any desired method (calling it like this.renderer()), but it causes create canvas tag for each l-circle-marker in HTML DOM and I won't such thing.

e.g.
https://codesandbox.io/s/v2l-circlemarker-renderer-forked-lq53u

mikeu commented

Thanks for reporting this @Alireza-Sadeghi-0123 , it is a strange issue indeed. To narrow the problem down even further, it still occurs when simply having two l-circle-marker components on the map that each set their renderer in this way, even without using v-for.

Can you elaborate a little on your second point there? If you don't want a different canvas for each l-circle-marker, then why is it that you are specifying a canvas renderer for each one? Does adding the renderer once to your l-map component and then not specifying a separate renderer property for each component solve your problem and still give you the behaviour you do want?

mikeu commented

I have done some more digging into this issue. The problem seems to be in adding a Vue watch on the Leaflet Canvas instance, although I haven't yet tracked down why it only happens when a second instance is added. Somehow once there are two of them, there's a circular reference somewhere that is leading to never-ending recursion. The issue seems very similar to vuejs/vue/issues/9081, although in this case we aren't trying to add a watch on a Vue object, or nest two different components with the same name. But if there is a circular reference, then the behaviour would end up being the same I think, and it is definitely the _traverse function called while adding the watch where we're seeing this issue, just as in that one.

Work-around

For the case where the real problem is wanting to render all circle markers and other vector layers with canvas instead of SVG, and you don't really care whether a renderer is specified for each of those components as long as they end up on the canvas, a work-around seems to be to add preferCanvas: true to the <l-map> options.

Here's a fork of the initial repro above, with the circle marker options removed and preferCanvas used on the map: https://codesandbox.io/s/v2l-circlemarker-renderer-forked-uluy8?file=/src/App.vue

It both loads correctly with all of the circle markers, and puts them all into the canvas element instead of creating DOM nodes for each one.

@Alireza-Sadeghi-0123, let me know if that option works to get you past this issue. In the meantime, I'll keep digging.

Does adding the renderer once to your l-map component and then not specifying a separate renderer property for each component solve your problem and still give you the behaviour you do want?

Unfortunately setting the renderer option on the map generates a <path> tag for each circle-marker point in the HTML DOM. So I need to set the renderer option on the l-circle-marker.
I have over 50,000 points on the map and I won't generate a canvas tag for each point in the DOM element.

For the case where the real problem is wanting to render all circle markers and other vector layers with canvas instead of SVG, and you don't really care whether a renderer is specified for each of those components as long as they end up on the canvas, a work-around seems to be to add preferCanvas: true to the <l-map> options.

Use preferCanvas solves my problem temporarily, but I still need use of renderer for each l-circle-marker, l-circle, l-polyline and so on separately. So I would appreciate if you could solve this problem.

mikeu commented

Use preferCanvas solves my problem temporarily, but I still need use of renderer for each l-circle-marker, l-circle, l-polyline and so on separately. So I would appreciate if you could solve this problem.

I will let you know what else I am able to come up with, though this seems to be a very niche issue in an older version of the library. I would also appreciate any contributions you are able to provide to this, in terms of a solution or debugging attempts or anything else you've learned while trying to resolve the problem.

What is the use case that requires a separate renderer instance for each component? What will be different in the end result you are looking for, that is not achieved by simply setting preferCanvas: true?

Use preferCanvas solves my problem temporarily, but I still need use of renderer for each l-circle-marker, l-circle, l-polyline and so on separately. So I would appreciate if you could solve this problem.

I will let you know what else I am able to come up with, though this seems to be a very niche issue in an older version of the library. I would also appreciate any contributions you are able to provide to this, in terms of a solution or debugging attempts or anything else you've learned while trying to resolve the problem.

What is the use case that requires a separate renderer instance for each component? What will be different in the end result you are looking for, that is not achieved by simply setting preferCanvas: true?

I checked the map in two states: 1. preferCanvas: true, and 2. renderer: L.canvas({ padding: 0, tolerance: 0 }) and I saw that huge points in both states cause lag in the map. Finally, I found leaflet-pixi-overlay plugin to manage huge points with pixi renderer.
This plugin uses WebGL instead of Canvas and it let us render many markers (more than one million) on the map.