Map Performance

Implemented ways to improve map performance are documented here.

Layer-based lazy loading

The plant layer (PlantsLayer.tsx) lazy loads elements as they enter the visible map area.

It does this by calculating the currently visible bounds of the canvas and checking which elements intersect with these bounds. The intersecting elements are saved into a Set, and only these elements are rendered. The used utility can be found in lazy-load-util.ts to make it easy to reuse in other layers.

This improves overall performance and reduces memory usage because elements are only loaded and rendered when they are needed.

However, this approach can still be improved. Before spatial indexing was added, the plant layer still iterated over all plantings to determine which elements were visible. This means that lazy loading reduced rendering cost, but visible-element lookup itself could still be expensive on larger maps.

Investigation: Map interaction and zooming performance

Map interaction performance was investigated because zooming and moving the map caused noticeable lag, especially on larger maps. The issue was more visible in Firefox.

Investigation method

Performance was mostly tested with the Firefox developer tools. The runtime analysis tab was used to record performance while panning and zooming the map for about 15 seconds.

Several possible bottlenecks were considered:

  • expensive event handling during panning and zooming;
  • rendering cost caused by many drawing-layer polygons;
  • unnecessary React or Konva updates during map interaction;
  • inefficient lookup of visible plantings.

Investigated areas and results

Event handling during panning and zooming

Panning and zooming can trigger many events in a short time. If many event listeners are active during these interactions, they could cause performance degradation.

To test this, event listeners were disabled one by one and the resulting performance was compared. No significant performance change was observed during this investigation.

Result:

  • event handling was not identified as the main bottleneck;
  • this only reflects the state of the code at the time of the investigation;
  • event handling could still become relevant again if more expensive handlers are added in the future.

Drawing-layer polygons

The drawing layer was also considered as a possible source of performance problems. Drawing-layer elements can contain polygon-based shapes. Rendering and updating many polygons, especially polygons with many points, can become expensive.

This was investigated by comparing performance with different layer combinations:

  • all layers enabled;
  • all layers except drawing layers enabled;
  • all layers except the plant layer enabled;
  • only drawing layers enabled;
  • only the plant layer enabled.

Some improvement was observed when layers were disabled. However, the improvement was not large enough to identify the drawing layer as the main bottleneck.

Result:

  • drawing-layer rendering may contribute to the total rendering cost;
  • it was not identified as the main cause of the observed zooming and panning performance problem.

React and Konva updates during map interaction

Unnecessary React or Konva updates during map interaction were also considered as a possible bottleneck. Panning and zooming can trigger many updates in a short time. If these updates cause React components to re-render unnecessarily or cause Konva to redraw unchanged canvas elements, map interaction can become less responsive.

To investigate this, log statements were added to monitor the update rate. Since lazy loading had already been implemented, the observed reload rate was not high enough to indicate a significant bottleneck from React or Konva updates during map interaction.

Result:

  • unnecessary React or Konva updates were not identified as the main bottleneck during this investigation;
  • this could still be worth checking again if performance problems reappear after future changes.

Plant-layer viewport selection

The most significant finding was that the plant layer's viewport selection still used a linear scan. Even though the plant layer was already lazy loading its elements, it still had to iterate over all plantings to determine which ones were visible.

This was identified as a significant bottleneck, especially on larger maps with many plantings.

Result:

  • lazy loading reduced the number of rendered plantings;
  • however, the lookup of visible plantings was still inefficient;
  • the linear scan was identified as a major performance bottleneck.

Implemented improvements

A spatial index was implemented for the plant layer. This allows the plant layer to find visible plantings more efficiently instead of scanning all plantings during viewport selection.

This improves the existing lazy-loading approach because it reduces the cost of finding the elements that need to be rendered. For more information check out the decision document at doc/decisions/implemented/frontend_spatial_indexing.md

Suggested improvements

Further investigation is still needed for long editing sessions. Performance degradation over longer editing sessions had been reported before, but it was not observed during this investigation.

Other possible improvements include:

  • adding application performance monitoring, for example with SigNoz, to observe performance during longer editing sessions;
  • checking which other layers still use linear scans for viewport selection;
  • investigating whether other layers could benefit from spatial indexing;
  • checking whether visually complex layers become expensive; this includes drawing polygons, shade elements, and similar content;
  • reducing unnecessary React or Konva updates if future profiling shows that they become relevant;
  • investigating whether dynamic plant icon preview sizes should be implemented;
  • verifying whether plant icons are already cached effectively by the browser.

Images

Already implemented

Plant icons are loaded from the Nextcloud instance. The tested plant icons were 512x512 px PNG files. Plant icons are expected to be relatively small, with a maximum size of 500 KB, and should use WebP where possible.

WebP is already used for plant icons where available. This is useful because WebP usually provides smaller file sizes than JPEG or PNG while maintaining acceptable visual quality.

Nextcloud previews are already enabled. The public Nextcloud preview endpoint can be used for plant icons.

Browser caching also works for plant icons.

Investigated possible improvements

Dynamic image resizing

Plant icons are currently loaded through the public WebDAV endpoint. This loads the stored icon file directly.

The public Nextcloud preview endpoint was tested as a possible way to load smaller plant icon previews. The following icons were tested:

IconRequestReturned dimensionsFile size
Ficus_carica.pngoriginal512x512152120 bytes
Ficus_carica.png64x64 preview64x645201 bytes
Ficus_carica.png128x128 preview256x25644584 bytes
Ficus_carica.png256x256 preview256x25644584 bytes
Ficus_carica.png500x500 preview512x512146828 bytes
Solanum_lycopersicum.pngoriginal512x512217373 bytes
Solanum_lycopersicum.png64x64 preview64x645639 bytes
Solanum_lycopersicum.png128x128 preview256x25663051 bytes
Solanum_lycopersicum.png256x256 preview256x25663051 bytes
Solanum_lycopersicum.png500x500 preview512x512210389 bytes
Eruca_sativa.pngoriginal512x51292971 bytes
Eruca_sativa.png64x64 preview64x643333 bytes
Eruca_sativa.png128x128 preview256x25629623 bytes
Eruca_sativa.png256x256 preview256x25629623 bytes
Eruca_sativa.png500x500 preview512x51290235 bytes

The tested icons show that smaller previews can significantly reduce file size. This could be useful when the map is zoomed out, because full-size plant icons are not necessary at low zoom levels.

However, Nextcloud does not return every requested size exactly. For the tested icons, requesting 128x128 and 256x256 returned the same 256x256 image. Requesting 500x500 returned a 512x512 image.

Therefore, if dynamic plant icon resizing is implemented, it should use only a small number of size buckets. Based on the tested icons, useful buckets would likely be 64, 256, and 512 px.

Since the current WebDAV images are cached by the browser, the expected benefit is mainly reduced initial loading time, reduced image decoding cost, and lower memory usage. It is not guaranteed that this would significantly improve panning or zooming performance. A final implementation should therefore still be benchmarked in the map before adding dynamic preview-size switching.

See the Nextcloud preview configuration documentation: https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/previews_configuration.html

Browser caching

Browser caching for plant icons was checked for one icon. Ficus_carica.png was loaded from the browser disk cache after the first request.

This means that repeated network loading is probably not the main performance problem for already loaded icons. However, caching behavior should be checked again if the image-loading implementation is changed to use preview URLs.