diff --git a/index.html b/index.html
new file mode 100644
index 0000000..a3b4f7f
--- /dev/null
+++ b/index.html
@@ -0,0 +1,58 @@
+
+
+
+Radar application
+
+
+
+
+
+
+
+
+
+
+
+
Radar application
+
A simple radar application in HTML
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/leaflet-radar.css b/src/leaflet-radar.css
new file mode 100644
index 0000000..c75b683
--- /dev/null
+++ b/src/leaflet-radar.css
@@ -0,0 +1,15 @@
+.leaflet-radar {
+
+ /* from leaflet-control-layers */
+ border-radius: 5px;
+ background: #fff;
+ border: 2px solid rgba(0, 0, 0, 0.2);
+ background-clip: padding-box;
+ color: black;
+ padding: 5px;
+ height: 67.5px;
+}
+
+.leaflet-radar .leaflet-radar-timestamp {
+ text-align: center;
+}
diff --git a/src/leaflet-radar.js b/src/leaflet-radar.js
new file mode 100644
index 0000000..e5664b4
--- /dev/null
+++ b/src/leaflet-radar.js
@@ -0,0 +1,229 @@
+// TODO add refresh (reload time layers)
+// TODO add buffer time to load layers where radar turned on
+
+L.Control.Radar = L.Control.extend({
+
+ NEXRAD_URL: `https://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0q.cgi`,
+ NEXRAD_LAYER: `nexrad-n0q-900913`,
+
+ isPaused: false,
+ timeLayerIndex: 0,
+ timeLayers: [],
+
+ options: {
+ position: `topright`,
+ opacity: 0.575,
+ zIndex: 200,
+ transitionMs: 750,
+ playHTML: `►`,
+ pauseHTML: `▐`,
+ },
+
+ onRemove: function () {
+ L.DomUtil.remove(this.container);
+ },
+
+ onAdd: function (map) {
+ this.map = map;
+
+ // setup control container
+ this.container = L.DomUtil.create(`div`, "leaflet-radar");
+
+ L.DomEvent.disableClickPropagation(this.container);
+ L.DomEvent.on(this.container, `control_container`, function (e) {
+ L.DomEvent.stopPropagation(e);
+ });
+ L.DomEvent.disableScrollPropagation(this.container);
+
+ // add control elements within container
+ checkbox_div = L.DomUtil.create(
+ `div`,
+ `leaflet-radar-toggle`,
+ this.container
+ );
+
+ this.checkbox = document.createElement(`input`);
+ this.checkbox.id = `leaflet-radar-toggle`;
+ this.checkbox.type = `checkbox`;
+ this.checkbox.checked = false;
+ this.checkbox.onclick = () => this.toggle();
+
+ checkbox_div.appendChild(this.checkbox);
+
+ let checkbox_label = document.createElement(`span`);
+ checkbox_label.innerText = `Radar`;
+
+ checkbox_div.appendChild(checkbox_label);
+
+ let slider_div = L.DomUtil.create(
+ `div`,
+ `leaflet-radar-slider`,
+ this.container
+ );
+
+ this.slider = document.createElement(`input`);
+ this.slider.id = `leaflet-radar-slider`;
+ this.slider.type = `range`;
+ this.slider.min = 0;
+
+ slider_div.appendChild(this.slider);
+
+ this.timestamp_div = L.DomUtil.create(
+ `div`,
+ `leaflet-radar-timestamp`,
+ this.container
+ );
+
+ this.setDisabled(true);
+ this.isPaused = true;
+
+ return this.container;
+ },
+
+ hideLayerByIndex: function (index) {
+ this.timeLayers[index].tileLayer.setOpacity(0);
+ this.timestamp_div.innerHTML = ``;
+ },
+
+ showLayerByIndex: function (index) {
+ this.timeLayers[index].tileLayer.setOpacity(
+ this.options.opacity
+ );
+ this.timestamp_div.innerHTML = this.timeLayers[index].timestamp;
+ },
+
+ setDisabled: function (disabled) {
+ this.slider.disabled = disabled;
+ this.timestamp_div.innerText = ``;
+ },
+
+ toggle: function () {
+ if (!this.checkbox.checked) {
+ this.setDisabled(true);
+ this.removeLayers();
+ return;
+ }
+
+ this.setDisabled(false);
+
+ this.timeLayers = this.generateLayers();
+ this.addLayers(this.timeLayers);
+
+ this.slider.max = `${this.timeLayers.length - 1}`;
+
+ this.timeLayerIndex = 0;
+
+ this.isPaused = false;
+
+ this.slider.oninput = () => {
+
+ this.hideLayerByIndex(this.timeLayerIndex);
+ this.timeLayerIndex = +this.slider.value;
+ this.showLayerByIndex(this.timeLayerIndex);
+
+ this.isPaused = true;
+ };
+
+ this.setTransitionTimer();
+ },
+
+
+ setTransitionTimer: function () {
+ setTimeout(() => {
+ if (this.isPaused) {
+ return;
+ }
+
+ this.timeLayers.forEach(timeLayer => {
+ timeLayer.tileLayer.setOpacity(0);
+ timeLayer.tileLayer.addTo(this.map);
+ });
+
+ if (this.checkbox.checked) {
+
+ this.hideLayerByIndex(this.timeLayerIndex);
+ this.incrementLayerIndex();
+ this.showLayerByIndex(this.timeLayerIndex);
+
+ this.slider.value = `${this.timeLayerIndex}`;
+
+ this.setTransitionTimer();
+ } else {
+ this.setDisabled(true);
+ this.removeLayers();
+ }
+ }, this.options.transitionMs);
+ },
+
+ incrementLayerIndex: function () {
+ this.timeLayerIndex++;
+ if (this.timeLayerIndex > this.timeLayers.length - 1) {
+ this.timeLayerIndex = 0;
+ }
+ },
+
+ addLayers: function () {
+ this.timeLayers.forEach(timeLayer => {
+ timeLayer.tileLayer.setOpacity(0);
+ timeLayer.tileLayer.addTo(this.map);
+ });
+ },
+
+ removeLayers: function () {
+ this.timeLayers.forEach(timeLayer =>
+ timeLayer.tileLayer.removeFrom(this.map)
+ );
+ this.timeLayers = [];
+ this.timeLayerIndex = 0;
+ },
+
+ generateLayers: function () {
+ let timeLayers = [];
+
+ const TOTAL_INTERVALS = 10;
+ const INTERVAL_LENGTH_HRS = 5;
+
+ const currentTime = new Date();
+
+ for (let i = 0; i <= TOTAL_INTERVALS; i++) {
+
+ const timeDiffMins =
+ TOTAL_INTERVALS * INTERVAL_LENGTH_HRS -
+ INTERVAL_LENGTH_HRS * i;
+
+ const suffix = (function(time) {
+ switch(time) {
+ case 0:
+ return '';
+ case 5:
+ return '-m05m';
+ default:
+ return '-m' + time + 'm';
+ }
+ })(timeDiffMins);
+
+ const layerRequest = this.NEXRAD_LAYER + suffix;
+
+ const layer = L.tileLayer.wms(this.NEXRAD_URL, {
+ layers: layerRequest,
+ format: `image/png`,
+ transparent: true,
+ opacity: this.options.opacity,
+ zIndex: this.options.zIndex
+ });
+
+ const timeString = new Date(
+ currentTime.valueOf() - timeDiffMins * 60 * 1000
+ ).toLocaleTimeString();
+ timeLayers.push({
+ timestamp: `${timeString} (-${timeDiffMins} min)`,
+ tileLayer: layer
+ });
+ }
+ return timeLayers;
+ }
+});
+
+L.control.radar = function (options) {
+ return new L.Control.Radar(options);
+};
\ No newline at end of file