From 9179b14fd4d876ea8997ce39bc0ec3cd4f6a0a73 Mon Sep 17 00:00:00 2001
From: Glenn Vorhes <gavorhes@wisc.edu>
Date: Tue, 3 May 2016 12:58:15 -0500
Subject: [PATCH] set up gulp tasks, porting content

---
 .babelrc                               |   3 -
 css/all-ol-style.less                  |   3 +
 css/glrtoc/glrtoc-opsmap.less          | 132 ++++
 css/glrtoc/img/glrtoc-logo.png         | Bin 0 -> 13466 bytes
 css/glrtoc/img/tops-logo.png           | Bin 0 -> 6816 bytes
 css/itsMap.less                        | 116 +++
 css/legend.less                        | 116 +++
 css/media-control.less                 |  68 ++
 css/npmrds-heatmap.less                | 174 +++++
 css/ol-popup.less                      |  68 ++
 css/peergroup.less                     | 173 +++++
 css/tip-colors.less                    |  30 +
 css/tip-results.less                   | 136 ++++
 css/tip.less                           | 159 ++++
 gulpfile.js                            | 230 ++++++
 lib/jquery.floatThead.js               | 967 +++++++++++++++++++++++++
 package.json                           |  25 +-
 projects/glrtoc/appConfig.js           | 150 ++++
 projects/glrtoc/layerPopups.js         |  37 +
 projects/glrtoc/layerStyles.js         |  39 +
 projects/glrtoc/legendTest.js          | 129 ++++
 projects/glrtoc/main.js                | 319 ++++++++
 projects/glrtoc/mainUi.js              | 105 +++
 projects/itsMap.js                     |  27 +
 projects/npmrds/delay/delay-config.js  |   3 +
 projects/npmrds/delay/delay-main.js    |  32 +
 projects/npmrds/heatmap/appConfig.js   |  50 ++
 projects/npmrds/heatmap/layerStyles.js |  43 ++
 projects/npmrds/heatmap/main-ui.js     | 368 ++++++++++
 projects/npmrds/heatmap/main.js        |  47 ++
 projects/tsmo/TipConfig.js             | 149 ++++
 projects/tsmo/TipSegmentLayer.js       | 191 +++++
 projects/tsmo/legend-test.js           |  84 +++
 projects/tsmo/main-report.js           | 198 +++++
 projects/tsmo/main-ui.js               | 126 ++++
 projects/tsmo/main.js                  | 166 +++++
 projects/tsmo/slider-test.js           |  29 +
 projects/tsmo/tipStyleFunction.js      |  84 +++
 src/collections/ItsLayerCollection.js  |   2 +-
 src/collections/LayerLegend.js         |   2 +-
 src/collections/Sliders.js             |   2 +-
 src/jquery-plugin/dayRange.js          |   2 +-
 src/jquery-plugin/mediaControl.js      |   2 +-
 src/jquery-plugin/rangeChange.js       |   2 +-
 src/jquery-plugin/rpPicker.js          |   2 +-
 src/jquery-plugin/ssaCorridorPicker.js |   2 +-
 src/jquery.js                          |   6 +
 src/layers/LayerBase.js                |   2 +-
 src/layers/LayerBaseVector.js          |   4 +-
 src/layers/LayerBaseVectorEsri.js      |   2 +-
 src/layers/LayerBaseVectorGeoJson.js   |   2 +-
 src/layers/LayerBaseXyzTile.js         |   2 +-
 src/layers/LayerEsriMapServer.js       |   2 +-
 src/layers/LayerItsInventory.js        |   2 +-
 src/layers/LayerRealEarthTile.js       |   2 +-
 src/mixin/RealEarthAnimate.js          |   2 +-
 src/olHelpers/mapMoveCls.js            |   2 +-
 src/olHelpers/mapPopupCls.js           |   2 +-
 src/olHelpers/quickMapBase.js          |   2 +-
 59 files changed, 4799 insertions(+), 25 deletions(-)
 delete mode 100644 .babelrc
 create mode 100644 css/all-ol-style.less
 create mode 100644 css/glrtoc/glrtoc-opsmap.less
 create mode 100644 css/glrtoc/img/glrtoc-logo.png
 create mode 100644 css/glrtoc/img/tops-logo.png
 create mode 100644 css/itsMap.less
 create mode 100644 css/legend.less
 create mode 100644 css/media-control.less
 create mode 100644 css/npmrds-heatmap.less
 create mode 100644 css/ol-popup.less
 create mode 100644 css/peergroup.less
 create mode 100644 css/tip-colors.less
 create mode 100644 css/tip-results.less
 create mode 100644 css/tip.less
 create mode 100644 gulpfile.js
 create mode 100644 lib/jquery.floatThead.js
 create mode 100644 projects/glrtoc/appConfig.js
 create mode 100644 projects/glrtoc/layerPopups.js
 create mode 100644 projects/glrtoc/layerStyles.js
 create mode 100644 projects/glrtoc/legendTest.js
 create mode 100644 projects/glrtoc/main.js
 create mode 100644 projects/glrtoc/mainUi.js
 create mode 100644 projects/itsMap.js
 create mode 100644 projects/npmrds/delay/delay-config.js
 create mode 100644 projects/npmrds/delay/delay-main.js
 create mode 100644 projects/npmrds/heatmap/appConfig.js
 create mode 100644 projects/npmrds/heatmap/layerStyles.js
 create mode 100644 projects/npmrds/heatmap/main-ui.js
 create mode 100644 projects/npmrds/heatmap/main.js
 create mode 100644 projects/tsmo/TipConfig.js
 create mode 100644 projects/tsmo/TipSegmentLayer.js
 create mode 100644 projects/tsmo/legend-test.js
 create mode 100644 projects/tsmo/main-report.js
 create mode 100644 projects/tsmo/main-ui.js
 create mode 100644 projects/tsmo/main.js
 create mode 100644 projects/tsmo/slider-test.js
 create mode 100644 projects/tsmo/tipStyleFunction.js
 create mode 100644 src/jquery.js

diff --git a/.babelrc b/.babelrc
deleted file mode 100644
index c6c9efb..0000000
--- a/.babelrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
-"presets": ["es2015"]
-}
\ No newline at end of file
diff --git a/css/all-ol-style.less b/css/all-ol-style.less
new file mode 100644
index 0000000..41a007c
--- /dev/null
+++ b/css/all-ol-style.less
@@ -0,0 +1,3 @@
+@import (inline) "../node_modules/openlayers/dist/ol.css";
+@import "legend";
+@import "ol-popup";
diff --git a/css/glrtoc/glrtoc-opsmap.less b/css/glrtoc/glrtoc-opsmap.less
new file mode 100644
index 0000000..6e0df65
--- /dev/null
+++ b/css/glrtoc/glrtoc-opsmap.less
@@ -0,0 +1,132 @@
+@import "../all-ol-style";
+@import "../media-control";
+
+
+body, html {
+  height: 100%;
+  width: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+#main-container {
+  height: 100%;
+}
+
+.flex-container {
+  display: flex;
+
+  //> div {
+  //  flex-grow: 1;
+  //}
+}
+
+#map{
+  flex-grow: 1;
+}
+
+
+#sidebar {
+  height: 100%;
+  width: 350px;
+  flex-grow: initial;
+  position: relative;
+}
+
+#logo-container {
+    > div {
+    flex-grow: 1;
+  }
+
+  div {
+    margin: 7px;
+    height: 150px;
+  }
+
+  div:first-child {
+    background: url('img/glrtoc-logo.png') no-repeat center;
+    background-size: contain;
+  }
+
+  div:nth-child(2) {
+    background: url('img/tops-logo.png') no-repeat center;
+    background-size: contain;
+  }
+}
+
+#map {
+  position: relative;
+}
+
+#tabs {
+  height: 100%;
+  padding: 0;
+  border: none;
+  width: 100%;
+
+
+  > div {
+    padding: 0;
+    height: 98%;
+
+    p {
+      text-align: justify;
+      padding: 7px 12px;
+      margin: 10px 0;
+    }
+
+  }
+
+  .ui-tab {
+    padding: 3px 7px !important;
+  }
+}
+
+#operations-tab {
+  h3 {
+    margin-top: 0;
+    padding-top: 5px;
+    padding-bottom: 5px;
+  }
+
+  > div{
+    padding: 0;
+  }
+}
+
+#hide-sidebar, #show-sidebar {
+  z-index: 5;
+  position: absolute;
+  color: white;
+  cursor: pointer;
+}
+
+//hide sidebar span
+#hide-sidebar {
+  right: 4px;
+  top: 9px;
+}
+
+#show-sidebar {
+  display: none;
+  left: -12px;
+  top: 74px;
+  background: linear-gradient(#74BBD8, #3FA3CA);
+  width: 40px;
+  text-align: right;
+  padding: 10px 4px;
+  border-radius: 5px;
+  border: solid #4297D7 1px;
+}
+
+#operations > div {
+  padding: 0;
+}
+
+#animation-control {
+  text-align: center;
+  padding: 10px;
+}
+
+
+
diff --git a/css/glrtoc/img/glrtoc-logo.png b/css/glrtoc/img/glrtoc-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e703e4220e4f9bfc334eb49e95d19b8e9720aefe
GIT binary patch
literal 13466
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
Cmu+?c

literal 0
HcmV?d00001

diff --git a/css/glrtoc/img/tops-logo.png b/css/glrtoc/img/tops-logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..436dbf5a4ff00a9dcea8dd51add6f68b5943c4f7
GIT binary patch
literal 6816
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

literal 0
HcmV?d00001

