<commit_before>Math.radians = function(deg) {
  return deg * (Math.PI / 180);
};

angular.module('app')
  .controller("MapCtrl", function(
    $scope,
    $rootScope,
    $state,
    $cordovaGeolocation,
    $location,
    $filter,
    $ionicHistory,
    $ionicModal,
    $ionicPopup,
    $ionicActionSheet,
    $ionicSideMenuDelegate,
    NewSpot,
    CurrentSpot,
    MapView,
    OfflineTilesFactory,
    SlippyTileNamesFactory,
    SpotsFactory,
    ViewExtentFactory,
    SymbologyFactory,
    MapLayerFactory) {

    // disable dragging back to ionic side menu because this affects drawing tools
    $ionicSideMenuDelegate.canDragContent(false);

    // ol3 map
    var map;

    // draw is a ol3 drawing interaction
    var draw;

    // added draw controls
    var drawControls = function(opt_options) {

      var options = opt_options || {};

      var drawPoint, drawLine, drawPoly;

      drawPoint = document.createElement('a');
      drawPoint.id = 'drawPointControl';
      drawPoint.href = '#drawPointControl';
      drawPoint.className = 'point';

      drawLine = document.createElement('a');
      drawLine.id = 'drawLineControl';
      drawLine.href = '#drawLineControl';
      drawLine.className = 'line';

      drawPoly = document.createElement('a');
      drawPoly.id = 'drawPolyControl';
      drawPoly.href = '#drawPolyControl';
      drawPoly.className = 'poly';

      var handleDrawPoint = function(e) {
        if (drawPoint.style.backgroundColor === '')
          drawPoint.style.backgroundColor = '#DDDDDD';
        else
          drawPoint.style.backgroundColor = '';
        drawLine.style.backgroundColor = '';
        drawPoly.style.backgroundColor = '';

        e.preventDefault();
        $scope.startDraw("Point");
      };
      var handleDrawLine = function(e) {
        if (drawLine.style.backgroundColor === '')
          drawLine.style.backgroundColor = '#DDDDDD';
        else
          drawLine.style.backgroundColor = '';
        drawPoint.style.backgroundColor = '';
        drawPoly.style.backgroundColor = '';

        e.preventDefault();
        $scope.startDraw("LineString");
      };
      var handleDrawPoly = function(e) {
        if (drawPoly.style.backgroundColor === '')
          drawPoly.style.backgroundColor = '#DDDDDD';
        else
          drawPoly.style.backgroundColor = '';
        drawPoint.style.backgroundColor = '';
        drawLine.style.backgroundColor = '';

        e.preventDefault();
        $scope.startDraw("Polygon");
      };

      drawPoint.addEventListener('click', handleDrawPoint, false);
      drawPoint.addEventListener('touchstart', handleDrawPoint, false);

      drawLine.addEventListener('click', handleDrawLine, false);
      drawLine.addEventListener('touchstart', handleDrawLine, false);

      drawPoly.addEventListener('click', handleDrawPoly, false);
      drawPoly.addEventListener('touchstart', handleDrawPoly, false);

      var element = document.createElement('div');
      element.className = 'draw-controls ol-unselectable';

      element.appendChild(drawPoint);
      element.appendChild(drawLine);
      element.appendChild(drawPoly);

      ol.control.Control.call(this, {
        element: element,
        target: options.target
      });

    };

    ol.inherits(drawControls, ol.control.Control);

    // initial map view, used for setting the view upon map creation
    var initialMapView = new ol.View({
      projection: 'EPSG:3857',
      center: [-11000000, 4600000],
      zoom: 4,
      minZoom: 4
    });

    // lets create a new map
    map = new ol.Map({
      target: 'mapdiv',
      view: initialMapView,
      // remove rotate icon from controls and add drawing controls
      controls: ol.control.defaults({
        rotate: false
      }),
      // turn off ability to rotate map via keyboard+mouse and using fingers on a mobile device
      interactions: ol.interaction.defaults({
        altShiftDragRotate: false,
        pinchRotate: false
      })
    });

    // restricts the map constraint to these coordinates
    // var mapExtent = ol.proj.transformExtent([-180,80,180,-80], 'EPSG:4326', 'EPSG:3857');

    var mlf = MapLayerFactory;

    // map layers
    var onlineLayer = mlf.getOnlineLayer();
    var onlineOverlayLayer = mlf.getOnlineOverlayLayer();
    var offlineLayer = mlf.getOfflineLayer();
    var offlineOverlayLayer = mlf.getOfflineOverlayLayer();
    var geolocationLayer = mlf.getGeolocationLayer();
    var featureLayer = mlf.getFeatureLayer();
    // var drawLayer = mlf.getDrawLayer();  //bug, TODO

    // layer where the drawing will go to
    var drawLayer = new ol.layer.Vector({
      name: 'drawLayer',
      source: new ol.source.Vector(),
      style: new ol.style.Style({
        fill: new ol.style.Fill({
          color: 'rgba(255, 255, 255, 0.2)'
        }),
        stroke: new ol.style.Stroke({
          color: '#ffcc33',
          width: 2
        }),
        image: new ol.style.Circle({
          radius: 7,
          fill: new ol.style.Fill({
            color: '#ffcc33'
          })
        })
      })
    });

    ///////////////////////////
    // map adding layers
    ///////////////////////////

    // add the feature layer to the map first
    map.addLayer(featureLayer);

    // add draw layer
    map.addLayer(drawLayer);

    // add geolocation layer
    map.addLayer(geolocationLayer);

    // add draw controls
    map.addControl(new drawControls());

    // layer switcher
    map.addControl(new ol.control.LayerSwitcher());

    // Popup
    var popup = new ol.Overlay.Popup();
    map.addOverlay(popup);

    /////////////////
    // END MAP LAYERS
    /////////////////

    // did we come back from a map provider?
    if (OfflineTilesFactory.getCurrentMapProvider()) {
      // yes -- then we need to change the current visible layer
      console.log("back at map, ", OfflineTilesFactory.getCurrentMapProvider());

      var onlineLayerCollection = onlineLayer.getLayers().getArray();

      _.each(onlineLayerCollection, function(layer) {
        if (layer.get('id') == OfflineTilesFactory.getCurrentMapProvider()) {
          layer.setVisible(true);
        } else {
          layer.setVisible(false);
        }
      });
    }

    // update the current visible layer, there is no return type as it updates the scope variable directly
    var getCurrentVisibleLayer = function() {

      // the first element in the layers array is our ol.layer.group that contains all the map tile layers
      var mapTileLayers = map.getLayers().getArray()[0].getLayers().getArray();

      // loop through and get the first layer that is visible
      var mapTileId = _.find(mapTileLayers, function(layer) {
        return layer.getVisible();
      });

      return mapTileId.get('id');
    };

    $scope.isOnline = function() {
      return navigator.onLine;
    };

    // Watch whether we have internet access or not
    $scope.$watch('isOnline()', function(online) {

      if (!online) {
        console.log("Offline");

        // remove the online maps
        map.removeLayer(onlineLayer);
        map.removeLayer(onlineOverlayLayer);

        // Add offline tile layer
        map.getLayers().insertAt(0, offlineLayer);
        map.getLayers().insertAt(1, offlineOverlayLayer);

        // clear the tiles, because we need to redraw if tiles have already been loaded to the screen
        map.getLayers().getArray()[0].getLayers().item(0).getSource().tileCache.clear();
        map.getLayers().getArray()[0].getLayers().item(1).getSource().tileCache.clear();
        map.getLayers().getArray()[1].getLayers().item(0).getSource().tileCache.clear();

        // re-render the map, grabs "new" tiles from storage
        map.render();
      } else {
        console.log("Online");

        // remove the offline layers
        map.removeLayer(offlineLayer);
        map.removeLayer(offlineOverlayLayer);

        // Add online map layer
        map.getLayers().insertAt(0, onlineLayer);
        map.getLayers().insertAt(1, onlineOverlayLayer);
      }
    });

    // cache the tiles in the current view but don't switch to the offline layer
    $scope.cacheOfflineTiles = function() {
      if (navigator.onLine) {
        // get the map extent
        var mapViewExtent = getMapViewExtent();

        // set the extent into the ViewExtentFactory
        ViewExtentFactory.setExtent(getCurrentVisibleLayer(), mapViewExtent.topRight, mapViewExtent.bottomLeft, mapViewExtent.zoom);

        // we set the current map provider so if we ever come back, we should try to use that map provider instead of the default provider
        OfflineTilesFactory.setCurrentMapProvider(getCurrentVisibleLayer());

        $location.path("/app/map/archiveTiles");
      } else
        $ionicPopup.alert({
          title: 'Offline!',
          template: 'You must be online to save a map!'
        });
    };

    // drawButtonActive used to keep state of which selected drawing tool is active
    $scope.drawButtonActive = null;

    $scope.startDraw = function(type, isFreeHand) {
      //if the type is already selected, we want to stop drawing
      if ($scope.drawButtonActive === type && !isFreeHand) {
        $scope.drawButtonActive = null;
        $scope.cancelDraw();
        return;
      } else {
        $scope.drawButtonActive = type;
      }

      console.log("isFreeHand, ", isFreeHand);

      // are we in freehand mode?
      if (isFreeHand) {
        // yes -- then disable the map drag pan
        map.getInteractions().forEach(function(interaction) {
          if (interaction instanceof ol.interaction.DragPan) {
            console.log(interaction);
            map.getInteractions().remove(interaction);
          }
        });
      }

      // is draw already set?
      if (draw !== null) {
        // yes, stop and remove the drawing interaction
        $scope.cancelDraw();
      }

      if (isFreeHand) {
        draw = new ol.interaction.Draw({
          source: drawLayer.getSource(),
          type: type,
          condition: ol.events.condition.singleClick,
          freehandCondition: ol.events.condition.noModifierKeys,
          snapTolerance: 96
        });
      } else {
        draw = new ol.interaction.Draw({
          source: drawLayer.getSource(),
          type: type
        });
      }


      draw.on("drawend", function(e) {

        // drawend event needs to end the drawing interaction
        map.removeInteraction(draw);

        // clear the drawing
        drawLayer.setSource(new ol.source.Vector());

        // we want a geojson object when the user finishes drawing
        var geojson = new ol.format.GeoJSON;

        // the actual geojson object that was drawn
        var geojsonObj = geojson.writeFeatureObject(e.feature, {
          featureProjection: "EPSG:3857"
        });


        if (isFreeHand) {
          console.log("Drawend : Freehand");

          // add the regular draw controls back
          map.addControl(new drawControls());

          // add the layer switcher controls back
          map.addControl(new ol.control.LayerSwitcher());

          // add the dragging back in
          map.addInteraction(new ol.interaction.DragPan());

          // does the drawing contain a kink, aka self-intersecting polygon?
          if (turf.kinks(geojsonObj).intersections.features.length === 0) {
            // no, good

            // contains all the lassoed objects
            var isLassoed = [];

            SpotsFactory.all().then(function(spots) {
              var mappedSpots = _.filter(spots, function(spot) {
                return spot.geometry;
              });
              _.each(mappedSpots, function(spot) {
                // if the spot is a point, we test using turf.inside
                // if the spot is a polygon or line, we test using turf.intersect

                var spotType = spot.properties.type;

                if (spotType === "point") {
                  // is the point inside the drawn polygon?
                  if (turf.inside(spot, geojsonObj)) {
                    isLassoed.push({
                      id: spot.properties.id,
                      name: spot.properties.name,
                      type: spot.properties.type
                    });
                  }
                }

                if (spotType === "line" || spotType === "polygon" || spotType === "group") {
                  // is the line or polygon within/intersected in the drawn polygon?
                  if (turf.intersect(spot, geojsonObj)) {
                    isLassoed.push({
                      id: spot.properties.id,
                      name: spot.properties.name,
                      type: spot.properties.type
                    });
                  }
                }
              });

              console.log("isLassoed, ", isLassoed);

              if (isLassoed.length == 0)
              {
                $ionicPopup.alert({
                  title: 'Empty Group',
                  template: "No spots are within or intersect the drawn poloygon."
                });
                return;
              }

              geojsonObj.properties = {
                type: "group",
                group_members: isLassoed
              };

              NewSpot.setNewSpot(geojsonObj);
              $state.go('spotTab.details');
            });
          } else {
            // contains a kink, aka self-intersecting polygon
            alert('cannot draw self-intersecting polygon');
          }
        } else {
          console.log("Drawend: Normal (not freehand)");

          // If there is already a current spot only update the geometry if the draw tool used
          // matches the required geometry for the Spot type
          if (CurrentSpot.getCurrentSpot()) {
            var curSpot = CurrentSpot.getCurrentSpot();
            switch (curSpot.properties.type) {
              case "point":
                if ($scope.drawButtonActive == "Point") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Station Spots must be drawn as Points. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
              case "line":
                if ($scope.drawButtonActive == "LineString") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Contacts and Traces Spots must be drawn as Lines. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
              case "polygon":
                if ($scope.drawButtonActive == "Polygon") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Rock Description Spots must be drawn as Polygons. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
              case "group":
                if ($scope.drawButtonActive == "Polygon") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Spot Groups must be drawn as Polygons. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
            }
          }
          // Initialize new Spot
          else {
            var goTo = "spotTab.details";
            switch ($scope.drawButtonActive) {
              case "Point":
                geojsonObj.properties = { type: "point" };
                break;
              case "LineString":
                geojsonObj.properties = { type: "line" };
                break;
              case "Polygon":
                geojsonObj.properties = { type: "polygon"};
                goTo = "spotTab.rockdescription";
                break;
            }
            NewSpot.setNewSpot(geojsonObj);
            $state.go(goTo);
          }
        }
      });
      map.addInteraction(draw);
    };

    // If the map is moved save the view
    map.on('moveend', function(evt) {
      MapView.setMapView(map.getView());

      // update the zoom information control
      $scope.currentZoom = evt.map.getView().getZoom();
      $scope.$apply();
    });

    // Zoom to the extent of the spots, if that fails geolocate the user
    $scope.zoomToSpotsExtent = function() {
      // nope, we have NO mapview set, so...

      // Loop through all spots and create ol vector layers
      SpotsFactory.all().then(function(spots) {

        // Remove spots that don't have a geometry defined or
        // are mapped on an image
        spots = _.reject(spots, function(spot) {
          return !_.has(spot, "geometry") || _.has(spot.properties, "image_map");
        });

        // do we even have any spots?
        if (spots.length > 0) {
          console.log("found spots, attempting to get the center of all spots and change the map view to that");
          var cr = new CoordinateRange(spots);
          var newExtent = ol.extent.boundingExtent(_.compact(cr._getAllCoordinates()));
          var newExtentCenter = ol.extent.getCenter(newExtent);

          // fly-by map animation
          var duration = 2000;
          var start = +new Date();
          var pan = ol.animation.pan({
            duration: duration,
            source: map.getView().getCenter(),
            start: start
          });
          var bounce = ol.animation.bounce({
            duration: duration,
            resolution: map.getView().getResolution(),
            start: start
          });
          map.beforeRender(pan, bounce);

          if (spots.length === 1) {
            // we just have a single spot, so we should fixate the resolution manually
            initialMapView.setCenter(ol.proj.transform([newExtentCenter[0], newExtentCenter[1]], 'EPSG:4326', 'EPSG:3857'));
            initialMapView.setZoom(15);
          } else {
            // we have multiple spots -- need to create the new view with the new center
            var newView = new ol.View({
              center: ol.proj.transform([newExtentCenter[0], newExtentCenter[1]], 'EPSG:4326', 'EPSG:3857')
            });

            map.setView(newView);
            map.getView().fit(ol.proj.transformExtent(newExtent, 'EPSG:4326', 'EPSG:3857'), map.getSize());
          }
        }
        // no spots either, then attempt to geolocate the user
        else {
          console.log("no spots found, attempting to geolocate");
          // attempt to geolocate instead
          $cordovaGeolocation.getCurrentPosition({
              maximumAge: 0,
              timeout: 10000,
              enableHighAccuracy: true
            })
            .then(function(position) {
              var lat = position.coords.latitude;
              var lng = position.coords.longitude;

              console.log("initial getLocation ", [lat, lng]);

              var newView = new ol.View({
                center: ol.proj.transform([lng, lat], 'EPSG:4326', 'EPSG:3857'),
                zoom: 17,
                minZoom: 4
              });
              map.setView(newView);
            }, function(err) {
              // uh oh, cannot geolocate, nor have any spots
              $ionicPopup.alert({
                title: 'Alert!',
                template: 'Could not geolocate your position.  Defaulting you to 0,0'
              });
              var newView = new ol.View({
                center: ol.proj.transform([0, 0], 'EPSG:4326', 'EPSG:3857'),
                zoom: 4,
                minZoom: 4
              });
              map.setView(newView);
            });
        }
      });
    };

    //  do we currently have mapview set?  if so, we should reset the map view to that first
    if (MapView.getMapView()) {
      console.log("have mapview set, changing map view to that");
      map.setView(MapView.getMapView());
    }
    else
      $scope.zoomToSpotsExtent();

    $scope.cancelDraw = function() {
      if (draw === null) return;
      map.removeInteraction(draw);
    };

    // Point object
    var Point = function(lat, lng) {
      this.lat = lat;
      this.lng = lng;
    };

    var getMapViewExtent = function() {
      var extent = map.getView().calculateExtent(map.getSize());
      var zoom = map.getView().getZoom();
      var bottomLeft = ol.proj.transform(ol.extent.getBottomLeft(extent),
        'EPSG:3857', 'EPSG:4326');
      var topRight = ol.proj.transform(ol.extent.getTopRight(extent),
        'EPSG:3857', 'EPSG:4326');

      return {
        topRight: new Point(topRight[1], topRight[0]),
        bottomLeft: new Point(bottomLeft[1], bottomLeft[0]),
        zoom: zoom
      };
    };

    // we want to load all the geojson markers from the persistence storage onto the map
    // creates a ol vector layer for supplied geojson object
    var geojsonToVectorLayer = function(geojson) {

      // textStyle is a function because each point has a different text associated
      var textStyle = function(text) {
        return new ol.style.Text({
          font: '12px Calibri,sans-serif',
          text: text,
          fill: new ol.style.Fill({
            color: '#000'
          }),
          stroke: new ol.style.Stroke({
            color: '#fff',
            width: 3
          })
        });
      };

      var getIconForFeature = function(feature) {

        var rotation = feature.get('strike') || feature.get('trend') || 0;
        var orientation = feature.get('dip') || feature.get('plunge') || 0;
        var feature_type = feature.get('planar_feature_type') || feature.get('linear_feature_type');

        // Is this a planar or linear feature? If both use planar.
        var pORl = feature.get('planar_feature_type') ? "planar" : "linear";
        // If feature_type is undefined use planar as default so get the default planar symbol
        pORl = feature_type ? pORl : "planar";

        return new ol.style.Icon({
          anchorXUnits: 'pixels',
          anchorYUnits: 'pixels',
          opacity: 1,
          rotation: Math.radians(rotation),
          src: SymbologyFactory.getSymbolPath(feature_type, pORl, orientation),
          scale: 0.05
        });
      };

      // Set styles for points, lines and polygon and groups
      function styleFunction(feature, resolution) {
        var styles = [];
        var pointText = (feature.get('plunge') != undefined) ? feature.get('plunge').toString() : feature.get('label');
        pointText = (feature.get('dip') != undefined) ? feature.get('dip').toString() : pointText;

        switch(feature.get("type")) {
          case "point":
            var pointStyle = [
              new ol.style.Style({
                image: getIconForFeature(feature),
                text: textStyle(pointText)
              })//,
           //   new ol.style.Style({
           //     text: textStyle(pointText)<commit_msg>spot label is rotational aware<commit_after>Math.radians = function(deg) {
  return deg * (Math.PI / 180);
};

angular.module('app')
  .controller("MapCtrl", function(
    $scope,
    $rootScope,
    $state,
    $cordovaGeolocation,
    $location,
    $filter,
    $ionicHistory,
    $ionicModal,
    $ionicPopup,
    $ionicActionSheet,
    $ionicSideMenuDelegate,
    NewSpot,
    CurrentSpot,
    MapView,
    OfflineTilesFactory,
    SlippyTileNamesFactory,
    SpotsFactory,
    ViewExtentFactory,
    SymbologyFactory,
    MapLayerFactory) {

    // disable dragging back to ionic side menu because this affects drawing tools
    $ionicSideMenuDelegate.canDragContent(false);

    // ol3 map
    var map;

    // draw is a ol3 drawing interaction
    var draw;

    // added draw controls
    var drawControls = function(opt_options) {

      var options = opt_options || {};

      var drawPoint, drawLine, drawPoly;

      drawPoint = document.createElement('a');
      drawPoint.id = 'drawPointControl';
      drawPoint.href = '#drawPointControl';
      drawPoint.className = 'point';

      drawLine = document.createElement('a');
      drawLine.id = 'drawLineControl';
      drawLine.href = '#drawLineControl';
      drawLine.className = 'line';

      drawPoly = document.createElement('a');
      drawPoly.id = 'drawPolyControl';
      drawPoly.href = '#drawPolyControl';
      drawPoly.className = 'poly';

      var handleDrawPoint = function(e) {
        if (drawPoint.style.backgroundColor === '')
          drawPoint.style.backgroundColor = '#DDDDDD';
        else
          drawPoint.style.backgroundColor = '';
        drawLine.style.backgroundColor = '';
        drawPoly.style.backgroundColor = '';

        e.preventDefault();
        $scope.startDraw("Point");
      };
      var handleDrawLine = function(e) {
        if (drawLine.style.backgroundColor === '')
          drawLine.style.backgroundColor = '#DDDDDD';
        else
          drawLine.style.backgroundColor = '';
        drawPoint.style.backgroundColor = '';
        drawPoly.style.backgroundColor = '';

        e.preventDefault();
        $scope.startDraw("LineString");
      };
      var handleDrawPoly = function(e) {
        if (drawPoly.style.backgroundColor === '')
          drawPoly.style.backgroundColor = '#DDDDDD';
        else
          drawPoly.style.backgroundColor = '';
        drawPoint.style.backgroundColor = '';
        drawLine.style.backgroundColor = '';

        e.preventDefault();
        $scope.startDraw("Polygon");
      };

      drawPoint.addEventListener('click', handleDrawPoint, false);
      drawPoint.addEventListener('touchstart', handleDrawPoint, false);

      drawLine.addEventListener('click', handleDrawLine, false);
      drawLine.addEventListener('touchstart', handleDrawLine, false);

      drawPoly.addEventListener('click', handleDrawPoly, false);
      drawPoly.addEventListener('touchstart', handleDrawPoly, false);

      var element = document.createElement('div');
      element.className = 'draw-controls ol-unselectable';

      element.appendChild(drawPoint);
      element.appendChild(drawLine);
      element.appendChild(drawPoly);

      ol.control.Control.call(this, {
        element: element,
        target: options.target
      });

    };

    ol.inherits(drawControls, ol.control.Control);

    // initial map view, used for setting the view upon map creation
    var initialMapView = new ol.View({
      projection: 'EPSG:3857',
      center: [-11000000, 4600000],
      zoom: 4,
      minZoom: 4
    });

    // lets create a new map
    map = new ol.Map({
      target: 'mapdiv',
      view: initialMapView,
      // remove rotate icon from controls and add drawing controls
      controls: ol.control.defaults({
        rotate: false
      }),
      // turn off ability to rotate map via keyboard+mouse and using fingers on a mobile device
      interactions: ol.interaction.defaults({
        altShiftDragRotate: false,
        pinchRotate: false
      })
    });

    // restricts the map constraint to these coordinates
    // var mapExtent = ol.proj.transformExtent([-180,80,180,-80], 'EPSG:4326', 'EPSG:3857');

    var mlf = MapLayerFactory;

    // map layers
    var onlineLayer = mlf.getOnlineLayer();
    var onlineOverlayLayer = mlf.getOnlineOverlayLayer();
    var offlineLayer = mlf.getOfflineLayer();
    var offlineOverlayLayer = mlf.getOfflineOverlayLayer();
    var geolocationLayer = mlf.getGeolocationLayer();
    var featureLayer = mlf.getFeatureLayer();
    // var drawLayer = mlf.getDrawLayer();  //bug, TODO

    // layer where the drawing will go to
    var drawLayer = new ol.layer.Vector({
      name: 'drawLayer',
      source: new ol.source.Vector(),
      style: new ol.style.Style({
        fill: new ol.style.Fill({
          color: 'rgba(255, 255, 255, 0.2)'
        }),
        stroke: new ol.style.Stroke({
          color: '#ffcc33',
          width: 2
        }),
        image: new ol.style.Circle({
          radius: 7,
          fill: new ol.style.Fill({
            color: '#ffcc33'
          })
        })
      })
    });

    ///////////////////////////
    // map adding layers
    ///////////////////////////

    // add the feature layer to the map first
    map.addLayer(featureLayer);

    // add draw layer
    map.addLayer(drawLayer);

    // add geolocation layer
    map.addLayer(geolocationLayer);

    // add draw controls
    map.addControl(new drawControls());

    // layer switcher
    map.addControl(new ol.control.LayerSwitcher());

    // Popup
    var popup = new ol.Overlay.Popup();
    map.addOverlay(popup);

    /////////////////
    // END MAP LAYERS
    /////////////////

    // did we come back from a map provider?
    if (OfflineTilesFactory.getCurrentMapProvider()) {
      // yes -- then we need to change the current visible layer
      console.log("back at map, ", OfflineTilesFactory.getCurrentMapProvider());

      var onlineLayerCollection = onlineLayer.getLayers().getArray();

      _.each(onlineLayerCollection, function(layer) {
        if (layer.get('id') == OfflineTilesFactory.getCurrentMapProvider()) {
          layer.setVisible(true);
        } else {
          layer.setVisible(false);
        }
      });
    }

    // update the current visible layer, there is no return type as it updates the scope variable directly
    var getCurrentVisibleLayer = function() {

      // the first element in the layers array is our ol.layer.group that contains all the map tile layers
      var mapTileLayers = map.getLayers().getArray()[0].getLayers().getArray();

      // loop through and get the first layer that is visible
      var mapTileId = _.find(mapTileLayers, function(layer) {
        return layer.getVisible();
      });

      return mapTileId.get('id');
    };

    $scope.isOnline = function() {
      return navigator.onLine;
    };

    // Watch whether we have internet access or not
    $scope.$watch('isOnline()', function(online) {

      if (!online) {
        console.log("Offline");

        // remove the online maps
        map.removeLayer(onlineLayer);
        map.removeLayer(onlineOverlayLayer);

        // Add offline tile layer
        map.getLayers().insertAt(0, offlineLayer);
        map.getLayers().insertAt(1, offlineOverlayLayer);

        // clear the tiles, because we need to redraw if tiles have already been loaded to the screen
        map.getLayers().getArray()[0].getLayers().item(0).getSource().tileCache.clear();
        map.getLayers().getArray()[0].getLayers().item(1).getSource().tileCache.clear();
        map.getLayers().getArray()[1].getLayers().item(0).getSource().tileCache.clear();

        // re-render the map, grabs "new" tiles from storage
        map.render();
      } else {
        console.log("Online");

        // remove the offline layers
        map.removeLayer(offlineLayer);
        map.removeLayer(offlineOverlayLayer);

        // Add online map layer
        map.getLayers().insertAt(0, onlineLayer);
        map.getLayers().insertAt(1, onlineOverlayLayer);
      }
    });

    // cache the tiles in the current view but don't switch to the offline layer
    $scope.cacheOfflineTiles = function() {
      if (navigator.onLine) {
        // get the map extent
        var mapViewExtent = getMapViewExtent();

        // set the extent into the ViewExtentFactory
        ViewExtentFactory.setExtent(getCurrentVisibleLayer(), mapViewExtent.topRight, mapViewExtent.bottomLeft, mapViewExtent.zoom);

        // we set the current map provider so if we ever come back, we should try to use that map provider instead of the default provider
        OfflineTilesFactory.setCurrentMapProvider(getCurrentVisibleLayer());

        $location.path("/app/map/archiveTiles");
      } else
        $ionicPopup.alert({
          title: 'Offline!',
          template: 'You must be online to save a map!'
        });
    };

    // drawButtonActive used to keep state of which selected drawing tool is active
    $scope.drawButtonActive = null;

    $scope.startDraw = function(type, isFreeHand) {
      //if the type is already selected, we want to stop drawing
      if ($scope.drawButtonActive === type && !isFreeHand) {
        $scope.drawButtonActive = null;
        $scope.cancelDraw();
        return;
      } else {
        $scope.drawButtonActive = type;
      }

      console.log("isFreeHand, ", isFreeHand);

      // are we in freehand mode?
      if (isFreeHand) {
        // yes -- then disable the map drag pan
        map.getInteractions().forEach(function(interaction) {
          if (interaction instanceof ol.interaction.DragPan) {
            console.log(interaction);
            map.getInteractions().remove(interaction);
          }
        });
      }

      // is draw already set?
      if (draw !== null) {
        // yes, stop and remove the drawing interaction
        $scope.cancelDraw();
      }

      if (isFreeHand) {
        draw = new ol.interaction.Draw({
          source: drawLayer.getSource(),
          type: type,
          condition: ol.events.condition.singleClick,
          freehandCondition: ol.events.condition.noModifierKeys,
          snapTolerance: 96
        });
      } else {
        draw = new ol.interaction.Draw({
          source: drawLayer.getSource(),
          type: type
        });
      }


      draw.on("drawend", function(e) {

        // drawend event needs to end the drawing interaction
        map.removeInteraction(draw);

        // clear the drawing
        drawLayer.setSource(new ol.source.Vector());

        // we want a geojson object when the user finishes drawing
        var geojson = new ol.format.GeoJSON;

        // the actual geojson object that was drawn
        var geojsonObj = geojson.writeFeatureObject(e.feature, {
          featureProjection: "EPSG:3857"
        });


        if (isFreeHand) {
          console.log("Drawend : Freehand");

          // add the regular draw controls back
          map.addControl(new drawControls());

          // add the layer switcher controls back
          map.addControl(new ol.control.LayerSwitcher());

          // add the dragging back in
          map.addInteraction(new ol.interaction.DragPan());

          // does the drawing contain a kink, aka self-intersecting polygon?
          if (turf.kinks(geojsonObj).intersections.features.length === 0) {
            // no, good

            // contains all the lassoed objects
            var isLassoed = [];

            SpotsFactory.all().then(function(spots) {
              var mappedSpots = _.filter(spots, function(spot) {
                return spot.geometry;
              });
              _.each(mappedSpots, function(spot) {
                // if the spot is a point, we test using turf.inside
                // if the spot is a polygon or line, we test using turf.intersect

                var spotType = spot.properties.type;

                if (spotType === "point") {
                  // is the point inside the drawn polygon?
                  if (turf.inside(spot, geojsonObj)) {
                    isLassoed.push({
                      id: spot.properties.id,
                      name: spot.properties.name,
                      type: spot.properties.type
                    });
                  }
                }

                if (spotType === "line" || spotType === "polygon" || spotType === "group") {
                  // is the line or polygon within/intersected in the drawn polygon?
                  if (turf.intersect(spot, geojsonObj)) {
                    isLassoed.push({
                      id: spot.properties.id,
                      name: spot.properties.name,
                      type: spot.properties.type
                    });
                  }
                }
              });

              console.log("isLassoed, ", isLassoed);

              if (isLassoed.length == 0)
              {
                $ionicPopup.alert({
                  title: 'Empty Group',
                  template: "No spots are within or intersect the drawn poloygon."
                });
                return;
              }

              geojsonObj.properties = {
                type: "group",
                group_members: isLassoed
              };

              NewSpot.setNewSpot(geojsonObj);
              $state.go('spotTab.details');
            });
          } else {
            // contains a kink, aka self-intersecting polygon
            alert('cannot draw self-intersecting polygon');
          }
        } else {
          console.log("Drawend: Normal (not freehand)");

          // If there is already a current spot only update the geometry if the draw tool used
          // matches the required geometry for the Spot type
          if (CurrentSpot.getCurrentSpot()) {
            var curSpot = CurrentSpot.getCurrentSpot();
            switch (curSpot.properties.type) {
              case "point":
                if ($scope.drawButtonActive == "Point") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Station Spots must be drawn as Points. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
              case "line":
                if ($scope.drawButtonActive == "LineString") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Contacts and Traces Spots must be drawn as Lines. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
              case "polygon":
                if ($scope.drawButtonActive == "Polygon") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Rock Description Spots must be drawn as Polygons. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
              case "group":
                if ($scope.drawButtonActive == "Polygon") {
                  curSpot.geometry = geojsonObj.geometry;
                  CurrentSpot.setCurrentSpot(curSpot);
                } else
                  $ionicPopup.alert({
                    title: 'Geometry Mismatch!',
                    template: "Spot Groups must be drawn as Polygons. Draw Again."
                  });
                $state.go('spotTab.georeference');
                break;
            }
          }
          // Initialize new Spot
          else {
            var goTo = "spotTab.details";
            switch ($scope.drawButtonActive) {
              case "Point":
                geojsonObj.properties = { type: "point" };
                break;
              case "LineString":
                geojsonObj.properties = { type: "line" };
                break;
              case "Polygon":
                geojsonObj.properties = { type: "polygon"};
                goTo = "spotTab.rockdescription";
                break;
            }
            NewSpot.setNewSpot(geojsonObj);
            $state.go(goTo);
          }
        }
      });
      map.addInteraction(draw);
    };

    // If the map is moved save the view
    map.on('moveend', function(evt) {
      MapView.setMapView(map.getView());

      // update the zoom information control
      $scope.currentZoom = evt.map.getView().getZoom();
      $scope.$apply();
    });

    // Zoom to the extent of the spots, if that fails geolocate the user
    $scope.zoomToSpotsExtent = function() {
      // nope, we have NO mapview set, so...

      // Loop through all spots and create ol vector layers
      SpotsFactory.all().then(function(spots) {

        // Remove spots that don't have a geometry defined or
        // are mapped on an image
        spots = _.reject(spots, function(spot) {
          return !_.has(spot, "geometry") || _.has(spot.properties, "image_map");
        });

        // do we even have any spots?
        if (spots.length > 0) {
          console.log("found spots, attempting to get the center of all spots and change the map view to that");
          var cr = new CoordinateRange(spots);
          var newExtent = ol.extent.boundingExtent(_.compact(cr._getAllCoordinates()));
          var newExtentCenter = ol.extent.getCenter(newExtent);

          // fly-by map animation
          var duration = 2000;
          var start = +new Date();
          var pan = ol.animation.pan({
            duration: duration,
            source: map.getView().getCenter(),
            start: start
          });
          var bounce = ol.animation.bounce({
            duration: duration,
            resolution: map.getView().getResolution(),
            start: start
          });
          map.beforeRender(pan, bounce);

          if (spots.length === 1) {
            // we just have a single spot, so we should fixate the resolution manually
            initialMapView.setCenter(ol.proj.transform([newExtentCenter[0], newExtentCenter[1]], 'EPSG:4326', 'EPSG:3857'));
            initialMapView.setZoom(15);
          } else {
            // we have multiple spots -- need to create the new view with the new center
            var newView = new ol.View({
              center: ol.proj.transform([newExtentCenter[0], newExtentCenter[1]], 'EPSG:4326', 'EPSG:3857')
            });

            map.setView(newView);
            map.getView().fit(ol.proj.transformExtent(newExtent, 'EPSG:4326', 'EPSG:3857'), map.getSize());
          }
        }
        // no spots either, then attempt to geolocate the user
        else {
          console.log("no spots found, attempting to geolocate");
          // attempt to geolocate instead
          $cordovaGeolocation.getCurrentPosition({
              maximumAge: 0,
              timeout: 10000,
              enableHighAccuracy: true
            })
            .then(function(position) {
              var lat = position.coords.latitude;
              var lng = position.coords.longitude;

              console.log("initial getLocation ", [lat, lng]);

              var newView = new ol.View({
                center: ol.proj.transform([lng, lat], 'EPSG:4326', 'EPSG:3857'),
                zoom: 17,
                minZoom: 4
              });
              map.setView(newView);
            }, function(err) {
              // uh oh, cannot geolocate, nor have any spots
              $ionicPopup.alert({
                title: 'Alert!',
                template: 'Could not geolocate your position.  Defaulting you to 0,0'
              });
              var newView = new ol.View({
                center: ol.proj.transform([0, 0], 'EPSG:4326', 'EPSG:3857'),
                zoom: 4,
                minZoom: 4
              });
              map.setView(newView);
            });
        }
      });
    };

    //  do we currently have mapview set?  if so, we should reset the map view to that first
    if (MapView.getMapView()) {
      console.log("have mapview set, changing map view to that");
      map.setView(MapView.getMapView());
    }
    else
      $scope.zoomToSpotsExtent();

    $scope.cancelDraw = function() {
      if (draw === null) return;
      map.removeInteraction(draw);
    };

    // Point object
    var Point = function(lat, lng) {
      this.lat = lat;
      this.lng = lng;
    };

    var getMapViewExtent = function() {
      var extent = map.getView().calculateExtent(map.getSize());
      var zoom = map.getView().getZoom();
      var bottomLeft = ol.proj.transform(ol.extent.getBottomLeft(extent),
        'EPSG:3857', 'EPSG:4326');
      var topRight = ol.proj.transform(ol.extent.getTopRight(extent),
        'EPSG:3857', 'EPSG:4326');

      return {
        topRight: new Point(topRight[1], topRight[0]),
        bottomLeft: new Point(bottomLeft[1], bottomLeft[0]),
        zoom: zoom
      };
    };

    // we want to load all the geojson markers from the persistence storage onto the map
    // creates a ol vector layer for supplied geojson object
    var geojsonToVectorLayer = function(geojson) {

      // textStyle is a function because each point has a different text associated
      var textStyle = function(text, rotation) {
        return new ol.style.Text({
          font: '12px Calibri,sans-serif',
          text: '          ' + text,  // we pad with spaces due to rotational offset
          textAlign: 'center',
          rotation: Math.radians(rotation) * (-1),
          fill: new ol.style.Fill({
            color: '#000'
          }),
          stroke: new ol.style.Stroke({
            color: '#fff',
            width: 3
          })
        });
      };

      var getIconForFeature = function(feature) {

        var rotation = feature.get('strike') || feature.get('trend') || 0;
        var orientation = feature.get('dip') || feature.get('plunge') || 0;
        var feature_type = feature.get('planar_feature_type') || feature.get('linear_feature_type');

        // Is this a planar or linear feature? If both use planar.
        var pORl = feature.get('planar_feature_type') ? "planar" : "linear";
        // If feature_type is undefined use planar as default so get the default planar symbol
        pORl = feature_type ? pORl : "planar";

        return new ol.style.Icon({
          anchorXUnits: 'fraction',
          anchorYUnits: 'fraction',
          opacity: 1,
          rotation: Math.radians(rotation) * (-1),
          src: SymbologyFactory.getSymbolPath(feature_type, pORl, orientation),
          scale: 0.05
        });
      };

      // Set styles for points, lines and polygon and groups
      function styleFunction(feature, resolution) {
        var styles = [];
        var pointText = (feature.get('plunge') != undefined) ? feature.get('plunge').toString() : feature.get('label');
        pointText = (feature.get('dip') != undefined) ? feature.get('dip').toString() : pointText;

        var rotation = feature.get('strike') || feature.get('trend') || 0;

        switch(feature.get("type")) {
          case "point":
            var pointStyle = [
              new ol.style.Style({
                image: getIconForFeature(feature),
                text: textStyle(pointText, rotation)
              })//,
           //   new ol.style.Style({
           //     text: textStyle(pointText)
{'fraction_non_alphanumeric': 0.11602853091201558, 'fraction_numerical': 0.007640696763815692, 'mean_word_length': 1.8543466441128496, 'pattern_counts': {'":': 16, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '209206', 'n_tokens_mistral': 13331, 'n_tokens_neox': 12491, 'n_words': 3234}