title: MapBox GL JS marker/icon animation slow performance
body: <p>I'm using mapbox-gl-js to animate many image from one coordinates to another on the map.
  It become slow frame rate when I try to add up to 15 image and above.
  Doing performance profile on chrome give me the hint to method Actor.receive, which cost the most computational time.</p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>/**
   * Util.js
   */
   var linesDataSource = {
    "type": "FeatureCollection",
    "features": [{
      "type": "Feature",
      "properties": {},
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [
            151.15684390068054, -33.89568424317427
          ],
          [
            151.15808844566345, -33.89606717952166
          ],
          [
            151.15779876708984, -33.89680633086413
          ],
          [
            151.15740180015564, -33.897794824453406
          ],
          [
            151.1582601070404, -33.8980085512904
          ],
          [
            151.1609423160553, -33.89863191817193
          ],
          [
            151.16222977638245, -33.89621857248702
          ],
          [
            151.16639256477356, -33.89771467675142
          ],
          [
            151.1694610118866, -33.898916884371395
          ],
          [
            151.17089867591858, -33.896298721595166
          ],
          [
            151.17217540740964, -33.899014841282515
          ],
          [
            151.1714780330658, -33.899192944468965
          ],
          [
            151.17132782936093, -33.89878330658397
          ],
          [
            151.1719822883606, -33.8985784869035
          ],
          [
            151.17339849472046, -33.89839147720036
          ],
          [
            151.17376327514648, -33.89825789858986
          ],
          [
            151.17332339286804, -33.897269410368615
          ],
          [
            151.1732053756714, -33.89697553328233
          ],
          [
            151.17341995239258, -33.89662822269281
          ],
          [
            151.17295861244202, -33.896263099778615
          ],
          [
            151.17225050926208, -33.89589797530112
          ],
          [
            151.17136001586914, -33.89561299901295
          ],
          [
            151.17184281349182, -33.894758064434605
          ],
          [
            151.17200374603271, -33.89455323508587
          ],
          [
            151.17257237434387, -33.89148073582115
          ],
          [
            151.17042660713196, -33.89132042847356
          ],
          [
            151.17168188095093, -33.88838140703873
          ],
          [
            151.1716067790985, -33.887606557247125
          ],
          [
            151.16321682929993, -33.888274531623864
          ],
          [
            151.16029858589172, -33.88777577791726
          ],
          [
            151.1591076850891, -33.88790937294604
          ],
          [
            151.15857124328613, -33.8892809364742
          ],
          [
            151.1584746837616, -33.89006467716016
          ],
          [
            151.15894675254822, -33.89009139546571
          ],
          [
            151.15893602371216, -33.889806399775104
          ]
        ]
      }
    }]
  }
  
  
  var PI = Math.PI;
  var TWO_PI = Math.PI * 2;
  
  function rotation(start, end) {
    var dx = end[0] - start[0];
    var dy = end[1] - start[1];
    return -Math.atan2(dy, dx) * (180 / PI);
  };
  
  function lerp(v0, v1, t) {
    return v0 * (1 - t) + v1 * t
  }
  
  function interpolateAngle(fromAngle, toAngle, t) {
    fromAngle = fromAngle * (PI / 180);
    toAngle = toAngle * (PI / 180);
  
    fromAngle = (fromAngle + TWO_PI) % TWO_PI;
    toAngle = (toAngle + TWO_PI) % TWO_PI;
  
    var diff = Math.abs(fromAngle - toAngle);
    if (diff &lt; PI) {
      return lerp(fromAngle, toAngle, t) * (180 / PI);
    } else {
      if (fromAngle &gt; toAngle) {
        fromAngle = fromAngle - TWO_PI;
        return lerp(fromAngle, toAngle, t) * (180 / PI);
      } else if (toAngle &gt; fromAngle) {
        toAngle = toAngle - TWO_PI;
        return lerp(fromAngle, toAngle, t) * (180 / PI);
      }
    }
  }
  
  
  
  
  
  /**
   * Car.js
   */
  function Car(name, map, path) {
    this.name = name;
    this.map = map;
    this.path = path;
    this.speed = 90; // 30 km/h
    this.accumulatedDistance = 0;
    this.previousPos = this.path.features[0].geometry.coordinates[0];
    this.previousAngle = 0;
  
    this.animate = function(frameInfo) {
  
      this.accumulatedDistance += ((frameInfo.deltaTime / 3600) * this.speed);
      var point = turf.along(this.path.features[0], this.accumulatedDistance, 'kilometers');
  
      this.map.getSource(this.name).setData(point);
  
      var newAngle = rotation(this.previousPos, point.geometry.coordinates);
      var rotate = interpolateAngle(this.previousAngle, newAngle, 0.1);
  
      this.map.setLayoutProperty(this.name, 'icon-rotate', rotate);
  
      this.previousAngle = rotate;
      this.previousPos = point.geometry.coordinates;
    };
  
    this.init = function() {
      this.map.addSource(this.name, {
        "type": "geojson",
        "data": {
          "type": "FeatureCollection",
          "features": [{
            "type": "Feature",
            "geometry": {
              "type": "Point",
              "coordinates": this.previousPos
            }
          }]
        }
      });
  
      this.map.addLayer({
        "id": this.name,
        "type": "symbol",
        "source": this.name,
        "layout": {
          "icon-image": "car",
          "icon-size": 1,
          "icon-rotate": 0,
          "icon-rotation-alignment": "map"
        }
      });
    };
  }
  
  
  
  
  
  /**
   * MapBoxTest.js
   */
  var destination = {};
  var cars = [];
  var style = 'mapbox://styles/mapbox/streets-v9'; //'/TestEmptyProject/mapbox-gl-styles-master/styles/basic-v8.json';
  //'http://localhost:8080/styles/osm-bright.json';  // 'http://localhost:8080/styles/fiord-color-gl.json'
  
  
  mapboxgl.accessToken = 'pk.eyJ1IjoiZW1wZXJvcjE0MTIiLCJhIjoiY2ozYTYxdXFlMDM3dzJyczRsa2M5ZjE3aCJ9.9zQGtkSsjOw6npohN6ba3w';
  var map = new mapboxgl.Map({
    container: 'map',
    style: style,
    center: [132.133333, -23.116667],
    zoom: 3
  });
  
  // Used to increment the value of the point measurement against the linesData.
  var counter = 0;
  var linesData = {};
  
  function addCar() {
    var car = new Car("Car_" + counter, map, linesData);
    car.init();
    cars.push(car);
    ++counter;
  }
  
  var previousTimeStamp = 0;
  // Add a source and layer displaying a point which will be animated in a circle.
  function animate(timeStamp) {
    if (timeStamp &lt;= previousTimeStamp) {
      console.log("Wrong timeStamp, now: " + timeStamp + "\t previous: " + previousTimeStamp);
      return;
    }
  
    var i;
    var frameInfo = {
      "timeStamp": timeStamp,
      "previousTimeStamp": previousTimeStamp,
      "deltaTime": (timeStamp - previousTimeStamp) / 1000
    };
    previousTimeStamp = timeStamp;
  
    for (i = 0; i &lt; cars.length; ++i) {
      var car = cars[i];
      car.animate(frameInfo);
    }
    requestAnimationFrame(animate);
  }
  
  map.on('load', function() {
  
    console.log("map load");
  
    map.loadImage('https://maxcdn.icons8.com/office/PNG/40/Transport/railroad_car-40.png', function(error, image) {
      if (error) throw error;
      map.addImage('car', image);
    });
  
    //fetch('./lines.geojson', {
    //method: 'get'
  //}).then(function(response) {
   // return response.json();
  //}).then(function(data) {
  
    linesData = linesDataSource;
    var coordinates = linesData.features[0].geometry.coordinates;
  
    var bounds = coordinates.reduce(function(bounds, coord) {
      return bounds.extend(coord);
    }, new mapboxgl.LngLatBounds(coordinates[0], coordinates[0]));
  
    map.fitBounds(bounds, {
      padding: 20,
      duration: 2000
    });
  
    map.addSource('lines', {
      "type": "geojson",
      "data": linesData
    });
  
    map.addLayer({
      "id": "route",
      "source": "lines",
      "type": "line",
      "paint": {
        "line-width": 2,
        "line-color": "#007cbf"
      }
    });
    // }).catch(function(err) {
    //console.log("error: " + err);
  //}); 
  
  document.getElementById('addCar').addEventListener('click', function() {
  addCar();
  });
  
  
  });
  
  
  requestAnimationFrame(animate);</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code> body {
     margin: 0;
     padding: 0;
   }
   
   #map {
     position: absolute;
     top: 0;
     bottom: 0;
     width: 100%;
   }
   
   .overlay {
     position: absolute;
     top: 10px;
     left: 10px;
   }
   
   .overlay button {
     font: 600 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
     background-color: #3386c0;
     color: #fff;
     display: inline-block;
     margin: 0;
     padding: 10px 20px;
     border: none;
     cursor: pointer;
     border-radius: 3px;
   }
   
   .overlay button:hover {
     background-color: #4ea0da;
   }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;script src="https://master.fieldtec.com/vendor/custom-component-modules/car_tracking_animation/scripts/turf.min.js"&gt;&lt;/script&gt;
  &lt;link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.37.0/mapbox-gl.css" rel="stylesheet"/&gt;
  &lt;script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.37.0/mapbox-gl.js"&gt;&lt;/script&gt;
  
  &lt;body&gt;
    &lt;div id='map'&gt;&lt;/div&gt;
    &lt;div class='overlay'&gt;
      &lt;button id='addCar'&gt;Add Car&lt;/button&gt;
    &lt;/div&gt;
  &lt;/body&gt;</code></pre>
  </div>
  </div>
  </p>