diff --git a/css/itsMap.less b/css/itsMap.less
new file mode 100644
index 0000000..7cbd153
--- /dev/null
+++ b/css/itsMap.less
@@ -0,0 +1,116 @@
+@import "./all-ol-style";
+
+
+@headerHeight: 80px;
+
+body, html {
+  height: 100%;
+  width: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+#header {
+  height: @headerHeight;
+  background-color: #808080;
+
+  span {
+    display: inline-block;
+    margin: 9px;
+    font-size: 28px;
+    line-height: 30px;
+  }
+
+  a {
+    float: right;
+  }
+
+  img {
+    margin: 3px 10px;
+    max-height: @headerHeight - 10px;
+    width: auto;
+  }
+}
+
+#container {
+  display: flex;
+  height: ~"calc(100% - " @headerHeight ~")";
+  width: 100%;
+}
+
+#left {
+  height: 100%;
+  width: 320px;
+  overflow-y: auto;
+}
+
+#map {
+  flex: 1
+}
+
+.legend-container {
+  border: none;
+}
+
+@media screen and (max-height: 650px) {
+
+}
+
+@media screen and (max-height: 640px), (max-width: 650px) {
+
+  #header a {
+    display: none;
+  }
+
+  #header{
+    display: none;
+  }
+
+  #container {
+    //display: block;
+    height: 100%;
+  }
+
+  #left {
+    width: 246px;
+    height: auto;
+    position: absolute;
+    top: 5px;
+    right: 5px;
+    z-index: 10;
+    background-color: white;
+    -webkit-border-radius:5px;
+    -moz-border-radius:5px;
+    border-radius: 5px;
+    max-height: 250px;
+    overflow-y: scroll;
+  }
+
+  #map{
+    height: 100%;
+  }
+
+  .legend-container ul{
+    padding-left: 5px;
+  }
+
+}
+
+@media screen and (max-height: 500px), (max-width: 346px) {
+  #left{
+    width: 204px;
+    max-height: 204px;
+  }
+
+  .ol-popup{
+    width: 240px;
+    min-width: 240px;
+
+    iframe{
+      width: 240px;
+    }
+  }
+}
+
+
+
diff --git a/css/legend.less b/css/legend.less
new file mode 100644
index 0000000..504f281
--- /dev/null
+++ b/css/legend.less
@@ -0,0 +1,116 @@
+@legendKeyOffset: 7px;
+
+@rectangleImg: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHQAAAATCAMAAAB2tdn3AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAAcHBwoKCgsLCw0NDQ4ODg8PDxAQEBYWFhcXFxsbGxwcHB0dHR4eHh8fHyAgICEhISIiIiQkJCUlJSYmJicnJygoKCkpKSoqKiwsLC0tLS4uLi8vLzAwMDQ0NDU1NTc3Nzg4ODk5OT09PT4+Pj8/P0FBQUJCQkNDQ0REREZGRkdHR0hISElJSUpKSktLS0xMTE1NTU5OTk9PT1FRUVJSUlNTU1RUVFZWVldXV1hYWFlZWVpaWl5eXmBgYGFhYWJiYmRkZGZmZmhoaGlpaWpqam1tbW5ubm9vb3Nzc3V1dXd3d3l5eXx8fH5+fn9/fwCADwGBEQKCEgGJEwKKEwSMFQGRFAKSFAKTFQiXGguUHRifKAynGAipFROjJhukLR+iMSaWNCuVOCGhMSSmND+oTT20SwnQGwzRHRDCJRPDKADjHQDmGgDmGwDlHQHpHwDyFwDzGQD1GAD2GQD3HQD3HgD/HgD/HwLqIAD/IAD/IQD/IkeFTkiFUEiGUFeNXlmPX0SrU0i4VVajX2CpaW2kdF7MbGHPbnyxgoCAgIGBgYKCgoODg4SEhIWFhYiIiImJiYuLi4yMjI6OjpCQkJWVlZiYmJqampubm5ycnImwjI+zkZqomKSkpKampqioqKurq6ysrK2tra6urq+vr6SwogAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4rSb8AAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4wLjOM5pdQAAAB+UlEQVRIS5WV6VfTQBTFA7YgoICsFtmxgLRShQIqCLKDKEFQ3FlERdlxwwTBBQRMKVqQRdSyCH/n803yevj65ndy8iY55957ZpJ5owDAgdceJkVSE6pMWlPoFRN7yS6qMNSbahlIUJdg1dM1VpXAUSRCL7fQoxQx4hZrDiVpvQjK/kkxajyf7Ujj4Eg/V7WBgp1KgMogDg6/9N7pus3iwcC8yDq1p3hEdRX7Aqs8At9rM9tQEglwHMvSo1eaPs1Dnxp+gpINjxKBpfqqX+Xju5mPGlxfsUZD73QZ3nxGjU0JA1h3Gt8kUI16nGo8wAmAxQlyY6K9+A1wTITWuVZUMmShGhUAcWboU7mJ6vrIJ4BwEVpe4ic7Jv7cUGinRmZMtPFZCvWWLpMbk5WcUGgHmbF5/4FCy2RDj2Z6i7y4aOMzFFrr9kt+07JQaN8UuXEZ/UihP/N8ZMdCNRqvh/7ehUky4zK4Lf5eG26cqmsy+9RoL0BNtLVPX8pN9e0z1NgV7L8AhaXLAW5L+tGUJbo1dqQoLF8fv5boSGP9KPnlVoK4TgA1ZzPTWZzJcJavCcUFvP5h/fO85173XRb3H84JZeJfBdz4geQxTxnzrJDlhlOcp66ETXpmc4XO0+RLVuWzlZJnHuKw67GFS5HYYDkANCfTKyY2VxAA/gOxObo6iNgi8wAAAABJRU5ErkJggg==');
+
+
+.legend-container {
+  list-style: none;
+  border: solid black 1px;
+  border-radius: 5px;
+  background-color: rgba(211, 211, 211, 0.2);
+  margin: 2px;
+  padding: 2px;
+
+  ul {
+    list-style: none;
+    padding-left: 17px;
+  }
+
+  li {
+    border-radius: 7px;
+    margin: 2px;
+    padding: 2px 4px;
+  }
+
+  hr {
+    display: inline-block;
+    width: 40px;
+    height: 10px;
+    background-color: blue;
+    border: none;
+    margin: 0 0 3px @legendKeyOffset;
+  }
+
+  > li:first-child {
+    font-weight: bold;
+    min-height: 25px;
+
+    input {
+      display: none;
+    }
+
+    input + label {
+      width: 100px;
+      height: 19px;
+      margin: 5px;
+      display: inline-block;
+    }
+
+    input + label > span {
+      display: inline-block;
+      width: 59px;
+      height: 19px;
+      background: @rectangleImg no-repeat 0 0;
+    }
+
+
+
+    input:checked + label > span {
+      background: @rectangleImg no-repeat -59px 0;
+    }
+  }
+}
+
+
+.legend-layer-subitem {
+  display: inline-block;
+  max-width: 148px;
+  overflow: hidden;
+  max-height: 20px;
+}
+
+.legend-layer-icon {
+  margin-left: @legendKeyOffset;
+}
+
+.legend-layer-div li {
+  margin: 0;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+
+.layer-not-visible {
+  background-color: gray;
+  display: none;
+}
+
+.layer-force-show {
+  display: inherit;
+}
+
+.layer-group-expander, .map-server-expander, .unique-symbol-expander, .legend-items-expander {
+  color: #59AFEE;
+  cursor: pointer;
+  padding-left: 4px;
+  font-size: 14px;
+}
+
+.esri-popup-table{
+  border-collapse: collapse;
+
+  td, th {
+    border: solid black 1px;
+    padding: 0 4px;
+  }
+
+  tr:nth-child(even){
+    background-color: lightblue;
+  }
+}
+
+.legend-layer-name, .legend-check{
+  cursor: pointer
+}
+
+
+
diff --git a/css/media-control.less b/css/media-control.less
new file mode 100644
index 0000000..9233513
--- /dev/null
+++ b/css/media-control.less
@@ -0,0 +1,68 @@
+@mediaImg: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJEAAAAdCAMAAACddH6yAAAABGdBTUEAALGPC/xhBQAAAwBQTFRFAAAAJycmKygmLCknKSkpKSkqLysqLCwsLS0tLi4uLi8vMCwrMi0sMS8tNCwpMTAvNzAvMDAwMTExMTEyMjIxMjIyMzMzMzM0MzQ0NjExNzMxNDQ0NTU1NTY2Njc3OTMxOTc2PDg2OTk5OTo7PTo5Pz4+Pj8/RDs2R0A9REA+QkNDQkRFR0hIS0NBS0VCTUdES0tLTU5NVFRUVVdYYFlXZl9caF9cYWBgZ2Jham1ubGxsc3JycXR2Zn6HaYCIcpSegHx7hIWFjo+PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAptsNGgAAAQB0Uk5T////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////AFP3ByUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuM4zml1AAAAIpSURBVFhH7ZdrV9QwEIYriLDgbQsdUfCKchEVBBVUFOH//6c482a627Qz2VVzjl94PiQznZP02TRNz1bhETnUIYRa4wEbXNzUeEAza+S2xgP4ntWqxhb+tEwYaWARGg0sQvaelQYmT7U32dH+z3mmvUktRuu8jgPecmHLK4qNzCuPZ8A7XR+zeMgF+aFm8ZhHNmxkFrER2MgpfohGmvV4JT5ecR9GzrS70cgZGmC0rEmfnBEXmRVN+sBI3hqLOYz8eWcZjTTpk1ujG6PI3Ebj2+g+ob0vTdfomr7EoCUx+jUh5nMa3UO+iraJO6tr1CxI+xw7eemutKlR72clRjAAnbw10qud4sQIbY3WM6rxbtVkGRGNYwyKGBF9lbtJ4hjxAcVG3NpGRA9jxhQyosf4Ul04RhyykbSeEdFlzLlYxojW8O08qA0jKbT4Ru2jK2bUYhh1lXyj5iJeKGY0xhqNrTVawDLNeGrvYyrFMkZ3sI/OvJ29jp3NVxyjzstWyOgIX3SO/+rtxywtRYzQ5s+jcIQTctEyOo+RUsQIeVx4w2h0C91PtHvSJEYPYj8hMerDlalRn6mRQddoSNdowI0R+H9GTvUNjJzij5zRCYyckZ9zRqfRiF5rnsIFNqKXmqbwYSpGLzRN0dP/iaYpXGQj2tY0hQswosZAruOvp+YJM4tA8wS5nh3ZVNMJhuCE90i/zT3+YWSVGft98gAMPuL8d/iWKx7nilfhN6Q1MB5pcaF/AAAAAElFTkSuQmCC');
+
+.media-control-container {
+  padding: 10px;
+  text-align: center;
+
+  input[type=range]{
+    width: 80%;
+  }
+
+  input[type=range]:disabled{
+      cursor: not-allowed;
+      background-color: lightgray;
+  }
+}
+
+.media-player-button {
+  background: @mediaImg no-repeat;
+  height: 29px;
+  width: 30px;
+  display: inline-block;
+  margin: 0 2px;
+  cursor: pointer;
+}
+
+.media-player-button:hover {
+  background-color: lightblue;
+}
+
+.media-pause {
+  background-position: -86px 0;
+}
+
+.media-play {
+  background-position: -28px 0;
+}
+
+.media-stop {
+  background-position: -57px 0;
+}
+
+.media-ahead {
+  background-position: -116px 0;
+}
+
+.media-back {
+  background-position: 1px 0;
+}
+
+.media-disabled {
+  display: none;
+}
+
+.media-locked{
+  background-color: lightgray !important;
+  cursor: not-allowed;
+}
+
+.media-control-value-label-container{
+  display: flex;
+  justify-content: space-between;
+
+  span{
+    display: block;
+    font-size: small;
+  }
+}
+
diff --git a/css/npmrds-heatmap.less b/css/npmrds-heatmap.less
new file mode 100644
index 0000000..16ffa8b
--- /dev/null
+++ b/css/npmrds-heatmap.less
@@ -0,0 +1,174 @@
+@heatMapControlHeight: 135px;
+@heatMapSliderHeight: 30px;
+@import "./all-ol-style";
+
+
+html, body {
+  height: 100%;
+  width: 100%;
+  margin: 0;
+  /*position: relative;*/
+}
+
+#container {
+  display: flex;
+  height: 100%;
+  width: 100%;
+}
+
+#map {
+  flex: 1;
+}
+
+#left {
+  flex: initial;
+  width: 300px;
+  min-width: 100px;
+
+  > div {
+    margin: 10px;
+  }
+}
+
+#ul-hwy-dirs {
+  list-style: none;
+  padding-left: 5px;
+  max-height: 300px;
+  overflow-y: auto;
+  background-color: lightgrey;
+
+  li {
+    padding: 2px;
+  }
+}
+
+h4 {
+  margin: 10px 5px;
+}
+
+#right {
+  width: 60%;
+  height: 100%;
+
+  input[type=button] {
+    margin: 3px 0;
+  }
+}
+
+#heat-map-control {
+  display: flex;
+  flex-wrap: wrap;
+  height: @heatMapControlHeight;
+
+  > div {
+    margin: 0 5px;
+  }
+
+  ul {
+    list-style: none;
+    padding-left: 0;
+    margin-top: 14px;
+  }
+}
+
+#heat-slider-container {
+  height: @heatMapSliderHeight;
+}
+
+#heat-map-slider {
+  border: none;
+  width: 95%;
+  padding: 0;
+  margin: 0 0 0 18px;
+}
+
+#heatmap-canvas {
+  position: absolute;
+  top: 0;
+  left: 22px;
+  cursor: crosshair;
+}
+
+#heat-slider-container, #canvas-container {
+  width: 100%;
+  position: relative;
+}
+
+#canvas-container {
+  height: ~"calc(100% - " @heatMapControlHeight + @heatMapSliderHeight ~")";
+  overflow-y: auto;
+}
+
+#heat-map-vertical-bar {
+  position: absolute;
+  /*z-index: 4;*/
+  border-right: solid blue 2px;
+  left: 22px;
+  top: 0;
+  width: 0;
+  height: 0;
+  background-color: rgba(100, 100, 100, 0.3);
+}
+
+#date-ul {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  position: absolute;
+  left: 0;
+  top: 0;
+  z-index: -5;
+
+  li {
+    border-bottom: solid #000000 1px;
+    height: 575px;
+  }
+
+  li:nth-child(odd) {
+    background-color: lightblue;
+  }
+
+  li div {
+    color: blue;
+    height: 150px;
+    transform: rotate(-90deg) translate(-259px, 0px);
+    transform-origin: left top 0;
+    float: left;
+  }
+}
+
+#canvas-tooltip-span {
+  display: none;
+  z-index: 100;
+  position: fixed;
+  background-color: lightgrey;
+  border: solid black 1px;
+  border-radius: 5px;
+  padding: 5px;
+  /*height: 50px;*/
+  width: 160px;
+  overflow: hidden;
+  text-align: center;
+}
+
+#progress-indicator-div {
+  z-index: 200;
+  background-color: lightblue;
+  border: solid darkslategray 1px;
+  border-radius: 5px;
+  width: 200px;
+  padding: 10px;
+  margin: 20px;
+  display: none;
+}
+
+#sel-hwy {
+  padding-left: 14px;
+  padding-top: 9px;
+  font-weight: bold;
+  display: inline-block;
+}
+
+/*.ui-datepicker{*/
+/*z-index: 100 !important;*/
+/*}*/
\ No newline at end of file
diff --git a/css/ol-popup.less b/css/ol-popup.less
new file mode 100644
index 0000000..8e13d3e
--- /dev/null
+++ b/css/ol-popup.less
@@ -0,0 +1,68 @@
+.ol-popup {
+  position: absolute;
+  background-color: white;
+  -webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
+  filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
+  padding: 22px 5px 5px 5px;
+  border-radius: 10px;
+  border: 1px solid #cccccc;
+  bottom: 12px;
+  left: -50px;
+  min-width: 280px;
+  cursor: default;
+}
+
+.ol-popup:after, .ol-popup:before {
+  top: 100%;
+  border: solid transparent;
+  content: " ";
+  height: 0;
+  width: 0;
+  position: absolute;
+  pointer-events: none;
+}
+
+.ol-popup:after {
+  border-top-color: white;
+  border-width: 10px;
+  left: 48px;
+  margin-left: -10px;
+}
+
+.ol-popup:before {
+  border-top-color: #cccccc;
+  border-width: 11px;
+  left: 48px;
+  margin-left: -11px;
+}
+
+.ol-popup-closer {
+  text-decoration: none;
+  position: absolute;
+  top: 2px;
+  right: 8px;
+}
+
+.ol-popup-closer:after {
+  content: "X";
+}
+
+.ol-popup-nav {
+  background-color: lightgray;
+  padding: 3px;
+  margin-bottom: 7px;
+}
+
+.ol-popup-nav-arrow{
+  color: #419CC4;
+  cursor: pointer;
+  margin: 0 3px;
+  font-size: larger;
+}
+
+.ol-inner-inner {
+  height: 300px;
+  //width: 392px;
+  //padding-right: 25px;
+  overflow-y: auto;
+}
diff --git a/css/peergroup.less b/css/peergroup.less
new file mode 100644
index 0000000..e0fb5e7
--- /dev/null
+++ b/css/peergroup.less
@@ -0,0 +1,173 @@
+@import './all-ol-style';
+
+@sidebarWidth: 280px;
+
+html, body {
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  width: 100%;
+}
+
+#app-container {
+  display: flex;
+  height: 100%;
+
+  > div {
+    height: 100%;
+    overflow-y: auto;
+  }
+}
+
+#left-sidebar {
+  width: @sidebarWidth;
+
+  label {
+    display: inline-block;
+    margin-left: 4px;
+  }
+
+  h6 {
+    margin-top: 5px;
+    margin-bottom: 2px;
+  }
+}
+
+#accordion {
+  font-size: 0.9em !important;
+
+  h3 {
+    padding-top: 1px;
+    padding-bottom: 1px;
+    margin-bottom: 2px;
+  }
+}
+
+#right-sidebar {
+  width: 400px;
+}
+
+#map {
+  flex: 1;
+}
+
+#dialog{
+  display: none;
+}
+//
+.ui-accordion-content {
+  padding: 5px 10px !important;
+}
+
+select {
+  margin: 4px;
+  padding: 0;
+}
+
+input[type=text] {
+  margin: 2px 7px;
+  padding: 5px;
+}
+
+.divUnd {
+  width: 100px;
+
+}
+
+.equality-compare {
+  width: 45px;
+}
+
+.class-adtVal {
+  width: 90px;
+}
+
+.class-laneVal, .class-speedVal {
+  width: 50px;
+}
+
+.comma-list {
+  width: 250px;
+
+}
+
+#summary {
+  border-collapse: collapse;
+}
+
+#summary tr {
+  height: 30px;
+}
+
+#summary th, td {
+  border: 1px solid black;
+  padding: 1px 4px;
+  text-align: center;
+  //color: #636363;
+}
+
+.invalid {
+  border: solid red 1px !important;
+}
+
+.popup-table {
+  border-collapse: collapse;
+  margin-top: 5px;
+
+}
+
+.popup-table th, td {
+  border: 1px solid black;
+}
+
+//
+//.ol-popup {
+//    display: none;
+//    position: absolute;
+//    background-color: white;
+//    -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+//    -webkit-filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
+//    filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.2));
+//    padding: 15px;
+//    border-radius: 10px;
+//    border: 1px solid #cccccc;
+//    bottom: 12px;
+//    left: -50px;
+//}
+//
+//.ol-popup:after, .ol-popup:before {
+//    top: 100%;
+//    border: solid transparent;
+//    content: " ";
+//    height: 0;
+//    width: 0;
+//    position: absolute;
+//    pointer-events: none;
+//}
+//
+//.ol-popup:after {
+//    border-top-color: white;
+//    border-width: 10px;
+//    left: 48px;
+//    margin-left: -10px;
+//}
+//
+//.ol-popup:before {
+//    border-top-color: #cccccc;
+//    border-width: 11px;
+//    left: 48px;
+//    margin-left: -11px;
+//}
+//
+//.ol-popup-closer {
+//    text-decoration: none;
+//    position: absolute;
+//    top: 2px;
+//    right: 8px;
+//}
+//
+//.ol-popup-closer:after {
+//    content: "✖";
+//}
+//
+//
diff --git a/css/tip-colors.less b/css/tip-colors.less
new file mode 100644
index 0000000..669e4c5
--- /dev/null
+++ b/css/tip-colors.less
@@ -0,0 +1,30 @@
+.cell-min {
+    background-color: #00ff00 !important;
+}
+
+.cell-very-low {
+    background-color: #55ff00  !important;
+}
+
+.cell-low {
+    background-color: #aaff00  !important;
+}
+
+.cell-mid {
+    background-color: #ffff00  !important;
+}
+
+.cell-high {
+    background-color: #ffaa00  !important;
+}
+
+.cell-very-high {
+    background-color: #ff5500  !important;
+    font-weight: bold;
+}
+
+.cell-max {
+    background-color: #ff0000  !important;
+    font-weight: bold;
+    text-decoration: underline;
+}
\ No newline at end of file
diff --git a/css/tip-results.less b/css/tip-results.less
new file mode 100644
index 0000000..8a04f94
--- /dev/null
+++ b/css/tip-results.less
@@ -0,0 +1,136 @@
+@import "tip-colors";
+@import "./all-ol-style";
+
+body, html, #container {
+  height: 100%;
+  width: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+#container, #summary {
+  display: flex;
+}
+
+#summary {
+  height: 38%;
+
+  > div {
+    flex-grow: 1;
+    height: 100%;
+    overflow-y: auto;
+  }
+}
+
+#crash-data {
+  table {
+    border-collapse: collapse;
+    margin: 7px 4px 0 4px;
+  }
+
+  th, td {
+    border: solid black 1px;
+    text-align: center;
+    padding: 0 5px;
+  }
+}
+
+#right {
+  flex: 1;
+}
+
+#map {
+  height: 60%;
+  position: relative;
+}
+
+#legend-container {
+  position: absolute;
+  width: 250px;
+  max-height: 80%;
+  min-height: 30px;
+  overflow-y: auto;
+  background-color: rgba(255, 255, 255, 0.8);
+  z-index: 10;
+  top: 5px;
+  right: 5px;
+  border-radius: 7px;
+
+  > ul {
+    > li:first-child {
+      display: none;
+    }
+  }
+}
+
+#summary {
+  h3, h4, ul {
+    margin: 5px 10px;
+  }
+}
+
+#results-container {
+  width: 936px;
+  padding-right: 20px;
+  height: 100%;
+  overflow: auto;
+  position: relative;
+  table {
+    border-collapse: collapse;
+    margin: 4px;
+    width: 100%;
+    //height: 100%;
+    position: relative;
+    table-layout: fixed;
+
+    th, td {
+      border: solid black 1px;
+      padding: 3px;
+      text-align: center;
+    }
+
+    > *{
+      width: 100%;
+    }
+
+    th {
+      cursor: pointer;
+    }
+
+    thead {
+
+      display: block;
+      width: 100%;
+
+      tr {
+        background-color: #4a4a4a;
+        color: #FDFDFD;
+        width: 100%;
+      }
+    }
+
+    tbody {
+      display: block;
+      height: 400px;
+      overflow-y: auto;
+      width: 100%;
+    }
+  }
+}
+
+.selectable-row:hover {
+  background-color: #00ffd0;
+  cursor: pointer;
+}
+
+.selected-row {
+  background-color: #0074ff !important;
+}
+
+.score-under-one {
+  display: none;
+}
+
+#show-all-toggle, #show-all-toggle-label {
+  cursor: pointer;
+}
diff --git a/css/tip.less b/css/tip.less
new file mode 100644
index 0000000..09ac1b5
--- /dev/null
+++ b/css/tip.less
@@ -0,0 +1,159 @@
+@import "./tip-colors";
+@import "./all-ol-style";
+
+html, body {
+  width: 100%;
+  height: 100%;
+  margin: 0;
+}
+
+#container {
+  display: flex;
+  height: 100%;
+  width: 100%;
+}
+
+#left {
+  overflow-y: scroll;
+  background-color: lightblue;
+  height: 100%;
+  width: 320px;
+}
+
+// suppress first li in legend
+.legend-container {
+  > li:first-child {
+    display: none;
+  }
+}
+
+//tip legend style
+#tip-segments-layer-li {
+  display: inherit;
+
+  > div {
+    height: 80px;
+
+    hr {
+      height: 4px;
+    }
+
+    ul:first-of-type {
+      float: right;
+      padding-left: 0;
+
+      li {
+        height: 10px;
+      }
+    }
+
+    ul:last-of-type {
+      float: right;
+      margin-top: 5px;
+
+      li {
+        margin: 3px 0;
+        text-align: right;
+        border-right: solid black 1px;
+        border-radius: 0;
+      }
+    }
+  }
+}
+
+//common border styling of legend and preset container
+#preset-wrapper, .legend-container {
+  border: solid black 1px;
+  border-radius: 5px;
+}
+
+#preset-wrapper {
+  margin: 3px;
+  padding: 3px;
+  border: solid black 1px;
+  border-radius: 5px;
+
+  h4 {
+    display: inline-block;
+    margin-top: 0;
+    margin-bottom: 0;
+  }
+
+  select {
+    width: 141px;
+  }
+
+  input[type=button] {
+    width: 88px;
+    margin: 0;
+  }
+}
+
+.slider-container {
+
+  > div {
+    padding: 2px;
+    border-bottom: solid darkblue 1px;
+    //min-height: 46px;
+  }
+
+  label {
+    display: inline-block;
+  }
+
+  .slider-label {
+    width: 97px;
+  }
+
+  .hidden-select {
+    display: none;
+  }
+
+  .percent-label {
+    padding: 2px 3px;
+    margin-left: 2px;
+    background-color: lightgray;
+    font-weight: bold;
+    color: orangered;
+  }
+
+  input[type=range] {
+    width: 153px;
+    padding: 1px;
+
+  }
+
+  input[type=range][disabled] {
+    background-color: #59afee;
+    cursor: not-allowed;
+  }
+
+  select {
+    margin-bottom: 2px;
+    width: 46px;
+  }
+
+  .low-high {
+    font-size: 12px;
+  }
+}
+
+// offset around show help button
+#tip-help {
+  margin-left: 10px;
+  margin-top: 4px;
+}
+
+#map {
+  background-color: slategray;
+  height: 100%;
+  flex: 1;
+  position: relative;
+}
+
+#loading-gif {
+  left: 10px;
+  top: 80px;
+  z-index: 10;
+  position: absolute;
+}
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..8ba80e8
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,230 @@
+const gulp = require('gulp');
+const babel = require('gulp-babel');
+const babelify = require('babelify');
+const browserify = require('browserify');
+const buffer = require('vinyl-buffer');
+const source = require('vinyl-source-stream');
+const sourcemaps = require('gulp-sourcemaps');
+const minify = require('gulp-minify');
+const less = require('gulp-less');
+const cssmin = require('gulp-cssmin');
+const rename = require('gulp-rename');
+
+
+/**
+ *
+ * @param {string} inputFile - input file
+ * @param {string} outputFile - output file
+ * @param {boolean|*} runMinify - if should minify
+ * @returns {object} return stream
+ */
+function processJsFile(inputFile, outputFile, runMinify) {
+    "use strict";
+    runMinify = typeof runMinify == 'boolean' ? runMinify : false;
+    let pathParts = outputFile.split('/');
+    let outFileName = pathParts[pathParts.length - 1];
+    pathParts.splice(pathParts.length - 1, 1);
+    let outDir = pathParts.length === 0 ? '.' : pathParts.join('/');
+
+    let bundler = browserify({entries: inputFile, "debug": true, "extensions": ["js"]});
+
+    bundler.transform(babelify.configure({
+        presets: ["es2015"],
+        "ignore": /custom-ol-build|jquery.min/
+    }));
+
+
+    //bundler.transform(babelify.configure({
+    //    ignore: /custom-ol-build|jquery.min/
+    //}));
+    //let bundler = browserify({entries: inputFile, extensions: ['.js'], debug: true})
+    //    .transform(babelify.configure({
+    //        ignore: /custom-ol-build.js|jquery.min|/,
+    //        presets: ["es2015"],
+    //        extensions: [".ts", ".js"]
+    //    }));
+
+
+    //bundler.transform("babelify", {presets: ["es2015"], ignore: /custom-ol-build.js|jquery.min|/}, extensions: ['.js', '.ts']);
+
+
+    if (runMinify) {
+        return bundler.bundle()
+            .on('error', function (err) {
+                console.error(err);
+            })
+            .pipe(source(outFileName))
+            .pipe(buffer())
+            .pipe(sourcemaps.init({loadMaps: true}))
+            .pipe(minify({
+                ext: {
+                    src: '-debug.js',
+                    min: '.js'
+                },
+                exclude: ['tasks'],
+                ignoreFiles: ['.combo.js', '-min.js']
+            }))
+            .pipe(sourcemaps.write('./'))
+            .pipe(gulp.dest(outDir));
+    } else {
+        return bundler.bundle()
+            .on('error', function (err) {
+                console.error(err);
+            })
+            .pipe(source(outFileName))
+            .pipe(buffer())
+            .pipe(sourcemaps.init({loadMaps: true}))
+            .pipe(sourcemaps.write('./'))
+            .pipe(gulp.dest(outDir));
+    }
+}
+
+function processLessFile(inputFile, outputFile) {
+    "use strict";
+    let pathParts = outputFile.split('/');
+    let outFileName = pathParts[pathParts.length - 1];
+    pathParts.splice(pathParts.length - 1, 1);
+    let outDir = pathParts.length === 0 ? '.' : pathParts.join('/');
+
+    let fileNameParts = outFileName.split('.');
+
+    return gulp.src(inputFile)
+        .pipe(less().on('error', function (err) {
+            console.log(err);
+        }))
+        .pipe(cssmin().on('error', function (err) {
+            console.log(err);
+        }))
+        .pipe(rename({
+            basename: fileNameParts[0],
+            extname: '.' + fileNameParts[1],
+            suffix: '.min'
+        }))
+        .pipe(gulp.dest(outDir));
+}
+
+//gulp.task('default', function () {
+//    "use strict";
+//    return gulp.src('src/app.js')
+//        .pipe(babel())
+//        .pipe(gulp.dest('dist'))
+//});
+
+function _itsInventory(doMinify) {
+    "use strict";
+    //processLessFile('./flaskApp/blueprints/its_inventory/static/css/itsMap.less', './flaskApp/blueprints/its_inventory/static/_build/itsMap.css');
+
+    return processJsFile('./projects/itsMap.js', './build/itsMap.js', doMinify);
+}
+
+gulp.task('itsInventory-dev', () => {
+    return _itsInventory(false);
+});
+
+gulp.task('itsInventory-prod', () => {
+    return _itsInventory(true);
+});
+
+function _glrtoc(doMinify) {
+    "use strict";
+    processJsFile('./projects/glrtoc/main.js', './build/glrtoc/main.js', doMinify);
+
+    return processJsFile('./projects/glrtoc/legendTest.js', './build/glrtoc/legendTest.js', doMinify);
+}
+
+gulp.task('glrtoc-dev', () => {
+    "use strict";
+
+    return _glrtoc(false);
+});
+
+gulp.task('glrtoc-prod', () => {
+    "use strict";
+
+    return _glrtoc(true);
+});
+
+function _tsmo(doMinify) {
+    "use strict";
+    processJsFile('./projects/tsmo/legend-test.js', './build/legend-test.js', doMinify);
+    //processJsFile('./projects/tsmo/slider-test.js', './build/slider-test.js', doMinify);
+    //processJsFile('./projects/tsmo/main.js', './build/main.js', doMinify);
+    //return processJsFile('./projects/tsmo/main-report.js', './build/main-report.js', doMinify);
+}
+
+gulp.task('tsmo-dev', () => {
+    "use strict";
+
+    return _tsmo(false);
+});
+
+gulp.task('tsmo-prod', () => {
+    "use strict";
+
+    return _tsmo(true);
+});
+
+function _npmrds(doMinify) {
+    "use strict";
+
+    return processJsFile('./flaskApp/blueprints/npmrds/static/js/heatmap/main.js', './flaskApp/blueprints/npmrds/static/_build/heatmap-main.js', doMinify);
+}
+
+gulp.task('npmrds-dev', () => {
+    "use strict";
+
+    return _npmrds(false);
+});
+
+gulp.task('npmrds-prod', () => {
+    "use strict";
+
+    return _npmrds(true);
+});
+
+function _ssa(doMinify) {
+    "use strict";
+    processLessFile('./flaskApp/blueprints/testing/static/css/ssa-corridor.less', './flaskApp/blueprints/testing/static/_build/ssa-corridor.css');
+
+    return processJsFile('./flaskApp/blueprints/testing/static/js/ssa-main.js', './flaskApp/blueprints/testing/static/_build/ssa-main.js', doMinify);
+}
+
+gulp.task('ssa-dev', () => {
+    "use strict";
+
+    return _ssa(false);
+});
+
+gulp.task('ssa-prod', () => {
+    "use strict";
+
+    return _ssa(true);
+});
+
+gulp.task('peerGroup-dev', () => {
+    "use strict";
+
+    return processJsFile('./flaskApp/blueprints/peerGroup/static/js/main.js', './flaskApp/blueprints/peerGroup/static/_build/main.js', false);
+});
+
+gulp.task('peerGroup-prod', () => {
+    "use strict";
+
+    return processJsFile('./flaskApp/blueprints/peerGroup/static/js/main.js', './flaskApp/blueprints/peerGroup/static/_build/main.js', true);
+});
+
+function _buildTestApps() {
+    "use strict";
+    //processJsFile('./flaskApp/blueprints/testing/static/js/test-custom-build.js', './flaskApp/blueprints/testing/static/_build/test-custom-build.js', false);
+
+    return processJsFile('./flaskApp/blueprints/testing/static/js/test-corridor-layer.js', './flaskApp/blueprints/testing/static/_build/test-corridor-layer.js', false);
+
+}
+
+gulp.task('buildTestApps', () => {
+    "use strict";
+
+    return _buildTestApps(false);
+});
+
+gulp.task('build-prod', ['glrtoc-prod', 'tsmo-prod', 'npmrds-prod', 'ssa-prod', 'itsInventory-prod', 'peerGroup-prod']);
diff --git a/lib/jquery.floatThead.js b/lib/jquery.floatThead.js
new file mode 100644
index 0000000..42eba92
--- /dev/null
+++ b/lib/jquery.floatThead.js
@@ -0,0 +1,967 @@
+// @preserve jQuery.floatThead 1.3.2 - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2015 Misha Koryak
+// @license MIT
+
+/* @author Misha Koryak
+ * @projectDescription lock a table header in place while scrolling - without breaking styles or events bound to the header
+ *
+ * Dependencies:
+ * jquery 1.9.0 + [required] OR jquery 1.7.0 + jquery UI core
+ *
+ * http://mkoryak.github.io/floatThead/
+ *
+ * Tested on FF13+, Chrome 21+, IE8, IE9, IE10, IE11
+ *
+ */
+let jQuery = require('jQuery');
+
+(function( $ ) {
+  /**
+   * provides a default config object. You can modify this after including this script if you want to change the init defaults
+   * @type {Object}
+   */
+  $.floatThead = $.floatThead || {};
+  $.floatThead.defaults = {
+    headerCellSelector: 'tr:visible:first>*:visible', //thead cells are this.
+    zIndex: 1001, //zindex of the floating thead (actually a container div)
+    position: 'auto', // 'fixed', 'absolute', 'auto'. auto picks the best for your table scrolling type.
+    top: 0, //String or function($table) - offset from top of window where the header should not pass above
+    bottom: 0, //String or function($table) - offset from the bottom of the table where the header should stop scrolling
+    scrollContainer: function($table){
+      return $([]); //if the table has horizontal scroll bars then this is the container that has overflow:auto and causes those scroll bars
+    },
+    getSizingRow: function($table, $cols, $fthCells){ // this is only called when using IE,
+      // override it if the first row of the table is going to contain colgroups (any cell spans greater than one col)
+      // it should return a jquery object containing a wrapped set of table cells comprising a row that contains no col spans and is visible
+      return $table.find('tbody tr:visible:first>*:visible');
+    },
+    floatTableClass: 'floatThead-table',
+    floatWrapperClass: 'floatThead-wrapper',
+    floatContainerClass: 'floatThead-container',
+    copyTableClass: true, //copy 'class' attribute from table into the floated table so that the styles match.
+    enableAria: false, //will copy header text from the floated header back into the table for screen readers. Might cause the css styling to be off. beware!
+    autoReflow: false, //(undocumented) - use MutationObserver api to reflow automatically when internal table DOM changes
+    debug: false //print possible issues (that don't prevent script loading) to console, if console exists.
+  };
+
+  var util = window._;
+
+  var canObserveMutations = typeof MutationObserver !== 'undefined';
+
+
+  //browser stuff
+  var ieVersion = function(){for(var a=3,b=document.createElement("b"),c=b.all||[];a = 1+a,b.innerHTML="<!--[if gt IE "+ a +"]><i><![endif]-->",c[0];);return 4<a?a:document.documentMode}();
+  var isFF = /Gecko\//.test(navigator.userAgent);
+  var isWebkit = /WebKit\//.test(navigator.userAgent);
+
+  //safari 7 (and perhaps others) reports table width to be parent container's width if max-width is set on table. see: https://github.com/mkoryak/floatThead/issues/108
+  var isTableWidthBug = function(){
+    if(isWebkit) {
+      var $test = $('<div style="width:0px"><table style="max-width:100%"><tr><th><div style="min-width:100px;">X</div></th></tr></table></div>');
+      $("body").append($test);
+      var ret = ($test.find("table").width() == 0);
+      $test.remove();
+      return ret;
+    }
+    return false;
+  };
+
+  var createElements = !isFF && !ieVersion; //FF can read width from <col> elements, but webkit cannot
+
+  var $window = $(window);
+
+  /**
+   * @param debounceMs
+   * @param cb
+   */
+  function windowResize(eventName, cb){
+    if(ieVersion == 8){ //ie8 is crap: https://github.com/mkoryak/floatThead/issues/65
+      var winWidth = $window.width();
+      var debouncedCb = util.debounce(function(){
+        var winWidthNew = $window.width();
+        if(winWidth != winWidthNew){
+          winWidth = winWidthNew;
+          cb();
+        }
+      }, 1);
+      $window.on(eventName, debouncedCb);
+    } else {
+      $window.on(eventName, util.debounce(cb, 1));
+    }
+  }
+
+
+  function debug(str){
+    window && window.console && window.console.error && window.console.error("jQuery.floatThead: " + str);
+  }
+
+  //returns fractional pixel widths
+  function getOffsetWidth(el) {
+    var rect = el.getBoundingClientRect();
+    return rect.width || rect.right - rect.left;
+  }
+
+  /**
+   * try to calculate the scrollbar width for your browser/os
+   * @return {Number}
+   */
+  function scrollbarWidth() {
+    var $div = $( //borrowed from anti-scroll
+        '<div style="width:50px;height:50px;overflow-y:scroll;'
+        + 'position:absolute;top:-200px;left:-200px;"><div style="height:100px;width:100%">'
+        + '</div>'
+    );
+    $('body').append($div);
+    var w1 = $div.innerWidth();
+    var w2 = $('div', $div).innerWidth();
+    $div.remove();
+    return w1 - w2;
+  }
+  /**
+   * Check if a given table has been datatableized (http://datatables.net)
+   * @param $table
+   * @return {Boolean}
+   */
+  function isDatatable($table){
+    if($table.dataTableSettings){
+      for(var i = 0; i < $table.dataTableSettings.length; i++){
+        var table = $table.dataTableSettings[i].nTable;
+        if($table[0] == table){
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+
+  function tableWidth($table, $fthCells, isOuter){
+    // see: https://github.com/mkoryak/floatThead/issues/108
+    var fn = isOuter ? "outerWidth": "width";
+    if(isTableWidthBug && $table.css("max-width")){
+      var w = 0;
+      if(isOuter) {
+        w += parseInt($table.css("borderLeft"), 10);
+        w += parseInt($table.css("borderRight"), 10);
+      }
+      for(var i=0; i < $fthCells.length; i++){
+        w += $fthCells.get(i).offsetWidth;
+      }
+      return w;
+    } else {
+      return $table[fn]();
+    }
+  }
+  $.fn.floatThead = function(map){
+    map = map || {};
+    if(!util){ //may have been included after the script? lets try to grab it again.
+      util = window._ || $.floatThead._;
+      if(!util){
+        throw new Error("jquery.floatThead-slim.js requires underscore. You should use the non-lite version since you do not have underscore.");
+      }
+    }
+
+    if(ieVersion < 8){
+      return this; //no more crappy browser support.
+    }
+
+    var mObs = null; //mutation observer lives in here if we can use it / make it
+
+    if(util.isFunction(isTableWidthBug)) {
+      isTableWidthBug = isTableWidthBug();
+    }
+
+    if(util.isString(map)){
+      var command = map;
+      var ret = this;
+      this.filter('table').each(function(){
+        var $this = $(this);
+        var opts = $this.data('floatThead-lazy');
+        if(opts){
+          $this.floatThead(opts);
+        }
+        var obj = $this.data('floatThead-attached');
+        if(obj && util.isFunction(obj[command])){
+          var r = obj[command]();
+          if(typeof r !== 'undefined'){
+            ret = r;
+          }
+        }
+      });
+      return ret;
+    }
+    var opts = $.extend({}, $.floatThead.defaults || {}, map);
+
+    $.each(map, function(key, val){
+      if((!(key in $.floatThead.defaults)) && opts.debug){
+        debug("Used ["+key+"] key to init plugin, but that param is not an option for the plugin. Valid options are: "+ (util.keys($.floatThead.defaults)).join(', '));
+      }
+    });
+    if(opts.debug){
+      var v = $.fn.jquery.split(".");
+      if(parseInt(v[0], 10) == 1 && parseInt(v[1], 10) <= 7){
+        debug("jQuery version "+$.fn.jquery+" detected! This plugin supports 1.8 or better, or 1.7.x with jQuery UI 1.8.24 -> http://jqueryui.com/resources/download/jquery-ui-1.8.24.zip")
+      }
+    }
+
+    this.filter(':not(.'+opts.floatTableClass+')').each(function(){
+      var floatTheadId = util.uniqueId();
+      var $table = $(this);
+      if($table.data('floatThead-attached')){
+        return true; //continue the each loop
+      }
+      if(!$table.is('table')){
+        throw new Error('jQuery.floatThead must be run on a table element. ex: $("table").floatThead();');
+      }
+      canObserveMutations = opts.autoReflow && canObserveMutations; //option defaults to false!
+      var $header = $table.children('thead:first');
+      var $tbody = $table.children('tbody:first');
+      if($header.length == 0 || $tbody.length == 0){
+        $table.data('floatThead-lazy', opts);
+        $table.unbind("reflow").one('reflow', function(){
+          $table.floatThead(opts);
+        });
+        return;
+      }
+      if($table.data('floatThead-lazy')){
+        $table.unbind("reflow");
+      }
+      $table.data('floatThead-lazy', false);
+
+      var headerFloated = true;
+      var scrollingTop, scrollingBottom;
+      var scrollbarOffset = {vertical: 0, horizontal: 0};
+      var scWidth = scrollbarWidth();
+      var lastColumnCount = 0; //used by columnNum()
+      var $scrollContainer = opts.scrollContainer($table) || $([]); //guard against returned nulls
+      var locked = $scrollContainer.length > 0;
+
+      var useAbsolutePositioning = null;
+      if(typeof opts.useAbsolutePositioning !== 'undefined'){
+        opts.position = 'auto';
+        if(opts.useAbsolutePositioning){
+          opts.position = opts.useAbsolutePositioning ? 'absolute' : 'fixed';
+        }
+        debug("option 'useAbsolutePositioning' has been removed in v1.3.0, use `position:'"+opts.position+"'` instead. See docs for more info: http://mkoryak.github.io/floatThead/#options")
+      }
+      if(typeof opts.scrollingTop !== 'undefined'){
+        opts.top = opts.scrollingTop;
+        debug("option 'scrollingTop' has been renamed to 'top' in v1.3.0. See docs for more info: http://mkoryak.github.io/floatThead/#options");
+      }
+      if(typeof opts.scrollingBottom !== 'undefined'){
+          opts.bottom = opts.scrollingBottom;
+          debug("option 'scrollingBottom' has been renamed to 'bottom' in v1.3.0. See docs for more info: http://mkoryak.github.io/floatThead/#options");
+      }
+
+
+      if (opts.position == 'auto') {
+        useAbsolutePositioning = null;
+      } else if (opts.position == 'fixed') {
+        useAbsolutePositioning = false;
+      } else if (opts.position == 'absolute'){
+        useAbsolutePositioning = true;
+      } else if (opts.debug) {
+        debug('Invalid value given to "position" option, valid is "fixed", "absolute" and "auto". You passed: ', opts.position);
+      }
+
+      if(useAbsolutePositioning == null){ //defaults: locked=true, !locked=false
+        useAbsolutePositioning = locked;
+      }
+      var $caption = $table.find("caption");
+      var haveCaption = $caption.length == 1;
+      if(haveCaption){
+        var captionAlignTop = ($caption.css("caption-side") || $caption.attr("align") || "top") === "top";
+      }
+
+      var $fthGrp = $('<fthfoot style="display:table-footer-group;border-spacing:0;height:0;border-collapse:collapse;visibility:hidden"/>');
+
+      var wrappedContainer = false; //used with absolute positioning enabled. did we need to wrap the scrollContainer/table with a relative div?
+      var $wrapper = $([]); //used when absolute positioning enabled - wraps the table and the float container
+      var absoluteToFixedOnScroll = ieVersion <= 9 && !locked && useAbsolutePositioning; //on IE using absolute positioning doesn't look good with window scrolling, so we change position to fixed on scroll, and then change it back to absolute when done.
+      var $floatTable = $("<table/>");
+      var $floatColGroup = $("<colgroup/>");
+      var $tableColGroup = $table.children('colgroup:first');
+      var existingColGroup = true;
+      if($tableColGroup.length == 0){
+        $tableColGroup = $("<colgroup/>");
+        existingColGroup = false;
+      }
+      var $fthRow = $('<fthtr style="display:table-row;border-spacing:0;height:0;border-collapse:collapse"/>'); //created unstyled elements (used for sizing the table because chrome can't read <col> width)
+      var $floatContainer = $('<div style="overflow: hidden;" aria-hidden="true"></div>');
+      var floatTableHidden = false; //this happens when the table is hidden and we do magic when making it visible
+      var $newHeader = $("<thead/>");
+      var $sizerRow = $('<tr class="size-row"/>');
+      var $sizerCells = $([]);
+      var $tableCells = $([]); //used for sizing - either $sizerCells or $tableColGroup cols. $tableColGroup cols are only created in chrome for borderCollapse:collapse because of a chrome bug.
+      var $headerCells = $([]);
+      var $fthCells = $([]); //created elements
+
+      $newHeader.append($sizerRow);
+      $table.prepend($tableColGroup);
+      if(createElements){
+        $fthGrp.append($fthRow);
+        $table.append($fthGrp);
+      }
+
+      $floatTable.append($floatColGroup);
+      $floatContainer.append($floatTable);
+      if(opts.copyTableClass){
+        $floatTable.attr('class', $table.attr('class'));
+      }
+      $floatTable.attr({ //copy over some deprecated table attributes that people still like to use. Good thing people don't use colgroups...
+        'cellpadding': $table.attr('cellpadding'),
+        'cellspacing': $table.attr('cellspacing'),
+        'border': $table.attr('border')
+      });
+      var tableDisplayCss = $table.css('display');
+      $floatTable.css({
+        'borderCollapse': $table.css('borderCollapse'),
+        'border': $table.css('border'),
+        'display': tableDisplayCss
+      });
+      if(tableDisplayCss == 'none'){
+        floatTableHidden = true;
+      }
+
+      $floatTable.addClass(opts.floatTableClass).css({'margin': 0, 'border-bottom-width': 0}); //must have no margins or you won't be able to click on things under floating table
+
+      if(useAbsolutePositioning){
+        var makeRelative = function($container, alwaysWrap){
+          var positionCss = $container.css('position');
+          var relativeToScrollContainer = (positionCss == "relative" || positionCss == "absolute");
+          var $containerWrap = $container;
+          if(!relativeToScrollContainer || alwaysWrap){
+            var css = {"paddingLeft": $container.css('paddingLeft'), "paddingRight": $container.css('paddingRight')};
+            $floatContainer.css(css);
+            $containerWrap = $container.data('floatThead-containerWrap') || $container.wrap("<div class='"+opts.floatWrapperClass+"' style='position: relative; clear:both;'></div>").parent();
+            $container.data('floatThead-containerWrap', $containerWrap); //multiple tables inside one scrolling container - #242
+            wrappedContainer = true;
+          }
+          return $containerWrap;
+        };
+        if(locked){
+          $wrapper = makeRelative($scrollContainer, true);
+          $wrapper.prepend($floatContainer);
+        } else {
+          $wrapper = makeRelative($table);
+          $table.before($floatContainer);
+        }
+      } else {
+        $table.before($floatContainer);
+      }
+
+
+      $floatContainer.css({
+        position: useAbsolutePositioning ? 'absolute' : 'fixed',
+        marginTop: 0,
+        top:  useAbsolutePositioning ? 0 : 'auto',
+        zIndex: opts.zIndex
+      });
+      $floatContainer.addClass(opts.floatContainerClass);
+      updateScrollingOffsets();
+
+      var layoutFixed = {'table-layout': 'fixed'};
+      var layoutAuto = {'table-layout': $table.css('tableLayout') || 'auto'};
+      var originalTableWidth = $table[0].style.width || ""; //setting this to auto is bad: #70
+      var originalTableMinWidth = $table.css('minWidth') || "";
+
+      function eventName(name){
+        return name+'.fth-'+floatTheadId+'.floatTHead'
+      }
+
+      function setHeaderHeight(){
+        var headerHeight = 0;
+        $header.children("tr:visible").each(function(){
+          headerHeight += $(this).outerHeight(true);
+        });
+        if($table.css('border-collapse') == 'collapse') {
+          var tableBorderTopHeight = parseInt($table.css('border-top-width'), 10);
+          var cellBorderTopHeight = parseInt($table.find("thead tr:first").find(">*:first").css('border-top-width'), 10);
+          if(tableBorderTopHeight > cellBorderTopHeight) {
+            headerHeight -= (tableBorderTopHeight / 2); //id love to see some docs where this magic recipe is found..
+          }
+        }
+        $sizerRow.outerHeight(headerHeight);
+        $sizerCells.outerHeight(headerHeight);
+      }
+
+
+      function setFloatWidth(){
+        var tw = tableWidth($table, $fthCells, true);
+        var width = $scrollContainer.width() || tw;
+        var floatContainerWidth = $scrollContainer.css("overflow-y") != 'hidden' ? width - scrollbarOffset.vertical : width;
+        $floatContainer.width(floatContainerWidth);
+        if(locked){
+          var percent = 100 * tw / (floatContainerWidth);
+          $floatTable.css('width', percent+'%');
+        } else {
+          $floatTable.outerWidth(tw);
+        }
+      }
+
+      function updateScrollingOffsets(){
+        scrollingTop = (util.isFunction(opts.top) ? opts.top($table) : opts.top) || 0;
+        scrollingBottom = (util.isFunction(opts.bottom) ? opts.bottom($table) : opts.bottom) || 0;
+      }
+
+      /**
+       * get the number of columns and also rebuild resizer rows if the count is different than the last count
+       */
+      function columnNum(){
+        var count;
+        var $headerColumns = $header.find(opts.headerCellSelector);
+        if(existingColGroup){
+          count = $tableColGroup.find('col').length;
+        } else {
+          count = 0;
+          $headerColumns.each(function () {
+              count += parseInt(($(this).attr('colspan') || 1), 10);
+          });
+        }
+        if(count != lastColumnCount){
+          lastColumnCount = count;
+          var cells = [], cols = [], psuedo = [], content;
+          for(var x = 0; x < count; x++){
+            if (opts.enableAria && (content = $headerColumns.eq(x).text()) ) {
+              cells.push('<th scope="col" class="floatThead-col">' + content + '</th>');
+            } else {
+              cells.push('<th class="floatThead-col"/>');
+            }
+            cols.push('<col/>');
+            psuedo.push("<fthtd style='display:table-cell;height:0;width:auto;'/>");
+          }
+
+          cols = cols.join('');
+          cells = cells.join('');
+
+          if(createElements){
+            psuedo = psuedo.join('');
+            $fthRow.html(psuedo);
+            $fthCells = $fthRow.find('fthtd');
+          }
+
+          $sizerRow.html(cells);
+          $sizerCells = $sizerRow.find("th");
+          if(!existingColGroup){
+            $tableColGroup.html(cols);
+          }
+          $tableCells = $tableColGroup.find('col');
+          $floatColGroup.html(cols);
+          $headerCells = $floatColGroup.find("col");
+
+        }
+        return count;
+      }
+
+      function refloat(){ //make the thing float
+        if(!headerFloated){
+          headerFloated = true;
+          if(useAbsolutePositioning){ //#53, #56
+            var tw = tableWidth($table, $fthCells, true);
+            var wrapperWidth = $wrapper.width();
+            if(tw > wrapperWidth){
+              $table.css('minWidth', tw);
+            }
+          }
+          $table.css(layoutFixed);
+          $floatTable.css(layoutFixed);
+          $floatTable.append($header); //append because colgroup must go first in chrome
+          $tbody.before($newHeader);
+          setHeaderHeight();
+        }
+      }
+      function unfloat(){ //put the header back into the table
+        if(headerFloated){
+          headerFloated = false;
+          if(useAbsolutePositioning){ //#53, #56
+            $table.width(originalTableWidth);
+          }
+          $newHeader.detach();
+          $table.prepend($header);
+          $table.css(layoutAuto);
+          $floatTable.css(layoutAuto);
+          $table.css('minWidth', originalTableMinWidth); //this looks weird, but it's not a bug. Think about it!!
+          $table.css('minWidth', tableWidth($table, $fthCells)); //#121
+        }
+      }
+      var isHeaderFloatingLogical = false; //for the purpose of this event, the header is/isnt floating, even though the element
+                                           //might be in some other state. this is what the header looks like to the user
+      function triggerFloatEvent(isFloating){
+        if(isHeaderFloatingLogical != isFloating){
+          isHeaderFloatingLogical = isFloating;
+          $table.triggerHandler("floatThead", [isFloating, $floatContainer])
+        }
+      }
+      function changePositioning(isAbsolute){
+        if(useAbsolutePositioning != isAbsolute){
+          useAbsolutePositioning = isAbsolute;
+          $floatContainer.css({
+            position: useAbsolutePositioning ? 'absolute' : 'fixed'
+          });
+        }
+      }
+      function getSizingRow($table, $cols, $fthCells, ieVersion){
+        if(createElements){
+          return $fthCells;
+        } else if(ieVersion) {
+          return opts.getSizingRow($table, $cols, $fthCells);
+        } else {
+          return $cols;
+        }
+      }
+
+      /**
+       * returns a function that updates the floating header's cell widths.
+       * @return {Function}
+       */
+      function reflow(){
+        var i;
+        var numCols = columnNum(); //if the tables columns changed dynamically since last time (datatables), rebuild the sizer rows and get a new count
+
+        return function(){
+          $tableCells = $tableColGroup.find('col');
+          var $rowCells = getSizingRow($table, $tableCells, $fthCells, ieVersion);
+
+          if($rowCells.length == numCols && numCols > 0){
+            if(!existingColGroup){
+              for(i=0; i < numCols; i++){
+                $tableCells.eq(i).css('width', '');
+              }
+            }
+            unfloat();
+            var widths = [];
+            for(i=0; i < numCols; i++){
+              widths[i] = getOffsetWidth($rowCells.get(i));
+            }
+            for(i=0; i < numCols; i++){
+              $headerCells.eq(i).width(widths[i]);
+              $tableCells.eq(i).width(widths[i]);
+            }
+            refloat();
+          } else {
+            $floatTable.append($header);
+            $table.css(layoutAuto);
+            $floatTable.css(layoutAuto);
+            setHeaderHeight();
+          }
+          $table.triggerHandler("reflowed", [$floatContainer]);
+        };
+      }
+
+      function floatContainerBorderWidth(side){
+        var border = $scrollContainer.css("border-"+side+"-width");
+        var w = 0;
+        if (border && ~border.indexOf('px')) {
+          w = parseInt(border, 10);
+        }
+        return w;
+      }
+      /**
+       * first performs initial calculations that we expect to not change when the table, window, or scrolling container are scrolled.
+       * returns a function that calculates the floating container's top and left coords. takes into account if we are using page scrolling or inner scrolling
+       * @return {Function}
+       */
+      function calculateFloatContainerPosFn(){
+        var scrollingContainerTop = $scrollContainer.scrollTop();
+
+        //this floatEnd calc was moved out of the returned function because we assume the table height doesn't change (otherwise we must reinit by calling calculateFloatContainerPosFn)
+        var floatEnd;
+        var tableContainerGap = 0;
+        var captionHeight = haveCaption ? $caption.outerHeight(true) : 0;
+        var captionScrollOffset = captionAlignTop ? captionHeight : -captionHeight;
+
+        var floatContainerHeight = $floatContainer.height();
+        var tableOffset = $table.offset();
+        var tableLeftGap = 0; //can be caused by border on container (only in locked mode)
+        var tableTopGap = 0;
+        if(locked){
+          var containerOffset = $scrollContainer.offset();
+          tableContainerGap = tableOffset.top - containerOffset.top + scrollingContainerTop;
+          if(haveCaption && captionAlignTop){
+            tableContainerGap += captionHeight;
+          }
+          tableLeftGap = floatContainerBorderWidth('left');
+          tableTopGap = floatContainerBorderWidth('top');
+          tableContainerGap -= tableTopGap;
+        } else {
+          floatEnd = tableOffset.top - scrollingTop - floatContainerHeight + scrollingBottom + scrollbarOffset.horizontal;
+        }
+        var windowTop = $window.scrollTop();
+        var windowLeft = $window.scrollLeft();
+        var scrollContainerLeft =  $scrollContainer.scrollLeft();
+
+        return function(eventType){
+          var isTableHidden = $table[0].offsetWidth <= 0 && $table[0].offsetHeight <= 0;
+          if(!isTableHidden && floatTableHidden) {
+            floatTableHidden = false;
+            setTimeout(function(){
+              $table.triggerHandler("reflow");
+            }, 1);
+            return null;
+          }
+          if(isTableHidden){ //it's hidden
+            floatTableHidden = true;
+            if(!useAbsolutePositioning){
+              return null;
+            }
+          }
+
+          if(eventType == 'windowScroll'){
+            windowTop = $window.scrollTop();
+            windowLeft = $window.scrollLeft();
+          } else if(eventType == 'containerScroll'){
+            scrollingContainerTop = $scrollContainer.scrollTop();
+            scrollContainerLeft =  $scrollContainer.scrollLeft();
+          } else if(eventType != 'init') {
+            windowTop = $window.scrollTop();
+            windowLeft = $window.scrollLeft();
+            scrollingContainerTop = $scrollContainer.scrollTop();
+            scrollContainerLeft =  $scrollContainer.scrollLeft();
+          }
+          if(isWebkit && (windowTop < 0 || windowLeft < 0)){ //chrome overscroll effect at the top of the page - breaks fixed positioned floated headers
+            return;
+          }
+
+          if(absoluteToFixedOnScroll){
+            if(eventType == 'windowScrollDone'){
+              changePositioning(true); //change to absolute
+            } else {
+              changePositioning(false); //change to fixed
+            }
+          } else if(eventType == 'windowScrollDone'){
+            return null; //event is fired when they stop scrolling. ignore it if not 'absoluteToFixedOnScroll'
+          }
+
+          tableOffset = $table.offset();
+          if(haveCaption && captionAlignTop){
+            tableOffset.top += captionHeight;
+          }
+          var top, left;
+          var tableHeight = $table.outerHeight();
+
+          if(locked && useAbsolutePositioning){ //inner scrolling, absolute positioning
+            if (tableContainerGap >= scrollingContainerTop) {
+              var gap = tableContainerGap - scrollingContainerTop + tableTopGap;
+              top = gap > 0 ? gap : 0;
+              triggerFloatEvent(false);
+            } else {
+              top = wrappedContainer ? tableTopGap : scrollingContainerTop;
+              //headers stop at the top of the viewport
+              triggerFloatEvent(true);
+            }
+            left = tableLeftGap;
+          } else if(!locked && useAbsolutePositioning) { //window scrolling, absolute positioning
+            if(windowTop > floatEnd + tableHeight + captionScrollOffset){
+              top = tableHeight - floatContainerHeight + captionScrollOffset; //scrolled past table
+            } else if (tableOffset.top >= windowTop + scrollingTop) {
+              top = 0; //scrolling to table
+              unfloat();
+              triggerFloatEvent(false);
+            } else {
+              top = scrollingTop + windowTop - tableOffset.top + tableContainerGap + (captionAlignTop ? captionHeight : 0);
+              refloat(); //scrolling within table. header floated
+              triggerFloatEvent(true);
+            }
+            left =  0;
+          } else if(locked && !useAbsolutePositioning){ //inner scrolling, fixed positioning
+            if (tableContainerGap > scrollingContainerTop || scrollingContainerTop - tableContainerGap > tableHeight) {
+              top = tableOffset.top - windowTop;
+              unfloat();
+              triggerFloatEvent(false);
+            } else {
+              top = tableOffset.top + scrollingContainerTop  - windowTop - tableContainerGap;
+              refloat();
+              triggerFloatEvent(true);
+              //headers stop at the top of the viewport
+            }
+            left = tableOffset.left + scrollContainerLeft - windowLeft;
+          } else if(!locked && !useAbsolutePositioning) { //window scrolling, fixed positioning
+            if(windowTop > floatEnd + tableHeight + captionScrollOffset){
+              top = tableHeight + scrollingTop - windowTop + floatEnd + captionScrollOffset;
+              //scrolled past the bottom of the table
+            } else if (tableOffset.top > windowTop + scrollingTop) {
+              top = tableOffset.top - windowTop;
+              refloat();
+              triggerFloatEvent(false); //this is a weird case, the header never gets unfloated and i have no no way to know
+              //scrolled past the top of the table
+            } else {
+              //scrolling within the table
+              top = scrollingTop;
+              triggerFloatEvent(true);
+            }
+            left = tableOffset.left - windowLeft;
+          }
+          return {top: top, left: left};
+        };
+      }
+      /**
+       * returns a function that caches old floating container position and only updates css when the position changes
+       * @return {Function}
+       */
+      function repositionFloatContainerFn(){
+        var oldTop = null;
+        var oldLeft = null;
+        var oldScrollLeft = null;
+        return function(pos, setWidth, setHeight){
+          if(pos != null && (oldTop != pos.top || oldLeft != pos.left)){
+            $floatContainer.css({
+              top: pos.top,
+              left: pos.left
+            });
+            oldTop = pos.top;
+            oldLeft = pos.left;
+          }
+          if(setWidth){
+            setFloatWidth();
+          }
+          if(setHeight){
+            setHeaderHeight();
+          }
+          var scrollLeft = $scrollContainer.scrollLeft();
+          if(!useAbsolutePositioning || oldScrollLeft != scrollLeft){
+            $floatContainer.scrollLeft(scrollLeft);
+            oldScrollLeft = scrollLeft;
+          }
+        }
+      }
+
+      /**
+       * checks if THIS table has scrollbars, and finds their widths
+       */
+      function calculateScrollBarSize(){ //this should happen after the floating table has been positioned
+        if($scrollContainer.length){
+          if($scrollContainer.data().perfectScrollbar){
+            scrollbarOffset = {horizontal:0, vertical:0};
+          } else {
+            var sw = $scrollContainer.width(), sh = $scrollContainer.height(), th = $table.height(), tw = tableWidth($table, $fthCells);
+            var offseth = sw < tw ? scWidth : 0;
+            var offsetv = sh < th ? scWidth : 0;
+            scrollbarOffset.horizontal = sw - offsetv < tw ? scWidth : 0;
+            scrollbarOffset.vertical = sh - offseth < th ? scWidth : 0;
+          }
+        }
+      }
+      //finish up. create all calculation functions and bind them to events
+      calculateScrollBarSize();
+
+      var flow;
+
+      var ensureReflow = function(){
+        flow = reflow();
+        flow();
+      };
+
+      ensureReflow();
+
+      var calculateFloatContainerPos = calculateFloatContainerPosFn();
+      var repositionFloatContainer = repositionFloatContainerFn();
+
+      repositionFloatContainer(calculateFloatContainerPos('init'), true); //this must come after reflow because reflow changes scrollLeft back to 0 when it rips out the thead
+
+      var windowScrollDoneEvent = util.debounce(function(){
+        repositionFloatContainer(calculateFloatContainerPos('windowScrollDone'), false);
+      }, 1);
+
+      var windowScrollEvent = function(){
+        repositionFloatContainer(calculateFloatContainerPos('windowScroll'), false);
+        if(absoluteToFixedOnScroll){
+          windowScrollDoneEvent();
+        }
+      };
+      var containerScrollEvent = function(){
+        repositionFloatContainer(calculateFloatContainerPos('containerScroll'), false);
+      };
+
+
+      var windowResizeEvent = function(){
+        if($table.is(":hidden")){
+          return;
+        }
+        updateScrollingOffsets();
+        calculateScrollBarSize();
+        ensureReflow();
+        calculateFloatContainerPos = calculateFloatContainerPosFn();
+        repositionFloatContainer = repositionFloatContainerFn();
+        repositionFloatContainer(calculateFloatContainerPos('resize'), true, true);
+      };
+      var reflowEvent = util.debounce(function(){
+        if($table.is(":hidden")){
+          return;
+        }
+        calculateScrollBarSize();
+        updateScrollingOffsets();
+        ensureReflow();
+        calculateFloatContainerPos = calculateFloatContainerPosFn();
+        repositionFloatContainer(calculateFloatContainerPos('reflow'), true);
+      }, 1);
+      if(locked){ //internal scrolling
+        if(useAbsolutePositioning){
+          $scrollContainer.on(eventName('scroll'), containerScrollEvent);
+        } else {
+          $scrollContainer.on(eventName('scroll'), containerScrollEvent);
+          $window.on(eventName('scroll'), windowScrollEvent);
+        }
+      } else { //window scrolling
+        $window.on(eventName('scroll'), windowScrollEvent);
+      }
+
+      $window.on(eventName('load'), reflowEvent); //for tables with images
+
+      windowResize(eventName('resize'), windowResizeEvent);
+      $table.on('reflow', reflowEvent);
+      if(isDatatable($table)){
+        $table
+          .on('filter', reflowEvent)
+          .on('sort',   reflowEvent)
+          .on('page',   reflowEvent);
+      }
+
+      $window.on(eventName('shown.bs.tab'), reflowEvent); // people cant seem to figure out how to use this plugin with bs3 tabs... so this :P
+      $window.on(eventName('tabsactivate'), reflowEvent); // same thing for jqueryui
+
+
+      if (canObserveMutations) {
+        var mutationElement = null;
+        if(util.isFunction(opts.autoReflow)){
+          mutationElement = opts.autoReflow($table, $scrollContainer)
+        }
+        if(!mutationElement) {
+          mutationElement = $scrollContainer.length ? $scrollContainer[0] : $table[0]
+        }
+        mObs = new MutationObserver(function(e){
+          var wasTableRelated = function(nodes){
+            return nodes && nodes[0] && (nodes[0].nodeName == "THEAD" || nodes[0].nodeName == "TD"|| nodes[0].nodeName == "TH");
+          };
+          for(var i=0; i < e.length; i++){
+            if(!(wasTableRelated(e[i].addedNodes) || wasTableRelated(e[i].removedNodes))){
+              reflowEvent();
+              break;
+            }
+          }
+        });
+        mObs.observe(mutationElement, {
+            childList: true,
+            subtree: true
+        });
+      }
+
+      //attach some useful functions to the table.
+      $table.data('floatThead-attached', {
+        destroy: function(){
+          var ns = '.fth-'+floatTheadId;
+          unfloat();
+          $table.css(layoutAuto);
+          $tableColGroup.remove();
+          createElements && $fthGrp.remove();
+          if($newHeader.parent().length){ //only if it's in the DOM
+            $newHeader.replaceWith($header);
+          }
+          if(canObserveMutations){
+            mObs.disconnect();
+            mObs = null;
+          }
+          $table.off('reflow reflowed');
+          $scrollContainer.off(ns);
+          if (wrappedContainer) {
+            if ($scrollContainer.length) {
+              $scrollContainer.unwrap();
+            }
+            else {
+              $table.unwrap();
+            }
+          }
+          if(locked){
+            $scrollContainer.data('floatThead-containerWrap', false);
+          } else {
+            $table.data('floatThead-containerWrap', false);
+          }
+          $table.css('minWidth', originalTableMinWidth);
+          $floatContainer.remove();
+          $table.data('floatThead-attached', false);
+          $window.off(ns);
+        },
+        reflow: function(){
+          reflowEvent();
+        },
+        setHeaderHeight: function(){
+          setHeaderHeight();
+        },
+        getFloatContainer: function(){
+          return $floatContainer;
+        },
+        getRowGroups: function(){
+          if(headerFloated){
+            return $floatContainer.find('>table>thead').add($table.children("tbody,tfoot"));
+          } else {
+            return $table.children("thead,tbody,tfoot");
+          }
+        }
+      });
+    });
+    return this;
+  };
+})(jQuery);
+
+/* jQuery.floatThead.utils - http://mkoryak.github.io/floatThead/ - Copyright (c) 2012 - 2014 Misha Koryak
+ * License: MIT
+ *
+ * This file is required if you do not use underscore in your project and you want to use floatThead.
+ * It contains functions from underscore that the plugin uses.
+ *
+ * YOU DON'T NEED TO INCLUDE THIS IF YOU ALREADY INCLUDE UNDERSCORE!
+ *
+ */
+
+(function($){
+
+  $.floatThead = $.floatThead || {};
+
+  $.floatThead._  = window._ || (function(){
+    var that = {};
+    var hasOwnProperty = Object.prototype.hasOwnProperty, isThings = ['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'];
+    that.has = function(obj, key) {
+      return hasOwnProperty.call(obj, key);
+    };
+    that.keys = function(obj) {
+      if (obj !== Object(obj)) throw new TypeError('Invalid object');
+      var keys = [];
+      for (var key in obj) if (that.has(obj, key)) keys.push(key);
+      return keys;
+    };
+    var idCounter = 0;
+    that.uniqueId = function(prefix) {
+      var id = ++idCounter + '';
+      return prefix ? prefix + id : id;
+    };
+    $.each(isThings, function(){
+      var name = this;
+      that['is' + name] = function(obj) {
+        return Object.prototype.toString.call(obj) == '[object ' + name + ']';
+      };
+    });
+    that.debounce = function(func, wait, immediate) {
+      var timeout, args, context, timestamp, result;
+      return function() {
+        context = this;
+        args = arguments;
+        timestamp = new Date();
+        var later = function() {
+          var last = (new Date()) - timestamp;
+          if (last < wait) {
+            timeout = setTimeout(later, wait - last);
+          } else {
+            timeout = null;
+            if (!immediate) result = func.apply(context, args);
+          }
+        };
+        var callNow = immediate && !timeout;
+        if (!timeout) {
+          timeout = setTimeout(later, wait);
+        }
+        if (callNow) result = func.apply(context, args);
+        return result;
+      };
+    };
+    return that;
+  })();
+})(jQuery);
+
+export default undefined;
\ No newline at end of file
diff --git a/package.json b/package.json
index df446ff..bef02ff 100644
--- a/package.json
+++ b/package.json
@@ -12,11 +12,34 @@
   "author": "TOPS Lab",
   "license": "ISC",
   "devDependencies": {
+    "angular2": "^2.0.0-beta.17",
+    "babel-cli": "^6.8.0",
     "babel-preset-es2015": "^6.6.0",
-    "openlayers": "^3.15.1"
+    "babelify": "^7.3.0",
+    "browserify": "^13.0.0",
+    "es6-mixins": "^1.0.2",
+    "es6-shim": "^0.35.0",
+    "gulp": "^3.9.1",
+    "gulp-babel": "^6.1.2",
+    "gulp-cssmin": "^0.1.7",
+    "gulp-less": "^3.0.5",
+    "gulp-minify": "0.0.11",
+    "gulp-rename": "^1.2.2",
+    "gulp-sourcemaps": "^2.0.0-alpha",
+    "jquery": "^2.2.3",
+    "jquery-ui": "^1.10.5",
+    "openlayers": "^3.15.1",
+    "reflect-metadata": "^0.1.2",
+    "rxjs": "^5.0.0-beta.6",
+    "vinyl-buffer": "^1.0.0",
+    "vinyl-source-stream": "^1.1.0",
+    "zone.js": "^0.6.12"
   },
   "repository": {
     "type": "git",
     "url": "https://github.com/glennvorhes/webmapsjs.git"
+  },
+  "dependencies": {
+    "angular2": "^2.0.0-beta.17"
   }
 }
diff --git a/projects/glrtoc/appConfig.js b/projects/glrtoc/appConfig.js
new file mode 100644
index 0000000..bc44356
--- /dev/null
+++ b/projects/glrtoc/appConfig.js
@@ -0,0 +1,150 @@
+/**
+ * Created by gavorhes on 12/8/2015.
+ */
+
+import mapPopup from '../../src/olHelpers/mapPopup';
+import $ from '../../src/jquery';
+let homeTabId = 'home-tab';
+let coordinationTabId = 'coordination-tab';
+let operationsTabId = 'operations-tab';
+
+let operationsStaticPanelId = 'operations-static';
+let operationsAnimatedPanelId = 'operations-animated';
+
+class AppConfig {
+    /**
+     * app configuration object
+     */
+    constructor() {
+        this.$opsAccordion = $("#" + operationsTabId);
+        this.debug = true;
+        this.map = undefined;
+        this._coordinationLayer = undefined;
+        this.lyrIds = [];
+        this.lyrLookup = {};
+        this.lyrArray = [];
+        this.mediaControl = undefined;
+        this.animationLayers = [];
+        this.coordinationLayerId = 'coorindation-layer';
+
+        /**
+         *
+         * @type {Array<LayerBase>}
+         */
+        this.operationsLayersStatic = [];
+
+        /**
+         *
+         * @type {Array<LayerBase>}
+         */
+        this.operationsLayersAnimated = [];
+
+        this.currentOperationsLayers = this.operationsLayersStatic;
+    }
+
+    init() {
+        for (let l of this.operationsLayersStatic) {
+            this.map.removeLayer(l['olLayer']);
+        }
+
+        for (let l of this.operationsLayersAnimated) {
+            this.map.removeLayer(l['olLayer']);
+        }
+
+        this.currentTabId = homeTabId;
+    }
+
+    /**
+     * Add the layer to the config object
+     * @param {LayerBase|*} lyr - base layer
+     */
+    _addLayer(lyr) {
+        this.lyrIds.push(lyr.id);
+        this.lyrLookup[lyr.id] = lyr;
+        this.lyrArray.push(lyr);
+    }
+
+    get coordinationLayer() {
+        return this._coordinationLayer;
+    }
+
+    set coordinationLayer(coordLayer) {
+        this._coordinationLayer = coordLayer;
+        this._addLayer(coordLayer);
+    }
+
+    addOperationsLayerStatic(lyr) {
+        this.operationsLayersStatic.push(lyr);
+        this._addLayer(lyr);
+    }
+
+    addOperationsLayerAnimate(lyr) {
+        this.operationsLayersAnimated.push(lyr);
+        this._addLayer(lyr);
+    }
+
+    set currentTabId(tabId) {
+        mapPopup.closePopup();
+        this.mediaControl.stopPlaying();
+        if (tabId == operationsTabId) {
+            for (let l of this.currentOperationsLayers) {
+                this.map.addLayer(l['olLayer']);
+            }
+        } else {
+            for (let l of this.currentOperationsLayers) {
+                this.map.removeLayer(l['olLayer']);
+            }
+        }
+
+        switch (tabId) {
+            case homeTabId:
+                this.coordinationLayer.visible = false;
+                break;
+            case coordinationTabId:
+                this.coordinationLayer.visible = true;
+                break;
+            case operationsTabId:
+                this.coordinationLayer.visible = false;
+                this.$opsAccordion.accordion("refresh");
+                break;
+            default:
+                throw tabId + ' tab id not found'
+        }
+    }
+
+    set currentOperationsPanelId(panelId) {
+        mapPopup.closePopup();
+        this.mediaControl.stopPlaying();
+
+        for (let l of this.currentOperationsLayers) {
+            this.map.removeLayer(l['olLayer']);
+        }
+
+        switch (panelId) {
+            case operationsStaticPanelId:
+                this.currentOperationsLayers = this.operationsLayersStatic;
+                break;
+
+            case operationsAnimatedPanelId:
+                this.currentOperationsLayers = this.operationsLayersAnimated;
+                break;
+            default:
+                throw panelId + ' panel not found';
+        }
+        for (let l of this.currentOperationsLayers) {
+            this.map.addLayer(l['olLayer']);
+        }
+
+    }
+
+    ///**
+    // * trick to trigger the map move event
+    // */
+    //forceRefresh() {
+    //    if (this.map) {
+    //        this.map.getView().setZoom(this.map.getView().getZoom());
+    //    }
+    //}
+}
+
+export default new AppConfig();
diff --git a/projects/glrtoc/layerPopups.js b/projects/glrtoc/layerPopups.js
new file mode 100644
index 0000000..4362c1c
--- /dev/null
+++ b/projects/glrtoc/layerPopups.js
@@ -0,0 +1,37 @@
+/**
+ * Created by gavorhes on 12/7/2015.
+ */
+
+
+/**
+ * coordination layer popup config
+ * @param {object} props
+ * @returns {string}
+ */
+export function coordination(props) {
+    "use strict";
+    return `<iframe src="${window.location.href.replace('#', '') +
+    '/../toc?toc=' + props['toc']}" width="368" height="292"></iframe>`;
+}
+
+/**
+ * wrs layer popup config
+ * @param {object} props
+ * @returns {string}
+ */
+export function wrs(props) {
+    "use strict";
+    return `<p style="text-align: center">${props['WMS_INFO'].replace(/\n/g, '<br>')}</p>`
+}
+
+export function specialEventWorkZone(props) {
+    "use strict";
+    let startDate = new Date(props['EventStartDate']);
+    let endDate = new Date(props['EventEndDate']);
+    let theContent = '<p style="text-align:center">';
+    theContent += props['EventDescription'] + '<br>';
+    theContent += `${props['HwyName']} ${props['Location']}` + '<br>';
+    theContent += `${startDate.toLocaleDateString()}&nbsp;-&nbsp;${endDate.toLocaleDateString()}`;
+    theContent += '</p>';
+    return theContent;
+}
diff --git a/projects/glrtoc/layerStyles.js b/projects/glrtoc/layerStyles.js
new file mode 100644
index 0000000..7b2eaed
--- /dev/null
+++ b/projects/glrtoc/layerStyles.js
@@ -0,0 +1,39 @@
+/**
+ * Created by gavorhes on 12/3/2015.
+ */
+import ol from '../../src/custom-ol';
+
+export const workZoneAndEventStyle = new ol.style.Style({
+    stroke: new ol.style.Stroke({
+        color: 'red',
+        width: 7
+    }),
+    image: new ol.style.Circle({
+        radius: 8,
+        fill: new ol.style.Fill({
+            color: 'magenta'
+        }),
+        stroke: new ol.style.Stroke({
+            color: 'magenta'
+        })
+    })
+});
+
+export function wrsStyle(feature, resolution) {
+    let colorString = feature.getProperties()['COLOR'];
+
+    let c;
+    if (typeof colorString === 'string') {
+        c = colorString.split(' ');
+    } else {
+        c = [125, 125, 125];
+    }
+
+    return [new ol.style.Style({
+        stroke: new ol.style.Stroke({
+            color: `rgba(${c[0]},${c[1]},${c[2]},0)`,
+            width: 5
+        })
+    })];
+}
+
diff --git a/projects/glrtoc/legendTest.js b/projects/glrtoc/legendTest.js
new file mode 100644
index 0000000..71ecba4
--- /dev/null
+++ b/projects/glrtoc/legendTest.js
@@ -0,0 +1,129 @@
+/**
+ * Created by gavorhes on 1/4/2016.
+ */
+import quickMap from '../../src/olHelpers/quickMap';
+import LayerItsInventory from '../../src/layers/LayerItsInventory';
+import LayerBaseVectorEsri from '../../src/layers/LayerBaseVectorEsri';
+import LayerEsriMapServer from '../../src/layers/LayerEsriMapServer';
+import LayerBaseXyzTile from '../../src/layers/LayerBaseXyzTile';
+import LayerLegend from '../../src/collections/LayerLegend';
+import ItsLayerCollection from '../../src/collections/ItsLayerCollection';
+import mapPopup from '../../src/olHelpers/mapPopup';
+
+
+(function () {
+    "use strict";
+    let map = quickMap({center: {x: -9907589, y: 5232317}, zoom: 12, minZoom: 3, maxZoom: 19});
+
+    let itsLayerCollection = new ItsLayerCollection(map);
+
+    let oakRidgeCams = new LayerEsriMapServer(
+        `http://itsdpro.ornl.gov/arcgis/rest/services/ITSPublic/cameras33/MapServer`,
+        {
+            id: 'cameras33',
+            name: 'Oak Cameras',
+            visible: true,
+            minZoom: 7,
+            zIndex: 20,
+            addPopup: true
+        }
+    );
+
+    map.addLayer(oakRidgeCams.olLayer);
+
+    glob.cat = (bird) => bird * 2;
+
+
+
+
+    glob.map = map;
+
+    let itsLayer = new LayerItsInventory({itsType: 'CCTV', itsIcon: 'cctv.png', name: 'Camera', visible: false});
+
+    map.addLayer(itsLayer.olLayer);
+
+    let legend = new LayerLegend(
+        [
+            {groupName: 'its layers', collapse: true, items: itsLayerCollection.layers},
+            itsLayer, oakRidgeCams
+        ],
+        'legend-container', {});
+
+
+
+    //let legend = new LayerLegend(
+    //    [
+    //        {groupName: 'its layers', expand: true, items: itsLayerCollection.layers},
+    //        itsLayer, testEsri, msg, tower, xyzTile,
+    //        {groupName: 'new group', expand: true, items: [rwis, coord, seg, esriMap]}
+    //    ],
+    //    'legend-container', {});
+
+    //let legend = new LayerLegend(
+    //    [
+    //        itsLayerCollection.layers[1]
+    //    ],
+    //    'legend-container', {});
+
+    map.getView().setZoom(13);
+
+    return;
+
+    let testEsri = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/3',
+        {name: 'test esri', useEsriStyle: true, visible: false}
+    );
+    map.addLayer(testEsri.olLayer);
+
+    let msg = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/8',
+        {name: 'message', useEsriStyle: true, visible: false}
+    );
+    map.addLayer(msg.olLayer);
+
+    let tower = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/0',
+        {name: 'tower', useEsriStyle: true, visible: false}
+    );
+    map.addLayer(tower.olLayer);
+
+
+    let rwis = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/1',
+        {name: 'rwis', useEsriStyle: true, visible: false}
+    );
+    map.addLayer(rwis.olLayer);
+
+    let coord = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/10',
+        {name: 'coord', useEsriStyle: true, minZoom: 6, visible: false, collapseLegend: true}
+    );
+    map.addLayer(coord.olLayer);
+
+    //let coord2 = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/11',
+    //    {name: 'coord2', useEsriStyle: true}
+    //);
+    //map.addLayer(coord.olLayer);
+
+    let seg = new LayerBaseVectorEsri('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer/9',
+        {name: 'seg', useEsriStyle: true, visible: false}
+    );
+    map.addLayer(seg.olLayer);
+
+    let esriMap = new LayerEsriMapServer('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/LegendTest/MapServer', {
+        name: 'esri map',
+        legendCollapse: true,
+        legendCheckbox: false,
+        visible: false
+    });
+
+    map.addLayer(esriMap.olLayer);
+
+    let xyzTile = new LayerBaseXyzTile('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/NPMRDS/npmrds_tile/MapServer/tile/{z}/{y}/{x}',
+        {minZoom: 4, maxZoom: 11, name: "NPMRDS", useEsriStyle: true, collapseLegend: true});
+
+    //let esriMapServer = new LayerEsriMapServer('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/NPMRDS/npmrds_dynamic/MapServer',
+    //    {minZoom: 12, maxZoom: 18});
+
+    map.addLayer(xyzTile.olLayer);
+    //map.addLayer(esriMapServer.olLayer);
+
+    glob.itsCollection = itsLayerCollection;
+
+
+})();
diff --git a/projects/glrtoc/main.js b/projects/glrtoc/main.js
new file mode 100644
index 0000000..2613e08
--- /dev/null
+++ b/projects/glrtoc/main.js
@@ -0,0 +1,319 @@
+/**
+ * Created by gavorhes on 12/2/2015.
+ */
+
+// <editor-fold desc="imports">
+
+import quickMap from '../../src/olHelpers/quickMap';
+import LayerBase from '../../src/layers/LayerBase';
+import LayerBaseVectorGeoJson from '../../src/layers/LayerBaseVectorGeoJson';
+import LayerBaseVectorEsri from  '../../src/layers/LayerBaseVectorEsri';
+import LayerRealEarthTile from  '../../src/layers/LayerRealEarthTile';
+import LayerRealEarthVector from '../../src/layers/LayerRealEarthVector';
+import LayerEsriMapServer from '../../src/layers/LayerEsriMapServer';
+import * as layerStyles from './layerStyles';
+import * as layerPopups from './layerPopups';
+import mapPopup from '../../src/olHelpers/mapPopup';
+import mapMove from '../../src/olHelpers/mapMove';
+import * as dteConvert from '../../src/util/dateConvert';
+import * as colors from '../../src/util/colors';
+import appConfig from './appConfig';
+import * as uiSetup from './mainUi';
+import LayerLegend from '../../src/collections/LayerLegend';
+// </editor-fold>
+
+(function () {
+    "use strict";
+    uiSetup.uiInit();
+
+    let legendItemsStatic = [];
+    let legendItemsAnimated = [];
+    let oakRidgeGroup = {groupName: 'Oak Ridge ITS', collapse: false, addCheck: true, items: []};
+    let realEarthGroup = {groupName: 'Real Earth', collapse: false, items: []};
+    let workZoneEventGroup = {groupName: 'Work Zones / Events', collapse: false, items: []};
+    legendItemsStatic.push(oakRidgeGroup);
+    legendItemsStatic.push(realEarthGroup);
+    legendItemsStatic.push(workZoneEventGroup);
+
+    function animationLoadCallback() {
+        appConfig.animationLayers.push(this);
+    }
+
+    let map = quickMap({center: {x: -85.413, y: 43.29320}, zoom: 6, minZoom: 3, maxZoom: 19});
+    appConfig.map = map;
+
+    // <editor-fold desc="Coordination Layer">
+    let coordinationLayer = new LayerBaseVectorEsri(
+        'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/GlrtocCoordination/MapServer/0',
+        {
+            id: appConfig.coordinationLayerId,
+            visible: true,
+            autoLoad: true,
+            name: 'Coordination',
+            useEsriStyle: true
+        }
+    );
+
+    map.addLayer(coordinationLayer.olLayer);
+    mapPopup.addVectorPopup(coordinationLayer, layerPopups.coordination);
+    appConfig.coordinationLayer = coordinationLayer;
+
+
+    //appConfig.addLayer(coordinationLayer);
+    // </editor-fold>
+
+    // <editor-fold desc="Oak Ridge Layers ">
+    let oakRidgeLayers = [
+        ['Cameras', 'cameras33'],
+        ['HAR', 'HAR33'],
+        ['DMS', 'MessageSigns33'],
+        //['State Summary', 'statesummary'],
+        ['Traffic Control', 'TrafficControl33'],
+        ['Traffic Detection', 'TrafficDetection33'],
+        ['Weather', 'Weather33']
+    ];
+
+
+    for (let i = 0; i < oakRidgeLayers.length; i++) {
+        let oakRidgeLayer = new LayerEsriMapServer(
+            `http://itsdpro.ornl.gov/arcgis/rest/services/ITSPublic/${oakRidgeLayers[i][1]}/MapServer`,
+            {
+                id: oakRidgeLayers[i][1],
+                name: oakRidgeLayers[i][0],
+                visible: false,
+                minZoom: 7,
+                zIndex: 20,
+                addPopup: true
+            }
+        );
+        oakRidgeGroup.items.push(oakRidgeLayer);
+        map.addLayer(oakRidgeLayer.olLayer);
+        appConfig.addOperationsLayerStatic(oakRidgeLayer);
+    }
+    // </editor-fold>
+
+    // <editor-fold desc="WRS Segments">
+
+    let wrsConfigVector = {
+        products: 'ROADS',
+        style: layerStyles.wrsStyle,
+        animate: false,
+        id: 'wrs-segments-vector-static',
+        name: 'Winter Roads',
+        minZoom: 5,
+        maxZoom: 13,
+        zIndex: 3
+    };
+
+    let wrsSegmentLayerVectorStatic = new LayerRealEarthVector(wrsConfigVector);
+    map.addLayer(wrsSegmentLayerVectorStatic.olLayer);
+    mapPopup.addVectorPopup(wrsSegmentLayerVectorStatic, layerPopups.wrs);
+    appConfig.addOperationsLayerStatic(wrsSegmentLayerVectorStatic);
+    //realEarthGroup.items.push(wrsSegmentLayerVectorStatic);
+
+    wrsConfigVector.animate = true;
+    wrsConfigVector.id = 'wrs-segments-vector-animate';
+    wrsConfigVector.loadCallback = animationLoadCallback;
+
+    let wrsSegmentLayerVectorAnimate = new LayerRealEarthVector(wrsConfigVector);
+    map.addLayer(wrsSegmentLayerVectorAnimate.olLayer);
+    mapPopup.addVectorPopup(wrsSegmentLayerVectorAnimate, layerPopups.wrs);
+    appConfig.addOperationsLayerAnimate(wrsSegmentLayerVectorAnimate);
+
+    let wrsConfigTile = {
+        products: 'ROADS',
+        id: 'roads-tile-static',
+        opacity: 0.6,
+        animate: false,
+        name: 'Winter Roads',
+        zIndex: 10
+    };
+
+    let wrsSegmentLayerTileStatic = new LayerRealEarthTile(wrsConfigTile);
+    map.addLayer(wrsSegmentLayerTileStatic.olLayer);
+    appConfig.addOperationsLayerStatic(wrsSegmentLayerTileStatic);
+    realEarthGroup.items.push(wrsSegmentLayerTileStatic);
+
+    wrsConfigTile.animate = true;
+    wrsConfigTile.id = 'roads-tile-animate';
+    wrsConfigTile.loadCallback = animationLoadCallback;
+
+    let wrsSegmentLayerTileAnimate = new LayerRealEarthTile(wrsConfigTile);
+    map.addLayer(wrsSegmentLayerTileAnimate.olLayer);
+    appConfig.addOperationsLayerAnimate(wrsSegmentLayerTileAnimate);
+    legendItemsAnimated.push(wrsSegmentLayerTileAnimate);
+
+    // </editor-fold>
+
+    // <editor-fold desc="24 hour snow">
+
+    let snow24Config = {
+        products: 'SNOWDEPTH24',
+        id: 'snowdepth24-static',
+        opacity: 0.5,
+        animate: false,
+        name: '24HR Snow',
+        maxZoom: 9
+    };
+
+
+    let snowDepthStatic = new LayerRealEarthTile(snow24Config);
+    map.addLayer(snowDepthStatic.olLayer);
+    appConfig.addOperationsLayerStatic(snowDepthStatic);
+    realEarthGroup.items.push(snowDepthStatic);
+
+    //snow24Config.animate = true;
+    //snow24Config.id = 'snowdepth24-animate';
+    //snow24Config.loadCallback = animationLoadCallback;
+    //
+    //let snowDepthAnimate = new LayerRealEarthTile(snow24Config);
+    //map.addLayer(snowDepthAnimate.olLayer);
+    //appConfig.addOperationsLayerAnimate(snowDepthAnimate);
+    //legendItemsAnimated.push(snowDepthAnimate);
+
+    // </editor-fold>
+
+    // <editor-fold desc="nexrhres and precipitation layers">
+    let nexrhresConfig = {
+        products: 'nexrhres',
+        id: 'nexrhres-static',
+        opacity: 0.6,
+        animate: false,
+        name: 'Hybrid Reflectivity',
+        maxZoom: 10
+    };
+
+    let nexrhresStatic = new LayerRealEarthTile(nexrhresConfig);
+    map.addLayer(nexrhresStatic.olLayer);
+    appConfig.addOperationsLayerStatic(nexrhresStatic);
+    realEarthGroup.items.push(nexrhresStatic);
+
+    nexrhresConfig.animate = true;
+    nexrhresConfig.id = 'nexrhres-animate';
+    nexrhresConfig.loadCallback = animationLoadCallback;
+
+    let nexrhresAnimate = new LayerRealEarthTile(nexrhresConfig);
+    map.addLayer(nexrhresAnimate.olLayer);
+    appConfig.addOperationsLayerAnimate(nexrhresAnimate);
+    legendItemsAnimated.push(nexrhresAnimate);
+
+    let precipRateConfig = {
+        products: 'nexrcomp',
+        id: 'precip-rate-static',
+        opacity: 0.6,
+        animate: false,
+        name: 'Precipitation Rate',
+        maxZoom: 10
+    };
+
+    let precipRateStatic = new LayerRealEarthTile(precipRateConfig);
+    map.addLayer(precipRateStatic.olLayer);
+    appConfig.addOperationsLayerStatic(precipRateStatic);
+    realEarthGroup.items.push(precipRateStatic);
+
+    precipRateConfig.animate = true;
+    precipRateConfig.id = 'precip-rate-animate';
+    precipRateConfig.loadCallback = animationLoadCallback;
+
+    let precipRateAnimate = new LayerRealEarthTile(precipRateConfig);
+    map.addLayer(precipRateAnimate.olLayer);
+    appConfig.addOperationsLayerAnimate(precipRateAnimate);
+    legendItemsAnimated.push(precipRateAnimate);
+
+
+    // </editor-fold>
+
+    // <editor-fold desc="Work Zones and special events">
+    let d = new Date();
+    d.setSeconds(0);
+    let endDate = dteConvert.dateToYyyyMmDdHhMmSs(d);
+    d.setYear(d.getYear() + 1901);
+    var startDate = dteConvert.dateToYyyyMmDdHhMmSs(d);
+
+    let workZoneSegLayer = new LayerBaseVectorEsri(
+        'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/GLRTOC_WZ_SE/MapServer/1',
+        {
+            where: `EventStartDate < '${startDate}' AND EventEndDate > '${endDate}' AND Impact IN('High', 'XXX')`,
+            name: "Work Zone Segments",
+            style: layerStyles.workZoneAndEventStyle,
+            id: 'work-zone-segments',
+            minZoom: 5,
+            maxZoom: 13
+        }
+    );
+
+    map.addLayer(workZoneSegLayer.olLayer);
+    appConfig.addOperationsLayerStatic(workZoneSegLayer);
+    mapPopup.addVectorPopup(workZoneSegLayer, layerPopups.specialEventWorkZone);
+    workZoneEventGroup.items.push(workZoneSegLayer);
+
+    let specialEventsLayer = new LayerBaseVectorEsri(
+        'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/GLRTOC/GLRTOC_WZ_SE/MapServer/2',
+        {
+            where: `EventStartDate < '${startDate}' AND EventEndDate > '${endDate}' AND Impact IN('High', 'XXX')`,
+            //where: '1=1',
+            name: "Special Events",
+            style: layerStyles.workZoneAndEventStyle,
+            id: 'special-event-points',
+            minZoom: 5,
+            maxZoom: 13
+        }
+    );
+
+    map.addLayer(specialEventsLayer.olLayer);
+    appConfig.addOperationsLayerStatic(specialEventsLayer);
+    mapPopup.addVectorPopup(specialEventsLayer, layerPopups.specialEventWorkZone);
+    workZoneEventGroup.items.push(specialEventsLayer);
+
+    // </editor-fold>
+
+    let legendStatic = new LayerLegend(legendItemsStatic, 'legend-container-static', {});
+    let legendAnimate = new LayerLegend(legendItemsAnimated, 'legend-container-animate', {});
+
+    appConfig.init();
+
+    uiSetup.uiAfterMap();
+})();
+
+
+// <editor-fold desc="Promise example">
+
+//var promise = new Promise(function (resolve, reject) {
+//    console.log('doing stuff');
+//
+//    $.get('http://realearth.ssec.wisc.edu:80/api/products', {products: 'ROADS'}, function (d) {
+//        //if (d.length == 0) {
+//        //    console.log(`${this._products} layer not available or does not have times`);
+//        //    return;
+//        //}
+//        d = d[0];
+//        console.log(d);
+//        resolve("Stuff worked!");
+//
+//
+//        //for (let i = 0; i < d['times'].length; i++) {
+//        //    _this._loadDates.call(_this, d['times'][i]);
+//        //}
+//        //console.log(_this._localDates);
+//
+//        //_this._loadAtTimeIndex.call(_this, _this._localDates.length - 1)
+//
+//    }, 'json').fail(function () {
+//        reject(Error("It broke"));
+//    });
+//
+//
+//});
+//
+//
+//promise.then(
+//    function (result) {
+//        console.log(result); // "Stuff worked!"
+//    },
+//    function (err) {
+//        console.log(err); // Error: "It broke"
+//    }
+//);
+// </editor-fold>
+
+
diff --git a/projects/glrtoc/mainUi.js b/projects/glrtoc/mainUi.js
new file mode 100644
index 0000000..9e4f17c
--- /dev/null
+++ b/projects/glrtoc/mainUi.js
@@ -0,0 +1,105 @@
+/**
+ * Created by gavorhes on 12/7/2015.
+ */
+
+import {} from '../../src/jquery-plugin/mediaControl';
+import {} from '../../src/jquery-plugin/rangeChange';
+import appConfig from './appConfig';
+import $ from '../../src/jquery';
+require('jquery-ui/tabs');
+require('jquery-ui/accordion');
+
+
+/**
+ * Set up the UI
+ */
+export function uiInit() {
+    "use strict";
+
+    //$("#operations").accordion('refresh');
+
+
+    let $sidebar = $('#sidebar');
+    let $hideSideBar = $('#hide-sidebar');
+    let $showSideBar = $('#show-sidebar');
+
+    let $tabs = $("#tabs");
+
+    let sidebarWidth = $sidebar.width();
+
+    //apply tab layout
+    $tabs.tabs({
+        heightStyle: "fill",
+        activate: function (event, ui) {
+            appConfig.currentTabId = ui.newPanel[0].id;
+        }
+    });
+
+    //apply accordion
+    appConfig.$opsAccordion.accordion({
+        heightStyle: "fill",
+        activate: function (event, ui) {
+            appConfig.currentOperationsPanelId = ui.newPanel[0].id;
+        }
+    });
+
+    $(window).resize(function () {
+        $tabs.tabs('refresh');
+        setTimeout(function () {
+            appConfig.$opsAccordion.accordion("refresh");
+        }, 50);
+    });
+
+    $hideSideBar.click(function () {
+        var mapCenter = appConfig.map.getView().getCenter();
+
+        appConfig.map.beforeRender(function () {
+            $sidebar.animate({'margin-left': -1 * sidebarWidth}, 200,
+                function () {
+                    $hideSideBar.hide();
+                    $showSideBar.show();
+                    appConfig.map.updateSize();
+                }
+            );
+        });
+        appConfig.map.getView().setCenter(mapCenter);
+    });
+
+    $showSideBar.click(function () {
+        var mapCenter = appConfig.map.getView().getCenter();
+
+        appConfig.map.beforeRender(function () {
+            $sidebar.animate({'margin-left': 0}, 200,
+                function () {
+                    $showSideBar.hide();
+                    $hideSideBar.show();
+                    appConfig.map.updateSize();
+                }
+            );
+        });
+        appConfig.map.getView().setCenter(mapCenter);
+    });
+
+    let d = new Date();
+    let endTime = d.getTime();
+    d.setHours(d.getHours() - 4);
+    let startTime = d.getTime();
+    let rangeStep = Math.round((endTime - startTime) / 8);
+
+    appConfig.mediaControl = $('#animation-control').mediaControl(startTime, endTime, endTime, rangeStep,
+        function (t) {
+            for (let l of appConfig.animationLayers) {
+                l.setLayerTime(t);
+            }
+        },
+        750, true);
+}
+
+
+//http://realearth.ssec.wisc.edu/api/image?products=nexrhres_20160108_180000&x=5&y=5&z=4
+
+export function uiAfterMap() {
+    "use strict";
+
+
+}
\ No newline at end of file
diff --git a/projects/itsMap.js b/projects/itsMap.js
new file mode 100644
index 0000000..d31b6c5
--- /dev/null
+++ b/projects/itsMap.js
@@ -0,0 +1,27 @@
+/**
+ * Created by gavorhes on 12/18/2015.
+ */
+
+import quickMap from '../src/olHelpers/quickMap';
+import mapMove from '../src/olHelpers/mapMove';
+import mapPopup from '../src/olHelpers/mapPopup';
+import ItsLayerCollection from '../src/collections/ItsLayerCollection';
+import LayerLegend from '../src/collections/LayerLegend';
+
+let map = quickMap();
+mapMove.init(map);
+mapPopup.init(map);
+
+let itsLayerCollection = new ItsLayerCollection(map);
+
+let layerArray = [
+    {
+        groupName: 'ITS Inventory Layers',
+        collapse: false,
+        addCheck: true,
+        items: itsLayerCollection.layers
+    }
+];
+
+
+let legend = new LayerLegend(layerArray, 'legend-container', {});
diff --git a/projects/npmrds/delay/delay-config.js b/projects/npmrds/delay/delay-config.js
new file mode 100644
index 0000000..5be9a94
--- /dev/null
+++ b/projects/npmrds/delay/delay-config.js
@@ -0,0 +1,3 @@
+/**
+ * Created by gavorhes on 2/9/2016.
+ */
diff --git a/projects/npmrds/delay/delay-main.js b/projects/npmrds/delay/delay-main.js
new file mode 100644
index 0000000..b0783f4
--- /dev/null
+++ b/projects/npmrds/delay/delay-main.js
@@ -0,0 +1,32 @@
+/**
+ * Created by gavorhes on 2/9/2016.
+ */
+
+import quickMap from '../../../src/olHelpers/quickMap';
+const angular = require('angular2');
+
+let map = quickMap();
+
+let app = angular.module('myApp', []);
+
+
+app.controller('myCtrl', function($scope) {
+    $scope.firstName = "John";
+    $scope.lastName = "Doe";
+
+    $scope.myFunction = function(){
+        "use strict";
+        console.log((new Date()).getTime());
+    };
+});
+
+
+app.controller('myCtrl2', function($scope) {
+    //$scope.firstName = "John";
+    //$scope.lastName = "Doe";
+    //
+    //$scope.myFunction = function(){
+    //    "use strict";
+    //    console.log((new Date()).getTime());
+    //}
+});
diff --git a/projects/npmrds/heatmap/appConfig.js b/projects/npmrds/heatmap/appConfig.js
new file mode 100644
index 0000000..32d4fcb
--- /dev/null
+++ b/projects/npmrds/heatmap/appConfig.js
@@ -0,0 +1,50 @@
+/**
+ * Created by gavorhes on 12/23/2015.
+ */
+
+class NpmrdsHeatmapConfig {
+    constructor() {
+
+        this.map = undefined;
+
+        /**
+         *
+         * @type {LayerBaseVectorGeoJson}
+         */
+        this.featureOverlay = undefined;
+
+        /**
+         *
+         * @type {LayerBaseVectorGeoJson}
+         */
+        this.lineLayer = undefined;
+
+
+        /**
+         *
+         * @type {LayerBaseVectorGeoJson}
+         */
+        this.pointLayer = undefined;
+
+
+        /**
+         *
+         * @type {LayerBaseVectorGeoJson}
+         */
+        this.trackerLayer = undefined;
+    }
+
+
+}
+
+///**
+// *
+// * @type {NpmrdsHeatmapConfig}
+// */
+//const npmrdsHeatmapConfig = new NpmrdsHeatmapConfig();
+
+/**
+ * @type {NpmrdsHeatmapConfig}
+ */
+export default new NpmrdsHeatmapConfig();
+
diff --git a/projects/npmrds/heatmap/layerStyles.js b/projects/npmrds/heatmap/layerStyles.js
new file mode 100644
index 0000000..e16bd69
--- /dev/null
+++ b/projects/npmrds/heatmap/layerStyles.js
@@ -0,0 +1,43 @@
+/**
+ * Created by gavorhes on 12/22/2015.
+ */
+import ol from '../../../src/custom-ol';
+
+
+export const overlayStyle = new ol.style.Style({
+    fill: new ol.style.Fill({
+        color: 'rgba(255, 0, 237, 0.1)'
+    }),
+    stroke: new ol.style.Stroke({
+        color: 'rgb(255, 0, 237)',
+        width: 2
+    })
+});
+
+
+export const lineIndicator = new ol.style.Style({
+    stroke: new ol.style.Stroke({
+        color: 'red',
+        width: 7
+    })
+});
+
+
+export const pointIndices = new ol.style.Style({
+    image: new ol.style.Circle({
+        radius: 4,
+        fill: new ol.style.Fill({
+            color: 'blue'
+        })
+    })
+});
+
+
+export const trackerPoint = new ol.style.Style({
+    image: new ol.style.Circle({
+        radius: 8,
+        fill: new ol.style.Fill({
+            color: 'blue'
+        })
+    })
+});
diff --git a/projects/npmrds/heatmap/main-ui.js b/projects/npmrds/heatmap/main-ui.js
new file mode 100644
index 0000000..7f6a44a
--- /dev/null
+++ b/projects/npmrds/heatmap/main-ui.js
@@ -0,0 +1,368 @@
+/**
+ * Created by gavorhes on 12/22/2015.
+ */
+
+import npmrdsHeatmapConfig from './appConfig';
+import {} from '../../../src/jquery-plugin/dayRange';
+import {} from '../../../src/jquery-plugin/rangeChange';
+import * as colors from '../../../src/util/colors';
+import SortedFeatures from '../../../src/olHelpers/SortedFeatures';
+import $ from '../../../src/jquery';
+import ol from '../../../src/custom-ol';
+
+
+let $btnCancelArea = $('#btn-cancel-area');
+let $btnSelectArea = $('#btn-select-area');
+let $divSelectArea = $('#div-select-area');
+let $ulHwyDirs = $('#ul-hwy-dirs');
+let $btnSelectHwy = $('#btn-select-hwy');
+//let $btnSelectHwyBack = $('#btn-select-hwy-back');
+let $divHighwaySelection = $('#div-highway-selection');
+let $divRight = $('#right');
+let $divLeft = $('#left');
+let $ckmapTrack = $('#chk-map-track');
+let $canvasToolTipSpan = $('#canvas-tooltip-span');
+let $canv = $('#heatmap-canvas');
+/**
+ *
+ * @type {*|jQuery|HTMLElement}
+ */
+let $canvContainer = $('#canvas-container');
+let $dateUl = $('#date-ul');
+let $heatMapVerticalBar = $('#heat-map-vertical-bar');
+let $selectedTmcs = $('#selected-tmcs');
+
+let sldrHeatMapId = 'heat-map-slider';
+let $sldrHeatMap = $('#' + sldrHeatMapId);
+
+
+let heatMapReady = false;
+
+/**
+ * @type {SortedFeatures}
+ */
+let sortedFeatures;
+
+/**
+ * @type {DayRange}
+ */
+let dayRange;
+
+function _leadingPad(num) {
+    let out = num.toFixed();
+    if (out.length == 1) {
+        out = '0' + out;
+    }
+    
+    return out;
+}
+
+function _formatDate(d) {
+    return `${_leadingPad(d.getMonth() + 1)}/${_leadingPad(d.getDate())}/${d.getYear() + 1900} ` +
+        `${_leadingPad(d.getHours())}:${_leadingPad(d.getMinutes())}:${_leadingPad(d.getSeconds())}`;
+}
+
+
+
+function clearCanvas() {
+    $dateUl.html('');
+    $canv.off('mousemove');
+    let canv = $canv[0];
+    let ctx = canv.getContext("2d");
+    ctx.clearRect(0, 0, canv.width, canv.height);
+
+    $canv.attr('width', $('#heat-map-slider').width());
+    $canv.attr('height', 100);
+    $heatMapVerticalBar.height(0);
+}
+
+
+export function startUi() {
+    "use strict";
+    dayRange = $('#div-date-range').dayRange(10);
+}
+
+export function drawSetup() {
+    "use strict";
+    let draw = new ol.interaction.Draw({
+        source: npmrdsHeatmapConfig.featureOverlay.source,
+        type: 'Polygon'
+    });
+
+    draw.on('drawend', function (evt) {
+        //$divSelectArea.hide();
+        $btnSelectArea.prop('disabled', false);
+        let geom = evt.feature.getGeometry();
+        let geomClone = geom.clone();
+
+        geomClone.transform('EPSG:3857', 'EPSG:4326');
+
+        setTimeout(function () {
+            npmrdsHeatmapConfig.map.removeInteraction(draw);
+        }, 100);
+        $btnCancelArea.hide();
+
+        $.get('npmrds/gethighways', {polygon: JSON.stringify(geomClone.getCoordinates())},
+            function (d) {
+                $ulHwyDirs.html(d);
+                $divHighwaySelection.show();
+
+                $('input[name=hwydirs]').change(function () {
+                    $btnSelectHwy.prop('disabled', false);
+                });
+            }, 'text');
+    });
+
+    $btnCancelArea.click(function () {
+        npmrdsHeatmapConfig.map.removeInteraction(draw);
+        $btnSelectArea.prop('disabled', false);
+        $btnCancelArea.hide();
+    });
+
+    $btnSelectArea.click(function () {
+        npmrdsHeatmapConfig.featureOverlay.source.clear();
+        npmrdsHeatmapConfig.map.addInteraction(draw);
+        $btnSelectArea.prop('disabled', true);
+        $btnCancelArea.show();
+        $ulHwyDirs.html('');
+        $divHighwaySelection.hide();
+        $btnSelectHwy.prop('disabled', true);
+    });
+}
+
+
+export function endUi() {
+    "use strict";
+
+    $divSelectArea.show();
+    $btnSelectArea.prop('disabled', false);
+    $btnCancelArea.hide();
+    $divHighwaySelection.hide();
+
+    $btnSelectHwy.click(function () {
+        let selectedHwy = $('input[name=hwydirs]:checked:first').val();
+        $('#sel-hwy').html(selectedHwy);
+        $.get('npmrds/getroute',
+            {
+                hwyDir: selectedHwy
+            }, function (d) {
+                npmrdsHeatmapConfig.lineLayer.clear();
+                npmrdsHeatmapConfig.lineLayer.addFeatures(d['line']);
+
+                npmrdsHeatmapConfig.pointLayer.clear();
+                npmrdsHeatmapConfig.pointLayer.addFeatures(d['points']);
+
+                sortedFeatures = new SortedFeatures(npmrdsHeatmapConfig.pointLayer.source.getFeatures(), 'distReal');
+
+                npmrdsHeatmapConfig.trackerLayer.clear();
+                npmrdsHeatmapConfig.trackerLayer.source.addFeature(sortedFeatures.sortedFeatures[0]);
+
+
+                $divLeft.hide();
+                $divRight.show();
+                npmrdsHeatmapConfig.map.updateSize();
+
+                let mapView = npmrdsHeatmapConfig.map.getView();
+
+                let panAnimation = ol.animation.pan({
+                    duration: 500,
+                    source: mapView.getCenter()
+                });
+                npmrdsHeatmapConfig.map.beforeRender(panAnimation);
+
+                mapView.fit(
+                    npmrdsHeatmapConfig.featureOverlay.source.getFeatures()[0].getGeometry().getExtent(),
+                    npmrdsHeatmapConfig.map.getSize()
+                );
+
+                mapView.setZoom(mapView.getZoom() - 1);
+
+                $('#vehicle-all').prop('checked', true);
+
+                npmrdsHeatmapConfig.featureOverlay.visible = false;
+                npmrdsHeatmapConfig.lineLayer.visible = true;
+                npmrdsHeatmapConfig.pointLayer.visible = true;
+                npmrdsHeatmapConfig.trackerLayer.visible = true;
+
+                $sldrHeatMap.prop('max', d['totalDistance']);
+                $sldrHeatMap.val(0);
+                $heatMapVerticalBar.css('left', '22px');
+                $selectedTmcs.val(JSON.stringify(d['tmcs']));
+
+                //console.log(d);
+            }, 'json').fail(function () {
+            alert("something went wrong, try another area selection");
+        });
+    });
+}
+
+
+export function heatMapUi() {
+    "use strict";
+
+    $('#heat-map-back').click(function () {
+        $divLeft.show();
+        $divRight.hide();
+        npmrdsHeatmapConfig.map.updateSize();
+        npmrdsHeatmapConfig.featureOverlay.visible = true;
+        npmrdsHeatmapConfig.lineLayer.visible = false;
+        npmrdsHeatmapConfig.pointLayer.visible = false;
+        npmrdsHeatmapConfig.trackerLayer.visible = false;
+
+        clearCanvas();
+    });
+
+    $canv.mouseenter(function () {
+        if (heatMapReady) {
+            $canvasToolTipSpan.css('display', 'block');
+        }
+    });
+
+    $canv.mouseleave(function () {
+        $canvasToolTipSpan.css('display', 'none');
+        $canvasToolTipSpan.html('');
+    });
+
+    //add the slider change interaction
+    $('#' + sldrHeatMapId).rangeChange(function(newVal, percent, evt){
+
+        let canvWidth = parseFloat($canv.width());
+
+        $heatMapVerticalBar.css('left', (canvWidth * percent + 22).toFixed() + 'px');
+
+        let selectedFeature = sortedFeatures.getFeature(newVal);
+
+        npmrdsHeatmapConfig.trackerLayer.clear();
+        npmrdsHeatmapConfig.trackerLayer.source.addFeature(selectedFeature);
+
+        if ($ckmapTrack.prop('checked')) {
+            let panAnimation = ol.animation.pan({
+                duration: 500,
+                source: npmrdsHeatmapConfig.map.getView().getCenter()
+            });
+            npmrdsHeatmapConfig.map.beforeRender(panAnimation);
+            npmrdsHeatmapConfig.map.getView().setCenter(selectedFeature.getGeometry().getCoordinates());
+        }
+    }, 25);
+
+
+    $('#heat-map-confirm').click(function () {
+        clearCanvas();
+        let canv = $canv[0];
+        let ctx = canv.getContext("2d");
+        let $progressDiv = $('#progress-indicator-div');
+        $dateUl.html('');
+
+        $progressDiv.show();
+
+        $.get('npmrds/getheatmap',
+            {
+                vehicleType: $('input[name=vehicle-type]:checked:first').val(),
+                startDate: _formatDate(dayRange.startDate),
+                endDate: _formatDate(dayRange.endDate),
+                tmcs: $selectedTmcs.val()
+            }, function (d) {
+                if (d['error']) {
+                    alert(d['error']);
+
+                    return;
+                }
+
+                let canvasWidth = parseInt($canv.attr('width'));
+                let colorGradient = colors.makeBlueGreenRedGradient(d['minSpeedVsFree'], d['maxSpeedVsFree'], true);
+                let colorGradientStdDev = colors.makeBlueGreenRedGradientZScore(d['medianSpeedVsFree'], d['stdDevSpeedVsFree'], true);
+
+                let recordLength = d['tmcResult'][0].values.length;
+                $canv.attr('height', (recordLength * 2).toFixed());
+                $heatMapVerticalBar.height(recordLength * 2);
+
+                let columnPosition = 0;
+                let tmcEndPositionArray = [];
+
+                for (let i = 0; i < d['tmcResult'].length; i++) {
+                    let tmc = d['tmcResult'][i];
+                    let rectangleWidth = canvasWidth * tmc['distPercent'] / 100;
+                    let valLength = tmc['values'].length;
+
+                    for (let j = 0; j < valLength; j++) {
+                        let speedVFreeSpeed = (tmc['values'][j] == null ? null : tmc['values'][j] / tmc['freeFlowSpeed']);
+                        let speedVFreeZScore = (speedVFreeSpeed - d['medianSpeedVsFree']) / d['stdDevSpeedVsFree'];
+                        //console.log(colorGradientStdDev(speedVFreeSpeed));
+                        //let z_score =
+                        //let z_score =
+
+                        ctx.fillStyle = colorGradientStdDev(speedVFreeSpeed);
+                        ctx.fillRect(columnPosition, j * 2, rectangleWidth, 2);
+                    }
+
+                    columnPosition += rectangleWidth;
+                    tmcEndPositionArray.push([tmc['tmc'], columnPosition]);
+                }
+
+                for (let i = 0; i < d['dateList'].length; i++) {
+                    $dateUl.append('<li><div>' + d['dateList'][i] + '</div></ul>');
+                }
+
+                // set the date li height
+                //$('#date-ul li').css('height', (2 + (parseInt($canv.attr('height')) / d['dateList'].length) - d['dateList'].length).toFixed());
+
+                let tmcResultDict = {};
+                for (let i = 0; i < d['tmcResult'].length; i++) {
+                    tmcResultDict[d['tmcResult'][i]['tmc']] = d['tmcResult'][i];
+                }
+
+                $canv.mousemove(function (evt) {
+
+                    // coordinates relative the canvas
+                    let offsetX = evt.offsetX;
+                    let offsetY = evt.offsetY;
+
+                    // coordinates relative the window
+                    let clientX = evt.clientX;
+                    let clientY = evt.clientY;
+
+                    // get the tmc
+                    let tmcId = null;
+                    for (let i = 0; i < tmcEndPositionArray.length; i++) {
+                        if (offsetX < tmcEndPositionArray[i][1]) {
+                            tmcId = tmcEndPositionArray[i][0];
+                            break;
+                        }
+                    }
+
+                    if (tmcId == null) {
+                        tmcId = tmcEndPositionArray[tmcEndPositionArray.length - 1][0];
+                    }
+
+                    // since each retangle in the heatmap is 2px high, get the index by divding by 2, round down
+                    let rowIndex = Math.floor(offsetY / 2);
+
+                    // make a date equal to the query start date
+                    let dte = new Date(d['dateList'][0]);
+                    // add minutes as 5X the row index
+                    dte.setMinutes(dte.getMinutes() + rowIndex * 5);
+
+                    let tmcObj = tmcResultDict[tmcId];
+
+                    //set the html
+                    $canvasToolTipSpan.html(`TMC: ${tmcId}<br>${dte.toLocaleDateString()} ${dte.toLocaleTimeString()}<br>` +
+                    `Speed: ${(tmcObj['values'][rowIndex] == null ? 'null' : tmcObj['values'][rowIndex].toFixed(1))}, Free: ${tmcObj['freeFlowSpeed'].toFixed(1)}`
+                    );
+
+                    //set the position
+                    let canvasToolipSpan = $canvasToolTipSpan[0];
+                    canvasToolipSpan.style.left = (clientX - 150).toFixed() + 'px';
+                    canvasToolipSpan.style.top = (clientY - 85).toFixed() + 'px';
+
+                });
+                //console.log(d);
+
+                $canvContainer.scrollTop(0);
+                heatMapReady = true;
+            }, 'json').fail(function(){
+                alert('Something went wrong with the request');
+            }).always(function(){
+                $progressDiv.hide();
+            });
+    });
+}
diff --git a/projects/npmrds/heatmap/main.js b/projects/npmrds/heatmap/main.js
new file mode 100644
index 0000000..2397377
--- /dev/null
+++ b/projects/npmrds/heatmap/main.js
@@ -0,0 +1,47 @@
+/**
+ * Created by gavorhes on 12/22/2015.
+ */
+
+import npmrdsHeatmapConfig from './appConfig';
+import quickMap from '../../../src/olHelpers/quickMap';
+import LayerBaseXyzTile from '../../../src/layers/LayerBaseXyzTile';
+import LayerEsriMapServer from '../../../src/layers/LayerEsriMapServer';
+import LayerBaseVectorGeoJson from '../../../src/layers/LayerBaseVectorGeoJson';
+import * as layerStyles from './layerStyles';
+import * as uiSetup from './main-ui';
+
+//glob.appConfig = npmrdsHeatmapConfig;
+
+(function(){
+    "use strict";
+    uiSetup.startUi();
+    let map = quickMap({center: {x: -10012438, y: 5548095}, zoom: 8, minZoom: 5});
+
+    npmrdsHeatmapConfig.map = map;
+
+    let xyzTile = new LayerBaseXyzTile('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/NPMRDS/npmrds_tile/MapServer/tile/{z}/{y}/{x}',
+        {minZoom: 4, maxZoom: 11});
+
+    let esriMapServer = new LayerEsriMapServer('http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/NPMRDS/npmrds_dynamic/MapServer',
+        {minZoom: 12, maxZoom: 18});
+
+    map.addLayer(xyzTile.olLayer);
+    map.addLayer(esriMapServer.olLayer);
+
+    let transform = {dataProjection: 'EPSG:3857', featureProjection: 'EPSG:3857'};
+
+    npmrdsHeatmapConfig.featureOverlay = new LayerBaseVectorGeoJson('', {style: layerStyles.overlayStyle, transform: transform});
+    npmrdsHeatmapConfig.lineLayer = new LayerBaseVectorGeoJson('', {style: layerStyles.lineIndicator, transform: transform});
+    npmrdsHeatmapConfig.pointLayer = new LayerBaseVectorGeoJson('', {style: layerStyles.pointIndices, transform: transform});
+    npmrdsHeatmapConfig.trackerLayer = new LayerBaseVectorGeoJson('', {style: layerStyles.trackerPoint, transform: transform});
+
+    map.addLayer(npmrdsHeatmapConfig.featureOverlay.olLayer);
+    map.addLayer(npmrdsHeatmapConfig.lineLayer.olLayer);
+    //map.addLayer(npmrdsHeatmapConfig.pointLayer.olLayer);
+    map.addLayer(npmrdsHeatmapConfig.trackerLayer.olLayer);
+
+    uiSetup.drawSetup();
+    uiSetup.heatMapUi();
+    uiSetup.endUi();
+})();
+
diff --git a/projects/tsmo/TipConfig.js b/projects/tsmo/TipConfig.js
new file mode 100644
index 0000000..705a404
--- /dev/null
+++ b/projects/tsmo/TipConfig.js
@@ -0,0 +1,149 @@
+/**
+ * Created by gavorhes on 12/14/2015.
+ */
+
+/**
+ * Tip config object
+ */
+class TipConfig {
+    constructor() {
+
+        /**
+         *
+         * @type {Sliders}
+         */
+        this.sliders = undefined;
+
+        /**
+         *
+         * @type {Map}
+         */
+        this.map = null;
+
+        this.$loadingGif = null;
+        this.$regionSelector = undefined;
+
+        /**
+         *
+         * @type {TipSegmentLayer}
+         */
+        this.tipSegmentLayer = undefined;
+
+
+        /**
+         *
+         * @type {LayerBase|*}
+         */
+        this.metamanagerSegments = undefined;
+        this.tipSegmentLayerMinZoom = 10;
+
+        /**
+         * its layer collection
+         * @type {ItsLayerCollection}
+         */
+        this.itsLayerCollection = undefined;
+
+        /**
+         * param list to set up the sliders, initial weight and drop down selection added later
+         */
+        this._sliderParamArray = [
+            ['AADT', [['aadtyr_1', '1']]],
+            ['AADT Future', [['aadtyr_5', '5'], ['aadtyr_10', '10'], ['aadtyr_15', '15'], ['aadtyr_20', '20']]],
+            ['Growth', [['growth_5', '5'], ['growth_10', '10'], ['growth_15', '15'], ['growth_20', '20']]],
+            ['Truck', [['trkdyr_1', '1'], ['trkdyr_20', '20']]],
+            ['LOS', [['losyr_1', '1']]],
+            ['LOS Future', [['losyr_5', '5'], ['losyr_10', '10'], ['losyr_15', '15'], ['losyr_20', '20']]],
+            ['Crash Rate', [['crash_rate', '1']]],
+            ['Severity', [['crash_severity', '1']]],
+            ['Weather', [['weather', '1']]],
+            ['Event', [['event', '1']]]
+        ];
+
+        /**
+         * Presets list, order of the parameters is important, must match that of the slider param list
+         */
+        this._presetArray =
+            [
+                ['Default TIP', [
+                    [10.0, 'aadtyr_1'],
+                    [7.0, 'aadtyr_20'],
+                    [7.0, 'growth_20'],
+                    [4.0, 'trkdyr_1'],
+                    [12.0, 'losyr_1'],
+                    [12.0, 'losyr_20'],
+                    [15.0, 'crash_rate'],
+                    [13.0, 'crash_severity'],
+                    [9.0, 'weather'],
+                    [11.0, 'event']]
+                ],
+                ['Safety', [
+                    [20.0, 'aadtyr_1'],
+                    [0.0, 'aadtyr_20'],
+                    [0.0, 'growth_20'],
+                    [0.0, 'trkdyr_1'],
+                    [0.0, 'losyr_1'],
+                    [0.0, 'losyr_20'],
+                    [40.0, 'crash_rate'],
+                    [40.0, 'crash_severity'],
+                    [0.0, 'weather'],
+                    [0.0, 'event']]
+                ],
+                ['Mobility Present', [
+                    [25.0, 'aadtyr_1'],
+                    [25.0, 'aadtyr_5'],
+                    [0.0, 'growth_20'],
+                    [25.0, 'trkdyr_1'],
+                    [25.0, 'losyr_1'],
+                    [0.0, 'losyr_20'],
+                    [0.0, 'crash_rate'],
+                    [0.0, 'crash_severity'],
+                    [0.0, 'weather'],
+                    [0.0, 'event']]
+                ],
+                ['Mobility Future', [
+                    [0.0, 'aadtyr_1'],
+                    [25.0, 'aadtyr_20'],
+                    [25.0, 'growth_20'],
+                    [25.0, 'trkdyr_20'],
+                    [0.0, 'losyr_1'],
+                    [25.0, 'losyr_20'],
+                    [0.0, 'crash_rate'],
+                    [0.0, 'crash_severity'],
+                    [0.0, 'weather'],
+                    [0.0, 'event']]
+                ],
+                ['Service', [
+                    [30.0, 'aadtyr_1'],
+                    [0.0, 'aadtyr_20'],
+                    [10.0, 'growth_20'],
+                    [0.0, 'trkdyr_1'],
+                    [30.0, 'losyr_1'],
+                    [30.0, 'losyr_20'],
+                    [0.0, 'crash_rate'],
+                    [0.0, 'crash_severity'],
+                    [0.0, 'weather'],
+                    [0.0, 'event']]
+                ],
+                ['Freight Performance', [
+                    [20.0, 'aadtyr_1'],
+                    [0.0, 'aadtyr_20'],
+                    [0.0, 'growth_20'],
+                    [60.0, 'trkdyr_1'],
+                    [20.0, 'losyr_1'],
+                    [0.0, 'losyr_20'],
+                    [0.0, 'crash_rate'],
+                    [0.0, 'crash_severity'],
+                    [0.0, 'weather'],
+                    [0.0, 'event']]
+                ]
+            ];
+
+        for (var i = 0; i < this._sliderParamArray.length; i++) {
+            this._sliderParamArray[i].push(this._presetArray[0][1][i][0]);
+            this._sliderParamArray[i].push(this._presetArray[0][1][i][1]);
+        }
+
+    }
+}
+
+export default new TipConfig();
diff --git a/projects/tsmo/TipSegmentLayer.js b/projects/tsmo/TipSegmentLayer.js
new file mode 100644
index 0000000..60f299b
--- /dev/null
+++ b/projects/tsmo/TipSegmentLayer.js
@@ -0,0 +1,191 @@
+/**
+ * Created by gavorhes on 12/10/2015.
+ */
+
+import LayerBaseVectorGeoJson from '../../src/layers/LayerBaseVectorGeoJson';
+import mapMove from '../../src/olHelpers/mapMove';
+import mapPopup from '../../src/olHelpers/mapPopup';
+import tipConfig from './TipConfig'
+import * as tipLayerStyles from './tipStyleFunction';
+const $ = require('jquery');
+
+/**
+ * Tip Segment layer with a bunch of configuration applied, extends base GeoJSON
+ */
+class TipSegmentLayer extends LayerBaseVectorGeoJson {
+
+    /**
+     * ITS device layer, types available at http://transportal.cee.wisc.edu/its/inventory/
+     * @param {Sliders} sliders
+     * @param {object} options
+     * @param {boolean} [options.visible=true]
+     * @param {boolean} [options.onDemand=true]
+     * @param {number} [options.onDemandDelay=300] delay before the map move callback should be called
+     * @param [options.$loadingGif=undefined] jquery reference to loading gif
+     * @param [options.$regionSelector=undefined] jquery reference to region selector
+     *
+     * @param {object} [options.selectionStyle={}] the selection style configuration
+     * @param {string} [options.selectionStyle.color=rgba(255,170,0,0.5)] the selection color
+     * @param {number} [options.selectionStyle.width=10] the selection width for linear features
+     * @param {object} [options.selectionStyle.olStyle=undefined] an openlayers style object
+     * @param {propertiesZoomStyle} [options.selectionStyle.olStyleFunction=undefined] modified OL style function
+     */
+    constructor(sliders, options) {
+        options['name'] = "TIP Segments";
+        options.onDemand = true;
+        options.style = tipLayerStyles.tipStyle;
+        options['minZoom'] = tipConfig.tipSegmentLayerMinZoom;
+        options['transform'] = {};
+        options.transform.dataProjection = 'EPSG:3857';
+        options.transform.featureProjection = 'EPSG:3857';
+        options['id'] = 'tip-segments';
+        super('tip/gettipfeatures?callback=?', options);
+        this._$regionSelector = options.$regionSelector || undefined;
+
+
+        this._$loadingGif = options.$loadingGif;
+
+        this.legendContent += '<ul><li><hr class="cell-min"></li><li><hr class="cell-very-low"></li>' +
+            '<li><hr class="cell-low"></li><li><hr class="cell-mid"></li><li><hr class="cell-high"></li>' +
+            '<li><hr class="cell-very-high"></li><li><hr class="cell-max">' +
+            '</li></ul>';
+
+        this.legendContent += '<ul>';
+        this.legendContent += '<li style="height: 15px;">low</li>';
+        this.legendContent += '<li style="height: 25px; margin-top: 4px;">medium</li>';
+        this.legendContent += '<li style="height: 15px; margin-top: 5px;">high</li>';
+        this.legendContent += '</ul>';
+
+        this.legendContent += '<span style="margin: 12px 0 0 52px; display: block">Relative Need</span>';
+
+        this._geometry_cache = [];
+
+        this.sliders = sliders;
+        tipConfig.tipSegmentLayer = this;
+
+        let _this = this;
+
+        mapPopup.addVectorPopup(this, function (props) {
+
+            let parms = {
+                pdpId: props['p'],
+                paramWeights: JSON.stringify(_this.sliders.getParams())
+            };
+
+            return '<iframe style="margin-top:5px;" ' +
+                `src="tip/getsegprops?${$.param(parms)}" ` +
+                `height="250" width="350"></iframe>`;
+        }, options.selectionStyle);
+    }
+
+    /**
+     * override base method to include show or hide loading indicator
+     * @param {number} zoom
+     * @param {string} [evtType=undefined] undefined for initial load, otherwise one of 'change:center', 'change:resolution'
+     * @returns {boolean}
+     */
+    mapMoveBefore(zoom, evtType) {
+        let returnVal = super.mapMoveBefore(zoom, evtType);
+
+        if (this._$loadingGif) {
+            if (returnVal) {
+                this._$loadingGif.fadeIn();
+            } else {
+                this._$loadingGif.fadeOut();
+            }
+        }
+        return returnVal;
+    }
+
+    /**
+     * callback to generate the parameters passed in the get request
+     * @param {object} extent
+     * @param {number} extent.minX
+     * @param {number} extent.minY
+     * @param {number} extent.maxX
+     * @param {number} extent.maxY
+     * @param {number} zoomLevel
+     */
+    mapMoveMakeGetParams(extent, zoomLevel) {
+        super.mapMoveMakeGetParams(extent, zoomLevel);
+        $.extend(this.mapMoveParams, extent);
+        this.mapMoveParams['param_weights'] = JSON.stringify(this.sliders.getParams());
+        if (this._$regionSelector){
+            this.mapMoveParams['region'] = this._$regionSelector.val();
+        }
+    }
+
+
+    /**
+     * callback function on map move
+     * @param d the json response
+     */
+    mapMoveCallback(d) {
+        if (d['geojson'].features.length > 0) {
+            this._geometry_cache = this._geometry_cache.concat(d['geojson'].features);
+            this._geometry_cache.sort(function (a, b) {
+                return (a.properties['p'] - b.properties['p']);
+            });
+        }
+
+        let passFeatures = [];
+
+        for (let s of d['scores']) {
+
+            let feat = this._set_pdp_score(s['p'], s['z']);
+            if (feat) {
+                passFeatures.push(feat);
+            }
+        }
+
+        d['geojson'].features = passFeatures;
+        super.mapMoveCallback(d['geojson']);
+        if (this._$loadingGif) {
+            this._$loadingGif.fadeOut();
+        }
+    }
+
+    /**
+     * set the score for the feature
+     * @param {number} pdp pdp_id
+     * @param {number} score to assign
+     * @param {object} [in_array=this._geometry_cache]
+     * @returns {*} the feature with the score assigned
+     * @private
+     */
+    _set_pdp_score(pdp, score, in_array) {
+        if (typeof in_array == 'undefined') {
+            in_array = this._geometry_cache;
+        }
+
+        if (in_array.length == 0) {
+            return undefined;
+        }
+
+        if (in_array.length == 1) {
+            let feat = in_array[0];
+            if (feat.properties['p'] == pdp) {
+                feat.properties['z'] = score;
+                return feat;
+            } else {
+                return undefined;
+            }
+        }
+
+        let midPoint = Math.floor(in_array.length / 2);
+
+        let midFeature = in_array[midPoint];
+        if (midFeature.properties['p'] == pdp) {
+            midFeature.properties['z'] = score;
+            return midFeature;
+        } else if (pdp < midFeature.properties['p']) {
+            return this._set_pdp_score(pdp, score, in_array.slice(0, midPoint));
+        } else {
+            return this._set_pdp_score(pdp, score, in_array.slice(midPoint + 1));
+        }
+    }
+
+}
+
+
+export default TipSegmentLayer;
diff --git a/projects/tsmo/legend-test.js b/projects/tsmo/legend-test.js
new file mode 100644
index 0000000..361da67
--- /dev/null
+++ b/projects/tsmo/legend-test.js
@@ -0,0 +1,84 @@
+/**
+ * Created by gavorhes on 12/18/2015.
+ */
+import quickMap from '../../src/olHelpers/quickMap';
+import mapMove from '../../src/olHelpers/mapMove';
+import mapPopup from '../../src/olHelpers/mapPopup';
+import ItsLayerCollection from '../../src/collections/ItsLayerCollection';
+import LayerLegend from '../../src/collections/LayerLegend';
+
+let map = quickMap();
+
+mapMove.init(map);
+mapPopup.init(map);
+
+let itsLayerCollection = new ItsLayerCollection(map);
+
+let layerArray = [
+    {
+        groupName: 'ITS Inventory Layers',
+        collapse: false,
+        addCheck: false,
+        items: itsLayerCollection.layers
+    }
+];
+
+let legend = new LayerLegend(layerArray, 'legend-container', {});
+
+//
+//let layerArry = itsLayerCollection.layers.slice(0, 3);
+//
+//layerArry.push(
+//    {
+//        groupName: 'Group 1',
+//        collapse: false,
+//        items: itsLayerCollection.layers.slice(3, 6)
+//    }
+//);
+//
+//layerArry.push(itsLayerCollection.layers[6]);
+//
+//let collection2 = itsLayerCollection.layers.slice(7, 9);
+//collection2.push({
+//
+//    groupName: 'Group 3',
+//    collapse: false,
+//    items: itsLayerCollection.layers.slice(10, 12)
+//
+//});
+//
+//layerArry.push(
+//    {
+//        groupName: 'Group 2',
+//        collapse: true,
+//        items: collection2
+//    }
+//);
+//
+//let layerArray = [
+//    itsLayerCollection.layers[0],
+//    itsLayerCollection.layers[1],
+//    {
+//
+//        groupName: 'Group 1',
+//        collapse: false,
+//        items: [itsLayerCollection.layers[2], itsLayerCollection.layers[3]]
+//    },
+//    {
+//        groupName: 'Group 2',
+//        collapse: true,
+//        addCheck: false,
+//        items: [
+//            itsLayerCollection.layers[4],
+//            itsLayerCollection.layers[5],
+//            {
+//                groupName: 'Group 3',
+//                collapse: false,
+//                items: [itsLayerCollection.layers[6], itsLayerCollection.layers[7]]
+//            }
+//        ]
+//    }
+//];
+
+
+
diff --git a/projects/tsmo/main-report.js b/projects/tsmo/main-report.js
new file mode 100644
index 0000000..6b8d948
--- /dev/null
+++ b/projects/tsmo/main-report.js
@@ -0,0 +1,198 @@
+/**
+ * Created by gavorhes on 12/22/2015.
+ */
+
+import quickMap from '../../src/olHelpers/quickMap';
+import LayerBaseVectorGeoJson from '../../src/layers/LayerBaseVectorGeoJson';
+import mapPopup from '../../src/olHelpers/mapPopup';
+import * as tipLayerStyles from './tipStyleFunction';
+import LayerEsriMapServer from '../../src/layers/LayerEsriMapServer';
+import ItsLayerCollection from '../../src/collections/ItsLayerCollection';
+import LayerLegend from '../../src/collections/LayerLegend';
+import {} from '../../lib/jquery.floatThead';
+let $ = require('jquery');
+
+let suppressLowScores = true;
+
+function tipStyleReport(feature, resolution) {
+    "use strict";
+
+    let seg_score = feature.getProperties()['z'];
+
+    if (seg_score < 1 && suppressLowScores) {
+        return null;
+    } else {
+        return tipLayerStyles.tipStyle(feature, resolution);
+    }
+}
+
+
+(function () {
+    "use strict";
+
+    let $crashData = $('#crash-data');
+
+    function getCrashData(pdpId) {
+        $crashData.html('');
+
+        $.get('getcrash', {pdpId: pdpId}, function (d) {
+            $crashData.html(d);
+        }, 'text')
+    }
+
+    // add the cell classes
+    $('.param-val').each(function () {
+        let z = parseFloat($(this).attr('data-crumb'));
+        let cellClass = 'cell-max';
+
+        if (z < -2.5) {
+            cellClass = 'cell-min';
+        } else if (z < -1.5) {
+            cellClass = 'cell-very-low';
+        } else if (z < -0.5) {
+            cellClass = 'cell-low';
+        } else if (z < 0.5) {
+            cellClass = 'cell-mid';
+        } else if (z < 1.5) {
+            cellClass = 'cell-high';
+        } else if (z < 2.5) {
+            cellClass = 'cell-very-high';
+        }
+
+        $(this).addClass(cellClass);
+    });
+
+    let map = quickMap({center: {x: -10012438, y: 5548095}, zoom: 6, minZoom: 6});
+
+    let wisDotRegions = new LayerEsriMapServer(
+        'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/MetaManager/Metamanager_regions/MapServer',
+        {
+            minZoom: 6,
+            maxZoom: 15,
+            opacity: 0.4
+        });
+
+    map.addLayer(wisDotRegions.olLayer);
+
+    let reportSegments = new LayerBaseVectorGeoJson('',
+        {
+            loadCallback: function (theLayer) {
+                map.getView().fit(theLayer.source.getExtent(), map.getSize());
+            },
+            transform: {featureProjection: 'EPSG:3857', dataProjection: 'EPSG:3857'},
+            style: tipStyleReport,
+            params: {d: (new Date()).getTime().toString()},
+            zIndex: 10
+        }
+    );
+
+    let geoJsonData = JSON.parse($('#geojson-data').val());
+    reportSegments.addFeatures(geoJsonData);
+
+    map.addLayer(reportSegments.olLayer);
+    map.getView().fit(reportSegments.source.getExtent(), map.getSize());
+
+    let itsLayerCollection = new ItsLayerCollection(map);
+
+    for (let l of itsLayerCollection.layers) {
+        l.zIndex = 10;
+        l.opacity = 0.5;
+    }
+
+    let layerArray = [
+        {
+            groupName: 'ITS Inventory Layers',
+            collapse: true,
+            addCheck: true,
+            items: itsLayerCollection.layers
+        }
+    ];
+
+    let legend = new LayerLegend(layerArray, 'legend-container', {});
+
+    let $resultsTable = $('#results-table');
+    let $resultsContainer = $('#results-container');
+
+
+
+    mapPopup.addMapClickFunction(function () {
+        $('.selected-row').removeClass('selected-row');
+    });
+
+    //return undefined in popup callback to prevent default popup display
+    let selectionLayer = mapPopup.addVectorPopup(reportSegments, function (props) {
+            let pdp = props['p'];
+            getCrashData(pdp);
+            let $selectedRow = $resultsTable.find(`#${pdp}`);
+            $selectedRow.addClass('selected-row');
+            $resultsContainer.find('table tbody').scrollTop(0);
+            $resultsContainer.find('table tbody').scrollTop($selectedRow.offset().top - 60);
+            return undefined;
+        },
+        {olStyle: tipLayerStyles.tipStyleSelection}
+    );
+
+    mapPopup.addMapClickFunction(function () {
+        $crashData.html('');
+    });
+
+    $('.selectable-row').click(function () {
+        let mapView = map.getView();
+        selectionLayer.getSource().clear();
+        $('.selected-row').removeClass('selected-row');
+        $(this).addClass('selected-row');
+        let pdp = parseInt(this.id);
+        let feats = reportSegments.source.getFeatures();
+        getCrashData(pdp);
+        for (let feature of feats) {
+            if (feature.getProperties()['p'] == pdp) {
+                selectionLayer.getSource().addFeature(feature);
+                mapView.fit(feature.getGeometry().getExtent(), map.getSize());
+                mapView.setZoom(mapView.getZoom() - 2);
+                return;
+            }
+        }
+    });
+
+    let $scoreUnderOneRows = $('.score-under-one-flag');
+
+    $('#show-all-toggle').click(function () {
+        if (this.checked) {
+            $scoreUnderOneRows.removeClass('score-under-one');
+            suppressLowScores = false;
+        } else {
+            $scoreUnderOneRows.addClass('score-under-one');
+            suppressLowScores = true;
+        }
+        reportSegments.refresh();
+        mapPopup.closePopup();
+        $('.selected-row').removeClass('selected-row');
+        $('#crash-table').html('');
+
+    }).prop('checked', false);
+
+    let oneRowTd = $resultsTable.find('tr:nth-of-type(2)').children('td');
+    let $firstTrTh = $resultsTable.find('tr:first-of-type').children('th');
+
+    function updateTableColumnWidth(){
+
+        let colWidths = [];
+
+        $firstTrTh.each(function(i, el){
+            colWidths.push($(el).width());
+        });
+
+        oneRowTd.each(function(i, el){
+            $(el).width(colWidths[i]);
+        });
+    }
+
+    updateTableColumnWidth();
+
+    $resultsTable.find('tr:first-of-type').click(function(){
+       setTimeout(updateTableColumnWidth, 50);
+
+    });
+
+
+})();
diff --git a/projects/tsmo/main-ui.js b/projects/tsmo/main-ui.js
new file mode 100644
index 0000000..48be215
--- /dev/null
+++ b/projects/tsmo/main-ui.js
@@ -0,0 +1,126 @@
+/**
+ * Created by gavorhes on 12/14/2015.
+ */
+import tipConfig from './TipConfig';
+import mapMove from '../../src/olHelpers/mapMove';
+import LayerLegend from '../../src/collections/LayerLegend';
+const $ = require('jquery');
+
+/**
+ * start the UI setup
+ */
+export function startUi() {
+
+    tipConfig.$loadingGif = $('#loading-gif');
+    tipConfig.$loadingGif.fadeIn();
+
+    tipConfig.$regionSelector = $('#region-selector');
+    tipConfig.$regionSelector.val('all');
+
+    // configure the preset selector
+    let $presetSelector = $('#preset-selector');
+    for (let i = 0; i < tipConfig._presetArray.length; i++) {
+        let weights = {};
+        let sumCheck = 0;
+        for (let j = 0; j < tipConfig._sliderParamArray.length; j++) {
+            sumCheck += tipConfig._presetArray[i][1][j][0];
+            weights[tipConfig._sliderParamArray[j][0].toLowerCase().replace(/ /g, '-')] = tipConfig._presetArray[i][1][j];
+        }
+        if (sumCheck != 100) {
+            alert('Sum not equal to 100 for preset ' + tipConfig._presetArray[i][0]);
+        }
+
+        let optionHtml = '<option value="';
+        optionHtml += JSON.stringify(weights).replace(/"/g, '&quot;');
+        optionHtml += '"';
+        optionHtml += (i == 0 ? ' selected="selected"' : '') + '>' + tipConfig._presetArray[i][0] + '</option>';
+
+        $presetSelector.append(optionHtml);
+    }
+
+    //enable get help button
+    $('#tip-help').click(function () {
+
+        let win = window.open('tip/help', '_blank');
+        if (win) {
+            //Browser has allowed it to be opened
+            win.focus();
+        } else {
+            //Browser has blocked it
+            alert('Please allow popups for this site');
+        }
+    });
+}
+
+
+/**
+ * finish the UI setup
+ */
+export function endUi() {
+    tipConfig.$loadingGif.fadeOut();
+
+    $('#make-report').click(function () {
+        let params = mapMove.mapExtent;
+        params['paramWeights'] = JSON.stringify(tipConfig.sliders.getParams());
+        params['region'] = $('#region-selector').val();
+        params['preset'] = $('#preset-selector option:selected').text();
+
+        let win = window.open('tip/getreport?' + $.param(params), '_blank');
+        if (win) {
+            //Browser has allowed it to be opened
+            win.focus();
+        } else {
+            //Browser has blocked it
+            alert('Please allow popups for this site');
+        }
+    });
+
+    //add events to the preset selector
+    let $presetSelector = $('#preset-selector');
+
+    // add preset selector change handler
+    let first = true;
+    $presetSelector.change(function () {
+
+        var weightSelection = JSON.parse(this.value);
+        tipConfig.sliders.setValues(weightSelection);
+        if (first) {
+            first = false;
+        } else {
+            mapMove.triggerLyrLoad(tipConfig.tipSegmentLayer);
+        }
+    });
+
+    $presetSelector.trigger('change');
+
+    $('#region-selector').change(function(){
+        "use strict";
+        mapMove.triggerLyrLoad(tipConfig.tipSegmentLayer);
+    });
+
+
+    tipConfig.sliders.addSlideFinishedFunction(function () {
+        $presetSelector[0].selectedIndex = 0;
+        mapMove.triggerLyrLoad(tipConfig.tipSegmentLayer);
+    });
+}
+
+export function endUiMap() {
+
+    mapMove.addCallback(function (ext, zoom, evt) {
+        $('#make-report').prop('disabled', !(zoom >= tipConfig.tipSegmentLayerMinZoom));
+    });
+
+    let layerArray = [
+        tipConfig.tipSegmentLayer,
+        tipConfig.metamanagerSegments,
+        {
+            groupName: 'ITS Inventory Layers',
+            collapse: true,
+            addCheck: false,
+            items: tipConfig.itsLayerCollection.layers
+        }
+    ];
+
+    let legend = new LayerLegend(layerArray, 'legend-container', {});
+}
diff --git a/projects/tsmo/main.js b/projects/tsmo/main.js
new file mode 100644
index 0000000..3a73607
--- /dev/null
+++ b/projects/tsmo/main.js
@@ -0,0 +1,166 @@
+/**
+ * Created by gavorhes on 12/14/2015.
+ */
+import Sliders from '../../src/collections/Sliders';
+import tipConfig from './TipConfig';
+import mapPopup from '../../src/olHelpers/mapPopup';
+import * as uiSetup from './main-ui';
+import LayerEsriMapServer from '../../src/layers/LayerEsriMapServer';
+import ItsLayerCollection from '../../src/collections/ItsLayerCollection';
+import quickMap from '../../src/olHelpers/quickMap';
+import TipSegmentLayer from './TipSegmentLayer';
+
+(function () {
+    "use strict";
+
+    glob.config = tipConfig;
+
+    uiSetup.startUi();
+
+    let sliders = new Sliders(tipConfig._sliderParamArray, 'slider-container');
+    tipConfig.sliders = sliders;
+
+    let map = quickMap({minZoom: 7});
+
+    tipConfig.map = map;
+
+    let wisDotRegions = new LayerEsriMapServer(
+        'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/MetaManager/Metamanager_regions/MapServer',
+        {
+            minZoom: 6,
+            maxZoom: 12,
+            name: 'WisDOT Regions',
+            useEsriStyle: true
+        });
+
+    map.addLayer(wisDotRegions.olLayer);
+
+    //initialize the tip segment layer
+    let tipSegmentLayer = new TipSegmentLayer(sliders,
+        {
+            selectionStyle: {
+                color: 'rgba(0,0,255,0.5)',
+                width: 7
+            },
+            $loadingGif: tipConfig.$loadingGif,
+            $regionSelector: tipConfig.$regionSelector
+        }
+    );
+    map.addLayer(tipSegmentLayer.olLayer);
+    tipConfig.tipSegmentLayer = tipSegmentLayer;
+
+    let metamanagerSegments = new LayerEsriMapServer(
+        'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/MetaManager/MM_All_Segments/MapServer',
+        {
+            minZoom: 7,
+            visible: false,
+            name: 'Metamanager Segments'
+        });
+
+    map.addLayer(metamanagerSegments.olLayer);
+    tipConfig.metamanagerSegments = metamanagerSegments;
+
+    //initialize the ITS layer collection
+    tipConfig.itsLayerCollection = new ItsLayerCollection(map);
+
+    uiSetup.endUi();
+    uiSetup.endUiMap();
+
+})();
+
+
+//
+//let sliders;
+//    let app;
+//    let extraLayers = {};
+//
+//    $(function () {
+//        return;
+//        // create the sliders object
+//        sliders = new SliderGroup(tipConfig._sliderParamArray, 'slider-container');
+//
+//        $('#btnResetDefault').click(function () {
+//            sliders.reset();
+//        });
+//
+
+//
+//        let geoJsonArray = [];
+//
+//        for (i = 0; i < itsLayerConfig.length; i++) {
+//            geoJsonArray.push(new ItsGeoJsonLayer(itsLayerConfig[i], app.map, 'its-legend-items'));
+//        }
+//
+//        $('#showVis').prop('checked', true);
+//
+//        $('input[name=showAll]').change(function () {
+//            let $legendDivs = $('#its-legend-items > div');
+//            if (this.id == 'showAll' && this.checked) {
+//                $legendDivs.addClass('force-show');
+//            } else {
+//                $legendDivs.removeClass('force-show');
+//            }
+//        });
+//
+//        function showHide() {
+//            let shown = true;
+//            let $legend = $('#its-legend-items');
+//            let $pic = $('#show-hide');
+//            return function () {
+//                if (shown) {
+//                    $legend.slideUp();
+//                    shown = false;
+//                    $pic.attr('src', $pic.attr('src').replace('up', 'down'));
+//                } else {
+//                    $legend.slideDown();
+//                    shown = true;
+//                    $pic.attr('src', $pic.attr('src').replace('down', 'up'));
+//                }
+//            }
+//        }
+//
+//
+//        $('#show-hide').click(showHide()).trigger('click');
+//
+//        $('#show-hide-all-its').prop('checked', true).change(function () {
+//
+//            for (let i = 0; i < geoJsonArray.length; i++) {
+//                geoJsonArray[i].groupVisible = this.checked;
+//            }
+//
+//        });
+//
+//
+//        let url = 'http://transportal.cee.wisc.edu/applications/arcgis2/rest/services/MetaManager/MM_All_Segments/MapServer';
+//
+//        extraLayers['mm-layer'] = new ol.layer.Tile({
+//            source: new ol.source.TileArcGISRest({
+//                url: url
+//            })
+//        });
+//
+//        let layerCheckboxes = $('#layer-switch').find('input[type="checkbox"]');
+//
+//        layerCheckboxes.each(function (idx, val) {
+//            val.checked = false;
+//        });
+//
+//        layerCheckboxes.click(function () {
+//            if (this.checked) {
+//                app.map.addLayer(extraLayers[this.id])
+//            } else {
+//                app.map.removeLayer(extraLayers[this.id])
+//            }
+//        });
+//
+//        //fade out the loading gif when complete
+//        $gif.fadeOut();
+//
+
+//
+
+//    });
+
+
+
+
diff --git a/projects/tsmo/slider-test.js b/projects/tsmo/slider-test.js
new file mode 100644
index 0000000..6bf939d
--- /dev/null
+++ b/projects/tsmo/slider-test.js
@@ -0,0 +1,29 @@
+/**
+ * Created by gavorhes on 12/14/2015.
+ */
+
+import Sliders from '../../src/collections/Sliders';
+import {} from '../../src/jquery-plugin/dayRange';
+import tipConfig from './TipConfig';
+const $ = require('jquery');
+
+(function(){
+    //alert('here');
+
+    let dayRange = $('#dates').dayRange(10);
+
+
+    //let sliders = new Sliders(tipConfig._sliderParamArray, 'slider-container');
+    //glob.sliders = sliders;
+    //
+    //
+    //
+    //
+    //console.log(tipConfig);
+})();
+
+
+
+
+
+
diff --git a/projects/tsmo/tipStyleFunction.js b/projects/tsmo/tipStyleFunction.js
new file mode 100644
index 0000000..33d6c2f
--- /dev/null
+++ b/projects/tsmo/tipStyleFunction.js
@@ -0,0 +1,84 @@
+/**
+ * Created by gavorhes on 12/22/2015.
+ */
+import * as zoomResolutionConvert from '../../src/olHelpers/zoomResolutionConvert';
+import ol from '../../src/custom-ol';
+
+
+/**
+ * tip style function
+ * @param feature
+ * @param resolution
+ * @returns {*[]}
+ */
+export function tipStyle(feature, resolution) {
+
+    let zoom = zoomResolutionConvert.resolutionToZoom(resolution);
+
+    let width = 5;
+
+    if (zoom >= 18){
+        width = 10;
+    } else if (zoom >= 14) {
+        width = 8;
+    } else if (zoom >= 12) {
+        width = 7;
+    } else if (zoom >= 10) {
+        width = 6;
+    }
+
+    let seg_score = feature.getProperties()['z'];
+    let hex_color;
+    if (seg_score < -2.5) {
+        hex_color = '#00ff00';
+    } else if (seg_score < -1.5) {
+        hex_color = '#55ff00';
+    } else if (seg_score < -0.5) {
+        hex_color = '#aaff00';
+    } else if (seg_score < 0.5) {
+        hex_color = '#ffff00';
+    } else if (seg_score < 1.5) {
+        hex_color = '#ffaa00';
+    } else if (seg_score < 2.5) {
+        hex_color = '#ff5500';
+    } else {
+        hex_color = '#ff0000'
+    }
+
+    return [new ol.style.Style({
+        stroke: new ol.style.Stroke({
+            color: hex_color,
+            width: width
+        })
+    })];
+}
+
+/**
+ * selection style
+ * @param feature
+ * @param resolution
+ * @returns {*[]}
+ */
+export function tipStyleSelection (feature, resolution) {
+
+    let zoom = zoomResolutionConvert.resolutionToZoom(resolution);
+
+    let width = 5;
+
+    if (zoom >= 18){
+        width = 10;
+    } else if (zoom >= 14) {
+        width = 8;
+    } else if (zoom >= 12) {
+        width = 7;
+    } else if (zoom >= 10) {
+        width = 6;
+    }
+
+    return [new ol.style.Style({
+        stroke: new ol.style.Stroke({
+            color: '#0074ff',
+            width: width
+        })
+    })];
+}
\ No newline at end of file
diff --git a/src/collections/ItsLayerCollection.js b/src/collections/ItsLayerCollection.js
index 78d4078..d446ae3 100644
--- a/src/collections/ItsLayerCollection.js
+++ b/src/collections/ItsLayerCollection.js
@@ -123,7 +123,7 @@ class ItsLayerCollection {
 
     /**
      * Create a collection of all ITS layers
-     * @param {Map} theMap the openlayers map
+     * @param {ol.Map} theMap the openlayers map
      * @param {Array} [exclude=[]] array of Its layer identifiers to exclude
      *
      * BLUE Bluetooth Detector - Bluetooth Detector
diff --git a/src/collections/LayerLegend.js b/src/collections/LayerLegend.js
index 6d7bb32..d852685 100644
--- a/src/collections/LayerLegend.js
+++ b/src/collections/LayerLegend.js
@@ -1,7 +1,7 @@
 /**
  * Created by gavorhes on 12/16/2015.
  */
-const $ = require('jquery');
+import $ from '../jquery';
 import provide from '../util/provide';
 import makeGuid from '../util/makeGuid';
 import mapMove from '../olHelpers/mapMove';
diff --git a/src/collections/Sliders.js b/src/collections/Sliders.js
index 3ea46e3..6254f48 100644
--- a/src/collections/Sliders.js
+++ b/src/collections/Sliders.js
@@ -3,7 +3,7 @@
  */
 import provide from '../util/provide';
 let nm = provide('collections');
-let $ = require('jquery');
+import $ from '../jquery';
 
 class _Slider {
 
diff --git a/src/jquery-plugin/dayRange.js b/src/jquery-plugin/dayRange.js
index 21037cd..75b3b51 100644
--- a/src/jquery-plugin/dayRange.js
+++ b/src/jquery-plugin/dayRange.js
@@ -1,4 +1,4 @@
-let $ = require('jquery');
+import $ from '../jquery';
 require('jquery-ui/datepicker');
 import provide from '../util/provide';
 let nm = provide('jQueryPlugin');
diff --git a/src/jquery-plugin/mediaControl.js b/src/jquery-plugin/mediaControl.js
index 10565ea..5fbe63a 100644
--- a/src/jquery-plugin/mediaControl.js
+++ b/src/jquery-plugin/mediaControl.js
@@ -1,4 +1,4 @@
-const $ = require('jquery');
+import $ from '../jquery';
 import provide from '../util/provide';
 let nm = provide('jQueryPlugin');
 
diff --git a/src/jquery-plugin/rangeChange.js b/src/jquery-plugin/rangeChange.js
index 19d8f69..eea8b88 100644
--- a/src/jquery-plugin/rangeChange.js
+++ b/src/jquery-plugin/rangeChange.js
@@ -4,7 +4,7 @@
  * Created by gavorhes on 11/2/2015.
  */
 
-const $ = require('jquery');
+import $ from '../jquery';
 let mouseIn = false;
 let mouseDown = false;
 let timeout = null;
diff --git a/src/jquery-plugin/rpPicker.js b/src/jquery-plugin/rpPicker.js
index dc7d958..05ffd49 100644
--- a/src/jquery-plugin/rpPicker.js
+++ b/src/jquery-plugin/rpPicker.js
@@ -1,4 +1,4 @@
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 
 import quickMap from '../olHelpers/quickMap';
diff --git a/src/jquery-plugin/ssaCorridorPicker.js b/src/jquery-plugin/ssaCorridorPicker.js
index eaded16..67855fb 100644
--- a/src/jquery-plugin/ssaCorridorPicker.js
+++ b/src/jquery-plugin/ssaCorridorPicker.js
@@ -1,4 +1,4 @@
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 
 import quickMap from '../olHelpers/quickMap';
diff --git a/src/jquery.js b/src/jquery.js
new file mode 100644
index 0000000..c863dfd
--- /dev/null
+++ b/src/jquery.js
@@ -0,0 +1,6 @@
+/**
+ * Created by gavorhes on 5/3/2016.
+ */
+const $ = require('jquery/dist/jquery.min');
+
+export default $;
diff --git a/src/layers/LayerBase.js b/src/layers/LayerBase.js
index ed07436..699e4a9 100644
--- a/src/layers/LayerBase.js
+++ b/src/layers/LayerBase.js
@@ -1,4 +1,4 @@
-const $ = require('jquery');
+import $ from '../jquery';
 import makeGuid from '../util/makeGuid';
 import * as zoomResolutionConvert from '../olHelpers/zoomResolutionConvert';
 import provide from '../util/provide';
diff --git a/src/layers/LayerBaseVector.js b/src/layers/LayerBaseVector.js
index 70af827..8dc214a 100644
--- a/src/layers/LayerBaseVector.js
+++ b/src/layers/LayerBaseVector.js
@@ -1,4 +1,4 @@
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import LayerBase from './LayerBase';
 import mapMove from '../olHelpers/mapMove';
@@ -183,7 +183,7 @@ class LayerBaseVector extends LayerBase {
 
     /**
      * get the style definition
-     * @type {ol.Style|function}
+     * @type {ol.Style|styleFunc}
      */
     get style() {
         return this._style;
diff --git a/src/layers/LayerBaseVectorEsri.js b/src/layers/LayerBaseVectorEsri.js
index 941cd3a..70862a3 100644
--- a/src/layers/LayerBaseVectorEsri.js
+++ b/src/layers/LayerBaseVectorEsri.js
@@ -1,7 +1,7 @@
 /**
  * Created by gavorhes on 11/2/2015.
  */
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import LayerBaseVector from './LayerBaseVector';
 import * as esriToOl from '../olHelpers/esriToOlStyle';
diff --git a/src/layers/LayerBaseVectorGeoJson.js b/src/layers/LayerBaseVectorGeoJson.js
index 73c381c..884a280 100644
--- a/src/layers/LayerBaseVectorGeoJson.js
+++ b/src/layers/LayerBaseVectorGeoJson.js
@@ -1,7 +1,7 @@
 /**
  * Created by gavorhes on 11/2/2015.
  */
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import LayerBaseVector from './LayerBaseVector';
 import provide from '../util/provide';
diff --git a/src/layers/LayerBaseXyzTile.js b/src/layers/LayerBaseXyzTile.js
index 3ccefd8..5b6ec6e 100644
--- a/src/layers/LayerBaseXyzTile.js
+++ b/src/layers/LayerBaseXyzTile.js
@@ -1,7 +1,7 @@
 /**
  * Created by gavorhes on 12/4/2015.
  */
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import LayerBase from './LayerBase';
 import * as esriToOl from '../olHelpers/esriToOlStyle';
diff --git a/src/layers/LayerEsriMapServer.js b/src/layers/LayerEsriMapServer.js
index 47a23d5..759775a 100644
--- a/src/layers/LayerEsriMapServer.js
+++ b/src/layers/LayerEsriMapServer.js
@@ -1,7 +1,7 @@
 /**
  * Created by gavorhes on 12/7/2015.
  */
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import LayerBase from './LayerBase';
 import * as esriToOl from '../olHelpers/esriToOlStyle';
diff --git a/src/layers/LayerItsInventory.js b/src/layers/LayerItsInventory.js
index cb793ff..e440543 100644
--- a/src/layers/LayerItsInventory.js
+++ b/src/layers/LayerItsInventory.js
@@ -2,7 +2,7 @@
  * Created by gavorhes on 12/8/2015.
  */
 
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import LayerBaseVectorGeoJson from './LayerBaseVectorGeoJson';
 import mapMove from '../olHelpers/mapMove';
diff --git a/src/layers/LayerRealEarthTile.js b/src/layers/LayerRealEarthTile.js
index c7bdddd..de86709 100644
--- a/src/layers/LayerRealEarthTile.js
+++ b/src/layers/LayerRealEarthTile.js
@@ -2,7 +2,7 @@
  * Created by gavorhes on 11/4/2015.
  */
 
-const $ = require('jquery');
+import $ from '../jquery';
 import LayerBaseXyzTile from './LayerBaseXyzTile';
 import RealEarthAnimateTile from '../mixin/RealEarthAnimateTile';
 let mixIns = require('es6-mixins');
diff --git a/src/mixin/RealEarthAnimate.js b/src/mixin/RealEarthAnimate.js
index 900105d..d2f38fc 100644
--- a/src/mixin/RealEarthAnimate.js
+++ b/src/mixin/RealEarthAnimate.js
@@ -3,7 +3,7 @@
  */
 import provide from '../util/provide';
 import mapPopup from '../olHelpers/mapPopup';
-const $ = require('jquery');
+import $ from '../jquery';
 let nm = provide('mixin');
 
 
diff --git a/src/olHelpers/mapMoveCls.js b/src/olHelpers/mapMoveCls.js
index c93ba7f..127ac8b 100644
--- a/src/olHelpers/mapMoveCls.js
+++ b/src/olHelpers/mapMoveCls.js
@@ -3,7 +3,7 @@
  */
 
 
-const $ = require('jquery');
+import $ from '../jquery';
 import MapInteractionBase from './mapInteractionBase';
 import * as checkDefined from '../util/checkDefined';
 import provide from '../util/provide';
diff --git a/src/olHelpers/mapPopupCls.js b/src/olHelpers/mapPopupCls.js
index 86c9256..8519140 100644
--- a/src/olHelpers/mapPopupCls.js
+++ b/src/olHelpers/mapPopupCls.js
@@ -2,7 +2,7 @@
  * Created by gavorhes on 11/3/2015.
  */
 
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import MapInteractionBase from './mapInteractionBase';
 import propertiesZoomStyle from '../olHelpers/propertiesZoomStyle';
diff --git a/src/olHelpers/quickMapBase.js b/src/olHelpers/quickMapBase.js
index 0990140..338af57 100644
--- a/src/olHelpers/quickMapBase.js
+++ b/src/olHelpers/quickMapBase.js
@@ -6,7 +6,7 @@
  * Created by gavorhes on 12/15/2015.
  */
 
-const $ = require('jquery');
+import $ from '../jquery';
 import ol from '../custom-ol';
 import provide from '../util/provide';
 import mapMove from './mapMove';
-- 
GitLab