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:
| Icon | Request | Returned dimensions | File size |
|---|---|---|---|
Ficus_carica.png | original | 512x512 | 152120 bytes |
Ficus_carica.png | 64x64 preview | 64x64 | 5201 bytes |
Ficus_carica.png | 128x128 preview | 256x256 | 44584 bytes |
Ficus_carica.png | 256x256 preview | 256x256 | 44584 bytes |
Ficus_carica.png | 500x500 preview | 512x512 | 146828 bytes |
Solanum_lycopersicum.png | original | 512x512 | 217373 bytes |
Solanum_lycopersicum.png | 64x64 preview | 64x64 | 5639 bytes |
Solanum_lycopersicum.png | 128x128 preview | 256x256 | 63051 bytes |
Solanum_lycopersicum.png | 256x256 preview | 256x256 | 63051 bytes |
Solanum_lycopersicum.png | 500x500 preview | 512x512 | 210389 bytes |
Eruca_sativa.png | original | 512x512 | 92971 bytes |
Eruca_sativa.png | 64x64 preview | 64x64 | 3333 bytes |
Eruca_sativa.png | 128x128 preview | 256x256 | 29623 bytes |
Eruca_sativa.png | 256x256 preview | 256x256 | 29623 bytes |
Eruca_sativa.png | 500x500 preview | 512x512 | 90235 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.