ghettovoice / vuelayers

Web map Vue components with the power of OpenLayers
https://vuelayers.github.io/
MIT License
680 stars 227 forks source link

Performance tuning #355

Closed logue closed 3 years ago

logue commented 3 years ago

The operation becomes heavy when the number of features exceeds 3000. Any good ideas for speeding up the display?

For the time being, instead of the implementation of turning vl-feature with v-for and pouring it into vl-vector-layer and color-coding with vl-style, use the API of openlayers directly, generate the style in advance, and then vector-layer. The implementation that substitutes for is able to speed up to some extent, but it is not enough.

ghettovoice commented 3 years ago

Hello @logue , you are right, first of all to improve performance you should not use vl-feature and use features as array instead. The Vue doesn't allow to make fully virtual components, therefore each vl-feature component add html element to the DOM, then on each refresh Vue manipulate with DOM element. Obviously this operations are very slow for map.

So:

  1. use features as array of GeoJSON objects and pass them to vl-source-vector through features prop. Style features with vl-style-func component at the layer level. Also you can freeze them to avoid Vue reactivity:
data () {
  features: Object.freeze(features), // or Object.seal()
},
  1. If you have a lot of features or they are changes very often and you need to react on this, then freezing is not a solution. Then you can work with features through OpenLayers native API and use VueLayers only to configurate map, layers, layer styles, source, but load, add, remove features with OpenLayers API. If you load feature from url, then you can subscribe to addfeature, removefeature events. Or you can define custom loader for the vl-source-vector. Style features as above with vl-style-func.

  2. Use server or client clustering, bbox loading, Vector tiles and so on. This obviously requires advanced backend .

logue commented 3 years ago

Yes. The current implementation carves out a json describing the coordinates to be read depending on the value of the VueRouter query.

So, I'm converting to GeoJson with the following source.

https://github.com/logue/Pip-BoyA/blob/a54d1b5a4638b7a96803292f704af8cf62516a26/src/assets/utility.js#L50-L91

Also, the marker's style is pre-set using Vuetify's color definitions, and the marker's definition is specified when specifying the feature.

https://github.com/logue/Pip-BoyA/blob/a54d1b5a4638b7a96803292f704af8cf62516a26/src/assets/utility.js#L132-L203

This has improved the operation considerably, but it becomes heavy at around 3000 items.

I am considering an implementation that uses trigonometric functions and the cosine theorem to extract only distance markers with respect to the center coordinates. But this method is not good when there are a lot of markers in a small area, and it has a problem that the data must be retrieved and redrawn every time when move the map.

So, I posted this to see if I had any good ideas.

ghettovoice commented 3 years ago

I think if disabling Vue reactivity on GeoJSON features array not work for you (my first point), then you should try to add/remove features through OpenLayers API (point 2).

Cause you already build GeoJSON features this shouldn't be very hard to convert them to ol.Feature objects. Just take a note that ol.Feature geometry coordinates should be always in map view projection (by default EPSG:3857). <vl-map data-projection="EPSG:4326"> only switches coordinates at the VueLayers level, but underlying ol.View object uses EPSG:3857.

<template>
  <vl-map data-projection="EPSG:4326">
    <vl-view :center.sync="center" :zoom.sync="zoom" />

   <vl-layer-vector id="features" ...>
      <vl-source-vector ref="featuresSource" :loader-factory="makeFeaturesLoader" @mounted="featuresSourceMounted" />
      <vl-style-func :factory="..." />
   </vl-layer-vector>
  </vl-map>
</template>

<script>
import Feature from 'ol/Feature'
import Point from 'ol/geom/Point'
import { fromLonLat } from 'ol/proj'

export default {
  data () {
    return {
      center: [10, 10],
      zoom: 2,
    }
  },
  methods: {
    async loadFeatures () {
       // load from backend
       // return array of GeoJSON features with coordinates in the projection defined as data-projection
      // or return array of ol.Feature with coordinates in vl-view projection
       return []
    },
    // from the loader just return array of GeoJSON features or array of ol.Feature's
    // also you can return just unparsed response body, but to parse it correctly you should provide
    // format-factory to the vl-source-vector (by default GeoJSON format is used)
    makeFeaturesLoader () {
      return (bbox, resolution, projection) => this.loadFeatures(),
    },
    // and sure you can just add all features manually
    async featuresSourceMounted (vm) {
       // convert to ol.Feature, you can use here ol/format/GeoJSON or any other implementation
      const features = (await this.loadFeatures()).map(feature => new Feature({
        ...feature,
        geometry: new Point(fromLonLat(feature.geometry.coordinates)),
      }))
      vm.$source.addFeatures(features)
    },
  },
}
</script>
stale[bot] commented 3 years ago

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.