<div id='map'></div>
body {
  margin: 0;
  padding: 0;
}

#map {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 100%;
}
#pause {
  width: inherit;
  color: black;
  text-align: center;
}
.pause-ctrl {
  width: 100px;
}
class PauseControl {
  onAdd(map) {
    this._map = map;
    this._container = document.createElement("div");
    this._container.className = "mapboxgl-ctrl mapboxgl-ctrl-group pause-ctrl";
    this._container.innerHTML = "<button id='pause'></button>";
    return this._container;
  }

  onRemove() {
    this._container.parentNode.removeChild(this._container);
    this._map = undefined;
  }
}

const imagePrefix = 'https://blog-public-files.s3.us-west-2.amazonaws.com/Hongqiao_railway_station/images/';

Train.load_railway_data(raw_railway_data);
Train.load_railway_path_data(railway_path_data);

trains = [];
trains_info.forEach((t) => {
  var train = new Train(t);
  if (train.legal) {
    trains.push(train);
  }
});

mapboxgl.accessToken =
  "pk.eyJ1IjoiYnJhbnpoYW5nIiwiYSI6ImNrM2U2dHh0ejE2YngzaXFlcjFvdG96b2EifQ.KSQQ0l2qGa1-nrEKn3YKPw";
var map = new mapboxgl.Map({
  container: "map",
  style: "mapbox://styles/branzhang/ck3e2bq8g1pj21cquzpiuhwmc",
  center: [121.315625, 31.196169],
  zoom: 16.5,
  minZoom: 13,
  maxZoom: 18
});
map.addControl(new mapboxgl.FullscreenControl());
map.addControl(new PauseControl(), "top-left");

// Set the map's max bounds.
map.setMaxBounds([
  [121.115625, 30.996169], // [west, south]
  [121.515625, 31.396169] // [east, north]
]);

// to store and cancel the animation
var animation;
var startTime = new Date(2019, 0, 1, 18, 3, 0);
var currentTime = startTime;
var endTime = new Date(2019, 0, 1, 18, 40, 0);
// 以 N 倍速播放
var speed = 5;
var resetTime = false;
var last_timestamp;
var timeDelay = 0;
var playing = true;

var pauseButton;

map.on("load", function () {
  pauseButton = document.getElementById("pause");

  var imageSource = {
    body: "body.png",
    high_head: "high_head.png",
    high_tail: "high_tail.png",
    normal_head: "normal_head.png",
    normal_tail: "normal_tail.png"
  };

  for (var imageName in imageSource) {
    (function (tmp) {
      map.loadImage(imagePrefix + imageSource[tmp], function (error, image) {
        if (error) {
          throw error;
        }
        map.addImage(tmp, image);
      });
    })(imageName);
  }

  map.addSource("train_source", {
    type: "geojson",
    data: {
      type: "FeatureCollection",
      features: []
    }
  });

  map.addLayer(
    {
      id: "train_layer",
      type: "symbol",
      source: "train_source",
      layout: {
        "icon-image": ["get", "train_type"],
        "icon-allow-overlap": true,
        "icon-ignore-placement": true,
        "icon-rotation-alignment": "map",
        "icon-rotate": ["get", "icon_rotate"],
        "icon-size": [
          "interpolate",
          ["linear"],
          ["zoom"],
          13,
          0.2,
          16,
          0.5,
          17,
          0.9,
          18.2,
          2
        ]
      },
      paint: {}
    },
    "bridge-pedestrian-case"
  );

  animateTrain();

  pauseButton.addEventListener("click", function () {
    if (playing) {
      cancelAnimationFrame(animation);
      pauseButton.innerHTML =
        "Play<br>" + currentTime.toLocaleTimeString("en-US");
    } else {
      resetTime = true;
      animateTrain();
    }
    playing = !playing;
  });

  function animateTrain(timestamp) {
    if (timestamp !== undefined) {
      if (resetTime) {
        timeDelay = timeDelay + timestamp - last_timestamp;
        resetTime = false;
      }

      timeChange =
        ((timestamp - timeDelay) * speed) %
        (endTime.getTime() - startTime.getTime());

      currentTime = new Date(startTime.getTime() + timeChange);
      last_timestamp = timestamp;

      pauseButton.innerHTML =
        "Pause<br>" + currentTime.toLocaleTimeString("en-US");
    }

    var updatedFeatures = [];

    trains.forEach((t) => {
      var tmp = t.getRealTimeLocation(currentTime);
      updatedFeatures.push.apply(updatedFeatures, tmp);
    });

    map.getSource("train_source").setData({
      type: "FeatureCollection",
      features: updatedFeatures
    });

    if (currentTime >= endTime) {
      console.log("one turn end.");
    }

    animation = requestAnimationFrame(animateTrain);
  }
});

External CSS

  1. https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.css

External JavaScript

  1. https://blog-public-files.s3.us-west-2.amazonaws.com/Hongqiao_railway_station/railway_data.js
  2. https://blog-public-files.s3.us-west-2.amazonaws.com/Hongqiao_railway_station/railway_path_data.js
  3. https://blog-public-files.s3.us-west-2.amazonaws.com/Hongqiao_railway_station/trains_info_data.js
  4. https://blog-public-files.s3.us-west-2.amazonaws.com/Hongqiao_railway_station/train.js
  5. https://api.mapbox.com/mapbox-gl-js/v2.10.0/mapbox-gl.js