2

Is it possible to have an animated svg as a vector layer feature? I'm using D3 to make the animation. Here's my animated svg: https://jsfiddle.net/eflowbeach/opfkh0n9/1/

I thought I was onto something when I got this to work:

// Turn the svg into an xml string for use in OpenLayers
var svgstring = (new XMLSerializer()).serializeToString(d3.select(svg.node()).node());

where svg is the animated version, but it only captures a snapshot of the animation.

I get this into OpenLayers like this:

 new ol.style.Style({
                image: new ol.style.Icon({
                    src: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgstring)
                })
            });

After I got this to work, it makes sense why it is not animated; the string will only capture the current state of the svg. So, is there another way to do this or do I have to look into putting this on a canvas?

  • try this animated svg [strategies] code acuriousanimal.com/AnimatedCluster – Mapperz Apr 4 at 0:57
  • Do you want this svg to be of constant size or zoomable? – TomazicM Apr 4 at 19:47
  • @TomazicM I would like it to scale as the user zooms in. – Jonathan Apr 4 at 21:37
  • Then it's going to be a bit more complicated, I'll have solution this evening. – TomazicM Apr 5 at 5:22
1

Animated svg with static (non zoomable) size can be put on map via ol.Overlay layer. Overlay can be use to display any html element on the map. In this case svg is embedded in div element.

Basis for code below was taken from OpenLayers Overlay example:

<div id="map" class="map"></div>
<div style="display: none;">
  <a class="overlay" id="vienna" target="_blank" href="http://en.wikipedia.org/wiki/Vienna">Vienna</a>
  <div id="svgContainer" style="width: 200px; height: 200px;">
     <svg viewBox="0 0 200 200" style="width: 100%; height: auto"></svg>
  </div>
</div>
<script>
  var svgLonLat = [16.3725, 48.208889];
  var svgPosition = ol.proj.fromLonLat(svgLonLat);
  var svgContainer = document.getElementById('svgContainer');

  var layer = new ol.layer.Tile({
    source: new ol.source.OSM()
  });

  var scaleLineControl = new ol.control.ScaleLine();

  var map = new ol.Map({
    layers: [layer],
    target: 'map',
    view: new ol.View({
      center: svgPosition,
      zoom: 8
    }),
    controls: ol.control.defaults().extend([scaleLineControl])
  });

  var svgLayer = new ol.Overlay({
    position: svgPosition,
    positioning: 'center-center',
    element: document.getElementById('svgContainer'),
    stopEvent: false
  });
  map.addOverlay(svgLayer);

  var vienna = new ol.Overlay({
    position: svgPosition,
    element: document.getElementById('vienna')
  });
  map.addOverlay(vienna);

  function lineAnimate(selection) {
    selection
      .attr('x1', 100)
      .attr('y1', 100)
      .attr('x2', 100) //function(d) {return d;})
      .attr('y2', 100) //function(d) {return d;})
      .attr('stroke', '#000000')
      .attr('stroke-width', 5)
      .style('opacity', 0.7)
      .transition()
      .ease(d3.easeLinear)
      .duration(200)
      .attr('x2', 200)
      .transition()
      .duration(1000)
      .style('opacity', 0)
      .on('end', function() {
          d3.select(this).call(lineAnimate);
      });
  }

  d3.select('svg')
    .attr("transform", "rotate(-90)")
    .selectAll('line')
    .data([0])
    .enter()
    .append('line')
    .call(lineAnimate);      
</script>

To make svg resize when zooming, it has to be resized at the end of zoom event. Size of svg in meters is also defined (lenM) and minimal size in pixels for svg to be displayed on map (minLenPixels):

var minLenPixels = 20;
var lenM = 50000;
svgContainer.style.display = 'none';

map.on('moveend', function() {
  var svgPixelPos = map.getPixelFromCoordinate(svgPosition);
  var svgPixelPos2 = map.getPixelFromCoordinate([svgPosition[0] + lenM, svgPosition[1]]);
  var lenPixels = svgPixelPos2[0] - svgPixelPos[0];

  var view = map.getView();
  var projection = view.getProjection();
  var resolutionAtEquator = view.getResolution();
  var center = view.getCenter();
  var pointResolution = ol.proj.getPointResolution(projection, resolutionAtEquator, center);
  var resolutionFactor = resolutionAtEquator/pointResolution;

  lenPixels = (lenPixels / projection.getMetersPerUnit()) * resolutionFactor;
  var svgWidth = lenPixels * 2;

  if (lenPixels < minLenPixels)
    svgContainer.style.display = 'none';
  else {
    svgContainer.style.width = svgWidth + "px";
    svgContainer.style.height = svgWidth + "px";
    svgContainer.style.display = 'block';
  }              
}); 
  • Thanks! That's a unique solution - now I have to figure out how to do many of these ;-) – Jonathan Apr 6 at 22:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.