Search code examples
javascriptanimationopenlayersopenlayers-5openlayers-6

How to create flying animations between two distant polygons in OpenLayers


Using Openlayers (v6.1.1.) I'm trying to create simple FlyTo animation between several polygons. FlyTo animation works fine when there is somehow small distance between them but with increase in distance there is decrease in user experience.

On larger distance I'm just seeing fast panning through map in low zoom. I tried with ahocevar answer from this link but without desired effect.

As per instructions, I combined a center animation with two zoom animations and started them at the same time. Example:

let view = map.getView();
let extentOfPolygon = feature.getGeometry().getExtent();
let resolution = view.getResolutionForExtent(extentOfPolygon);
let zoom = view.getZoomForResolution(resolution);
let center = ol.extent.getCenter(extentOfPolygon);

view.animate({
  center: center,
  duration: duration
});
view.animate({
  zoom: zoom - 1,
  duration: duration / 2
}, {
  zoom: zoom,
  duration: duration / 2
});

This FlyTo animation works fine when polygons are in vicinity but with the increase in distance, FlyTo animation is transforming into fast panning across map.

I've created small app for testing purposes. When we click on list item map will animate and zoom to clicked field. In example when awesome, another and super field are in vicinity and FlyTo animations works as expected but when we click from super to epic field (which is in far south. Then map is just fast panning to location).

So, my question is this. Is it possible to achieve same FlyTo effect for polygons independently of polygon location (leaflet has this nicely set https://regionbound.com/leaflet-fly-demo)

Here is jsfiddle example: https://jsfiddle.net/Svinjica/1kjfp4ds/


Solution

  • I think you need more refinement in how you zoom. For example zoom out just enough so both start and destination are visible at peak altitude. You can do that by calculating distance and dividing by map width. Make some adjustment to duration for overall up and down changes, but not too much, and divide the up and down durations proportionately. To reduce the initial duration if the current resolution is already greater than the calculated peak altitude resolution 100% of duration should be downward, and you should also open the map close to where your features are located by fitting it to the vectorSource extent https://jsfiddle.net/b4mL9s28/8/

    let geojsonData = new ol.format.GeoJSON().readFeatures(fieldData, {
      featureProjection: 'EPSG:3857'
    });
    let vectorSource = new ol.source.Vector({
      features: geojsonData
    });
    
    let vectorLayer = new ol.layer.Vector({
      source: vectorSource
    });
    
    var map = new ol.Map({
      target: 'map',
      layers: [
        new ol.layer.Tile({
          source: new ol.source.OSM()
        }),
        vectorLayer
      ],
      view: new ol.View({
        center: [0, 0],
        zoom: 4
      })
    });
    
    map.getView().fit(vectorSource.getExtent());
    
    const duration = 2000;
    
    var featureListElement = document.getElementById("vectorFeatures");
    
    var features = vectorLayer.getSource().getFeatures();
    for (let feature of features) {
      let aElement = document.createElement("a");
      aElement.classList.add("list-group-item", "list-group-item-action");
      featureListElement.appendChild(aElement);
      aElement.innerHTML = feature.get("Name");
      aElement.onclick = function() {
    
        let view = map.getView();
        var extentOfPolygon = feature.getGeometry().getExtent();
        let resolution = view.getResolutionForExtent(extentOfPolygon);
        var center = ol.extent.getCenter(extentOfPolygon);
        var currentCenter = map.getView().getCenter();
        var currentResolution = map.getView().getResolution();
        var distance = Math.sqrt(Math.pow(center[0] - currentCenter[0], 2) + Math.pow(center[1] - currentCenter[1], 2));
        var maxResolution = Math.max(distance/ map.getSize()[0], currentResolution);
        var up = Math.abs(maxResolution - currentResolution);
        var down = Math.abs(maxResolution - resolution);
        var adjustedDuration = duration + Math.sqrt(up + down) * 100;
    
        view.animate({
          center: center,
          duration: adjustedDuration
        });
        view.animate({
          resolution: maxResolution,
          duration: adjustedDuration * up / (up + down)
        }, {
          resolution: resolution,
          duration: adjustedDuration * down / (up + down)
        });
      }
    }