<commit_before>$(document).ready(startup);

/* The Big Nested Function
==========================*/

// Print to ensure file is loaded

function startup() {
  console.log("trailhead.js");

  // Map generated in CfA Account

  var MAPBOX_MAP_ID = "codeforamerica.map-4urpxezk";
  var AKRON = {
    lat: 41.1,
    lng: -81.5
  };

  var METERSTOMILESFACTOR = 0.00062137;
  var MAX_ZOOM = 14;
  var MIN_ZOOM = 12;
  var SECONDARY_TRAIL_ZOOM = 13;
  var SHORT_MAX_DISTANCE = 1.5;
  var MEDIUM_MAX_DISTANCE = 4.0;
  var SHOW_ALL_TRAILS = 1;
  var USE_LOCAL = 1; // Set this to a true value to preload/use a local trail segment cache
  var API_HOST = "http://127.0.0.1:3000";

  var map = {};
  var trailData = {}; // all of the trails metadata (from traildata table), with trail name as key
  // { *cartodb_id*: { geometry: null,  // this field is added for CartoDB's approval
  //                  properties: { cartodb_id: *uniqueID*,
  //                                length: *length of trail in meters*,
  //                                name: *name of trail*,
  //                                source: *whose data this info came from*,
  //                              }
  //                }
  // }
  var trailheads = []; // all trailheads (from trailsegments)
  // [ {  marker: *Leaflet marker*,
  //      trails: *[array of matched trail IDs],
  //      popupContent: *HTML of Leaflet popup*,
  //      properties: { cartodb_id: *uniqueID*,
  //                    distance: *from current location in meters*,
  //                    name: *name*,
  //                    source: *whose data this info came from*,
  //                    trail1: *trail at this trailhead*,
  //                    trail2: *trail at this trailhead*,
  //                    trail3: *trail at this trailhead*,
  //                    updated_at: *update time*,
  //                    created_at: *creation time*,
  //                    wkt: *original wkt for trailhead point*
  //                  }, 
  //   }[, ...}]
  // ]
  var trailSegments = [];


  var currentMultiTrailLayer = {}; // We have to know if a trail layer is already being displayed, so we can remove it
  var currentTrailLayers = [];
  var currentHighlightedTrailLayer = {};
  var currentLocation = {};
  var currentFilters = {};
  var currentDetailTrail = null;
  var userMarker = null;
  var allSegmentLayer = null;

  // Prepping for API calls (defining data for the call)
  var endpoint = "http://cfa.cartodb.com/api/v2/sql/";

  // comment these/uncomment the next set to switch between tables
  var TRAILHEADS_TABLE = "summit_trailheads";
  var TRAILSEGMENTS_TABLE = "summit_trailsegments";
  var TRAILDATA_TABLE = "summit_traildata";

  // var TRAILHEADS_TABLE = "summit_trailheads_test";
  // var TRAILSEGMENTS_TABLE = "summit_trail_segments_test";
  // var TRAILDATA_TABLE = "summit_traildata_test";


  // UI events to react to

  $("#redoSearch").click(reorderTrailsWithNewLocation);
  $(document).on('click', '.trailhead-trailname', trailnameClick);
  $(document).on('click', '.closeDetail', closeDetailPanel);
  $("#showAllTrailSegments").click(function() {
    getAllTrailPaths(drawMultiTrailLayer);
  });
  $("#showUnusedTrailSegments").click(function() {
    getAllTrailPaths(filterKnownTrails);
  });
  $(document).on('change', '.selectpicker', filterChangeHandler);


  // -----------------------------------
  // Kick things off

  initialSetup();

  // -----------------------------------

  //  Difficulty Filtering
  //  Length Filtering
  //  Helper functions

  function filterTrailList() {
    // defining the function, not calling it
    // click events / activation of checkboxes
    // change currentFilters object
    // apply current Filters to trailData
    // initialSetup performs the mapping
  }


  // The next three functions perform trailhead/trail mapping
  // on a) initial startup, b) requested re-sort of trailheads based on the map, 
  // and c) a change in filter settings
  // They all call addTrailDataToTrailheads() as their final action 
  // --------------------------------------------------------------

  // on startup, get location, display the map,
  // get and display the trailheads, populate trailData, 
  // add trailData to trailheads

  function initialSetup() {
    console.log("initialSetup");
    setCurrentLocation();
    displayInitialMap();
    getOrderedTrailheads(currentLocation, function() {
      getTrailData(function() {
        addTrailDataToTrailheads(trailData);
      });
    });
    if (USE_LOCAL) {
      getTrailSegments(function() {
        //console.log(trailSegments);
      });
    }
  }

  // set currentLocation to the center of the currently viewed map
  // then get the ordered trailheads and add trailData to trailheads

  function reorderTrailsWithNewLocation() {
    setCurrentLocationFromMap();
    getOrderedTrailheads(currentLocation, function() {
      addTrailDataToTrailheads(trailData);
    });
  }

  // This should probably be called after a UI change in the filters,
  // and the accompanying change to the currentFilters

  function applyFilterChange(currentFilters, trailData) {
    // TODO:
    var filteredTrailData = $.extend(true, {}, trailData);
    $.each(trailData, function(trail_id, trail) {
      if (currentFilters.activityFilter) {
        for (var i = 0; i < currentFilters.activityFilter.length; i++) {
          var activity = currentFilters.activityFilter[i];
          if (trail.properties[activity].toLowerCase() !== "true") {
            delete filteredTrailData[trail_id];
          }
        }
      }
      if (currentFilters.difficultyFilter) {
        var include = false;
        for (var j = 0; j < currentFilters.difficultyFilter.length; j++) {
          var difficulty = currentFilters.difficultyFilter[j];
          if (trail.properties.difficulty.toLowerCase() == difficulty.toLowerCase()) {
            include = true;
            break;
          }
        }
        if (!include) {
          delete filteredTrailData[trail_id];
        }
      }
      if (currentFilters.lengthFilter) {
        var distInclude = false;
        for (var k = 0; k < currentFilters.lengthFilter.length; k++) {
          var distance = currentFilters.lengthFilter[k];
          var trailDist = trail.properties["length"];
          if ((distance.toLowerCase() == "short" && trailDist <= SHORT_MAX_DISTANCE) ||
            (distance.toLowerCase() == "medium" && trailDist > SHORT_MAX_DISTANCE && trailDist <= MEDIUM_MAX_DISTANCE) ||
            (distance.toLowerCase() == "long" && trailDist > MEDIUM_MAX_DISTANCE)) {
            distInclude = true;
            break;
          }
        }
        if (!distInclude) {
          delete filteredTrailData[trail_id];
        }
      }
    });

    // loop through trailData
    // apply currentFilters object
    // put trails we want to display into filteredTrailData
    addTrailDataToTrailheads(filteredTrailData);
  }

  function filterChangeHandler(e) {
    var $currentTarget = $(e.currentTarget);
    console.log($currentTarget);
    var filterType = $currentTarget.attr("id");
    //  true if selected, false if not selected ^^
    var currentUIFilterState = $currentTarget.val();
    updateFilterObject(filterType, currentUIFilterState);
  }

  function updateFilterObject(filterType, currentUIFilterState) {
    currentFilters[filterType] = currentUIFilterState;
    console.log(currentFilters);
    applyFilterChange(currentFilters, trailData);
  }

  // these two set currentLocation

  function setCurrentLocationFromMap() {
    currentLocation = map.getCenter();
  }

  function setCurrentLocation() {
    // for now, just returns Akron
    // should use browser geolocation,
    // and only return Akron if we're far from home base

    currentLocation = AKRON;
  }

  // display the map based on currentLocation

  function displayInitialMap() {
    console.log("displayInitialMap");
    console.log(currentLocation);
    map = L.map('trailMap', {
      zoomControl: true
    }).setView([currentLocation.lat, currentLocation.lng], 11);

    // Switch between MapBox and other providers by commenting/uncommenting these
    L.tileLayer.provider('MapBox.' + MAPBOX_MAP_ID).addTo(map);
    // L.tileLayer.provider('Thunderforest.Landscape').addTo(map);
    map.on("popupopen", function() {
      console.log("popupOpen");
    });
    map.on("locationfound", function(location) {
      if (!userMarker)
        userMarker = L.userMarker(location.latlng, {
          smallIcon: true,
          pulsing: true,
          accuracy: 0
        }).addTo(map);
      console.log(location.latlng);
      userMarker.setLatLng(location.latlng);
    });
    map.locate({
      watch: true,
      setView: false,
      enableHighAccuracy: true
    });
    map.on("zoomend", function(e) {
      console.log("zoomend");
      if (SHOW_ALL_TRAILS && allSegmentLayer) {
        if (map.getZoom() >= SECONDARY_TRAIL_ZOOM && !(map.hasLayer(allSegmentLayer))) {
          map.addLayer(allSegmentLayer);
        }
        if (map.getZoom() < SECONDARY_TRAIL_ZOOM && map.hasLayer(allSegmentLayer)) {
          map.removeLayer(allSegmentLayer);
        }
      }
    });
    map.on("locationerror", function(errorEvent) {
      console.log("Location Error:");
      console.log(errorEvent.message);
      console.log(errorEvent.code);
    });
  }

  function getOrderedTrailheads(location, callback) {
    console.log("getOrderedTrailheads");
    var callData = {
      loc: location.lat + "," + location.lng,
      type: "GET",
      path: "/trailheads.json?loc=" + location.lat + "," + location.lng
    };
    makeAPICall(callData, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  // get all trailhead info, in order of distance from "location"

  function getOrderedTrailheadsOld(location, callback) {
    console.log("getOrderedTrailheads");
    var nearest_trailhead_query = "select trailheads.*, " +
      "ST_Distance_Sphere(ST_WKTToSQL('POINT(" + location.lng + " " + location.lat + ")'), the_geom) distance " +
      "from " + TRAILHEADS_TABLE + " as trailheads " +
      "ORDER BY distance " + "";

    makeSQLQuery(nearest_trailhead_query, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }


  // given the getOrderedTrailheads response, a geoJSON collection of trailheads ordered by distance,
  // populate trailheads[] with the each trailhead's stored properties, a Leaflet marker, 
  // and a place to put the trails for that trailhead

  function populateTrailheadArray(trailheadsGeoJSON) {
    console.log("populateTrailheadArray");
    console.log(trailheadsGeoJSON);
    trailheads = [];
    for (var i = 0; i < trailheadsGeoJSON.features.length; i++) {
      var currentFeature = trailheadsGeoJSON.features[i];
      currentFeatureLatLng = new L.LatLng(currentFeature.geometry.coordinates[1], currentFeature.geometry.coordinates[0]);
      var newMarker = L.circleMarker(currentFeatureLatLng, {
        title: 'test',
        radius: 4
      });

      // adding closure to call trailheadMarkerClick with trailheadID on marker click
      newMarker.on("click", function(trailheadID) {
        return function() {
          trailheadMarkerClick(trailheadID);
        };
      }(currentFeature.properties.id));

      var trailhead = {
        properties: currentFeature.properties,
        marker: newMarker,
        trails: [],
        popupContent: ""
      };
      trailheads.push(trailhead);
    }
  }

  // on trailhead marker click, this is invoked with the id of the trailhead
  // not used for anything but logging at the moment

  function trailheadMarkerClick(id) {
    console.log("trailheadMarkerClick");
    highlightTrailhead(id, 0);
  }

  // get the trailData from the DB

  function getTrailData(callback) {
    console.log("getTrailData");
    var trail_list_query = "select * from " + TRAILDATA_TABLE + " order by name";
    // Another AJAX call, for the trails
    makeSQLQuery(trail_list_query, function(response) {
      populateTrailData(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  function populateTrailData(trailDataGeoJSON) {
    for (var i = 0; i < trailDataGeoJSON.features.length; i++) {
      trailData[trailDataGeoJSON.features[i].properties.cartodb_id] = trailDataGeoJSON.features[i];
    }
  }

  function getTrailSegments(callback) {
    console.log("getTrailSegments");
    var trail_segment_query = "select the_geom, name1, name2, name3, source from " + TRAILSEGMENTS_TABLE;
    makeSQLQuery(trail_segment_query, function(response) {
      trailSegments = response;

      allSegmentLayer = L.geoJson(trailSegments, {
        style: function() {
          return {
            color: '#060',
            weight: 2,
            opacity: 0.5,
            clickable: true,
            dashArray: "5,5"
          };
        },
        onEachFeature: function(feature, layer) {
          var popupHTML = "<div class='trail-popup'>";
          if (feature.properties.name1) {
            popupHTML = popupHTML + feature.properties.name1;
          }
          if (feature.properties.name2) {
            popupHTML = popupHTML + "<br>" + feature.properties.name2;
          }
          if (feature.properties.name3) {
            popupHTML = popupHTML + "<br>" + feature.properties.name3;
          }
          popupHTML = popupHTML + "</div>";
          layer.bindPopup(popupHTML);
        }
      });
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  // given trailData,
  // populate trailheads[x].trails with all of the trails in trailData
  // that match each trailhead's named trails from the trailhead table.
  // Also add links to the trails within each trailhead popup 

  function addTrailDataToTrailheads(myTrailData) {
    console.log("addTrailDataToTrailheads");
    for (var j = 0; j < trailheads.length; j++) {
      var trailhead = trailheads[j];
      trailhead.trails = [];
      // for each original trailhead trail name
      for (var trailNum = 1; trailNum <= 3; trailNum++) {
        var trailWithNum = "trail" + trailNum;
        if (trailhead.properties[trailWithNum] === "") {
          continue;
        }
        var trailheadTrailName = trailhead.properties[trailWithNum];
        // TODO: add a test for the case of duplicate trail names.
        // Right now this
        // loop through all of the trailData objects, looking for trail names that match
        // the trailhead trailname.
        // this works great, except for things like "Ledges Trail," which get added twice,
        // one for the CVNP instance and one for the MPSSC instance.
        // we should test for duplicate names and only use the nearest one.
        // to do that, we'll need to either query the DB for the trail segment info,
        // or check distance against the (yet-to-be) pre-loaded trail segment info
        $.each(myTrailData, function(trailID, trail) {
          if (trailhead.properties[trailWithNum] == trail.properties.name) {
            trailhead.trails.push(trailID);
          }
        });
      }
    }
    fixDuplicateTrailNames(trailheads);
    makeTrailheadPopups(trailheads);
    mapActiveTrailheads(trailheads);
    makeTrailDivs(trailheads);
  }


  // this is so very wrong and terrible and makes me want to never write anything again.
  // alas, it works for now.
  // for each trailhead, if two or more of the matched trails from addTrailDataToTrailheads() have the same name,
  // remove any trails that don't match the trailhead source

  function fixDuplicateTrailNames(trailheads) {
    console.log("fixDuplicateTrailNames");
    for (var trailheadIndex = 0; trailheadIndex < trailheads.length; trailheadIndex++) {
      var trailhead = trailheads[trailheadIndex];
      var trailheadTrailNames = {};
      for (var trailsIndex = 0; trailsIndex < trailhead.trails.length; trailsIndex++) {
        var trailName = trailData[trailhead.trails[trailsIndex]].properties.name;
        trailheadTrailNames[trailName] = trailheadTrailNames[trailName] || [];
        var sourceAndTrailID = {
          source: trailData[trailhead.trails[trailsIndex]].properties.source,
          trailID: trailData[trailhead.trails[trailsIndex]].properties.cartodb_id
        };
        trailheadTrailNames[trailName].push(sourceAndTrailID);
      }
      for (var trailheadTrailName in trailheadTrailNames) {
        if (trailheadTrailNames.hasOwnProperty(trailheadTrailName)) {
          if (trailheadTrailNames[trailheadTrailName].length > 1) {
            // remove the ID from the trailhead trails array if the source doesn't match
            for (var i = 0; i < trailheadTrailNames[trailheadTrailName].length; i++) {
              var mySourceAndTrailID = trailheadTrailNames[trailheadTrailName][i];
              if (mySourceAndTrailID.source != trailhead.properties.source) {
                var idToRemove = mySourceAndTrailID.trailID;
                var removeIndex = $.inArray(idToRemove.toString(), trailhead.trails);
                trailhead.trails.splice(removeIndex, 1);
              }
            }
          }
        }
      }
    }
  }

  // given the trailheads,
  // make the popup menu for each one, including each trail present
  // and add it to the trailhead object

  function makeTrailheadPopups(trailheads) {
    for (var trailheadIndex = 0; trailheadIndex < trailheads.length; trailheadIndex++) {
      var trailhead = trailheads[trailheadIndex];
      var $popupContentMainDiv = $("<div>").addClass("trailhead-popup");
      var $popupTrailheadDiv = $("<div>").addClass("trailhead-name").html(trailhead.properties.name).appendTo($popupContentMainDiv);
      for (var trailsIndex = 0; trailsIndex < trailhead.trails.length; trailsIndex++) {
        var trail = trailData[trailhead.trails[trailsIndex]];
        var $popupTrailDiv = $("<div>").addClass("trailhead-trailname trail" + (trailsIndex + 1))
          .attr("data-trailname", trail.properties.name)
          .attr("data-trailid", trail.properties.cartodb_id)
          .attr("data-trailheadname", trailhead.properties.name)
          .attr("data-trailheadid", trailhead.properties.id)
          .attr("data-index", trailsIndex)
          .append("<a href='#'>").html(trail.properties.name)
          .appendTo($popupTrailheadDiv);<commit_msg>Now getting trail data & segments via API<commit_after>$(document).ready(startup);

/* The Big Nested Function
==========================*/

// Print to ensure file is loaded

function startup() {
  console.log("trailhead.js");

  // Map generated in CfA Account

  var MAPBOX_MAP_ID = "codeforamerica.map-4urpxezk";
  var AKRON = {
    lat: 41.1,
    lng: -81.5
  };

  var METERSTOMILESFACTOR = 0.00062137;
  var MAX_ZOOM = 14;
  var MIN_ZOOM = 12;
  var SECONDARY_TRAIL_ZOOM = 13;
  var SHORT_MAX_DISTANCE = 1.5;
  var MEDIUM_MAX_DISTANCE = 4.0;
  var SHOW_ALL_TRAILS = 1;
  var USE_LOCAL = 1; // Set this to a true value to preload/use a local trail segment cache
  var API_HOST = "http://127.0.0.1:3000";

  var map = {};
  var trailData = {}; // all of the trails metadata (from traildata table), with trail ID as key
  // { *id*: { geometry: null,  // this field is added for CartoDB's approval
  //                  properties: { id: *uniqueID*,
  //                                length: *length of trail in meters*,
  //                                name: *name of trail*,
  //                                source: *whose data this info came from*,
  //                              }
  //                }
  // }
  var trailheads = []; // all trailheads (from trailsegments)
  // [ {  marker: *Leaflet marker*,
  //      trails: *[array of matched trail IDs],
  //      popupContent: *HTML of Leaflet popup*,
  //      properties: { id: *uniqueID*,
  //                    distance: *from current location in meters*,
  //                    name: *name*,
  //                    source: *whose data this info came from*,
  //                    trail1: *trail at this trailhead*,
  //                    trail2: *trail at this trailhead*,
  //                    trail3: *trail at this trailhead*,
  //                    updated_at: *update time*,
  //                    created_at: *creation time*,
  //                    wkt: *original wkt for trailhead point*
  //                  }, 
  //   }[, ...}]
  // ]
  var trailSegments = [];


  var currentMultiTrailLayer = {}; // We have to know if a trail layer is already being displayed, so we can remove it
  var currentTrailLayers = [];
  var currentHighlightedTrailLayer = {};
  var currentLocation = {};
  var currentFilters = {};
  var currentDetailTrail = null;
  var userMarker = null;
  var allSegmentLayer = null;

  // Prepping for API calls (defining data for the call)
  var endpoint = "http://cfa.cartodb.com/api/v2/sql/";

  // comment these/uncomment the next set to switch between tables
  var TRAILHEADS_TABLE = "summit_trailheads";
  var TRAILSEGMENTS_TABLE = "summit_trailsegments";
  var TRAILDATA_TABLE = "summit_traildata";

  // var TRAILHEADS_TABLE = "summit_trailheads_test";
  // var TRAILSEGMENTS_TABLE = "summit_trail_segments_test";
  // var TRAILDATA_TABLE = "summit_traildata_test";


  // UI events to react to

  $("#redoSearch").click(reorderTrailsWithNewLocation);
  $(document).on('click', '.trailhead-trailname', trailnameClick);
  $(document).on('click', '.closeDetail', closeDetailPanel);
  $("#showAllTrailSegments").click(function() {
    getAllTrailPaths(drawMultiTrailLayer);
  });
  $("#showUnusedTrailSegments").click(function() {
    getAllTrailPaths(filterKnownTrails);
  });
  $(document).on('change', '.selectpicker', filterChangeHandler);


  // -----------------------------------
  // Kick things off

  initialSetup();

  // -----------------------------------

  //  Difficulty Filtering
  //  Length Filtering
  //  Helper functions

  function filterTrailList() {
    // defining the function, not calling it
    // click events / activation of checkboxes
    // change currentFilters object
    // apply current Filters to trailData
    // initialSetup performs the mapping
  }


  // The next three functions perform trailhead/trail mapping
  // on a) initial startup, b) requested re-sort of trailheads based on the map, 
  // and c) a change in filter settings
  // They all call addTrailDataToTrailheads() as their final action 
  // --------------------------------------------------------------

  // on startup, get location, display the map,
  // get and display the trailheads, populate trailData, 
  // add trailData to trailheads

  function initialSetup() {
    console.log("initialSetup");
    setCurrentLocation();
    displayInitialMap();
    getOrderedTrailheads(currentLocation, function() {
      getTrailData(function() {
        addTrailDataToTrailheads(trailData);
      });
    });
    if (USE_LOCAL) {
      getTrailSegments(function() {
        //console.log(trailSegments);
      });
    }
  }

  // set currentLocation to the center of the currently viewed map
  // then get the ordered trailheads and add trailData to trailheads

  function reorderTrailsWithNewLocation() {
    setCurrentLocationFromMap();
    getOrderedTrailheads(currentLocation, function() {
      addTrailDataToTrailheads(trailData);
    });
  }

  // This should probably be called after a UI change in the filters,
  // and the accompanying change to the currentFilters

  function applyFilterChange(currentFilters, trailData) {
    // TODO:
    var filteredTrailData = $.extend(true, {}, trailData);
    $.each(trailData, function(trail_id, trail) {
      if (currentFilters.activityFilter) {
        for (var i = 0; i < currentFilters.activityFilter.length; i++) {
          var activity = currentFilters.activityFilter[i];
          if (trail.properties[activity].toLowerCase() !== "true") {
            delete filteredTrailData[trail_id];
          }
        }
      }
      if (currentFilters.difficultyFilter) {
        var include = false;
        for (var j = 0; j < currentFilters.difficultyFilter.length; j++) {
          var difficulty = currentFilters.difficultyFilter[j];
          if (trail.properties.difficulty.toLowerCase() == difficulty.toLowerCase()) {
            include = true;
            break;
          }
        }
        if (!include) {
          delete filteredTrailData[trail_id];
        }
      }
      if (currentFilters.lengthFilter) {
        var distInclude = false;
        for (var k = 0; k < currentFilters.lengthFilter.length; k++) {
          var distance = currentFilters.lengthFilter[k];
          var trailDist = trail.properties["length"];
          if ((distance.toLowerCase() == "short" && trailDist <= SHORT_MAX_DISTANCE) ||
            (distance.toLowerCase() == "medium" && trailDist > SHORT_MAX_DISTANCE && trailDist <= MEDIUM_MAX_DISTANCE) ||
            (distance.toLowerCase() == "long" && trailDist > MEDIUM_MAX_DISTANCE)) {
            distInclude = true;
            break;
          }
        }
        if (!distInclude) {
          delete filteredTrailData[trail_id];
        }
      }
    });

    // loop through trailData
    // apply currentFilters object
    // put trails we want to display into filteredTrailData
    addTrailDataToTrailheads(filteredTrailData);
  }

  function filterChangeHandler(e) {
    var $currentTarget = $(e.currentTarget);
    console.log($currentTarget);
    var filterType = $currentTarget.attr("id");
    //  true if selected, false if not selected ^^
    var currentUIFilterState = $currentTarget.val();
    updateFilterObject(filterType, currentUIFilterState);
  }

  function updateFilterObject(filterType, currentUIFilterState) {
    currentFilters[filterType] = currentUIFilterState;
    console.log(currentFilters);
    applyFilterChange(currentFilters, trailData);
  }

  // these two set currentLocation

  function setCurrentLocationFromMap() {
    currentLocation = map.getCenter();
  }

  function setCurrentLocation() {
    // for now, just returns Akron
    // should use browser geolocation,
    // and only return Akron if we're far from home base

    currentLocation = AKRON;
  }

  // display the map based on currentLocation

  function displayInitialMap() {
    console.log("displayInitialMap");
    console.log(currentLocation);
    map = L.map('trailMap', {
      zoomControl: true
    }).setView([currentLocation.lat, currentLocation.lng], 11);

    // Switch between MapBox and other providers by commenting/uncommenting these
    L.tileLayer.provider('MapBox.' + MAPBOX_MAP_ID).addTo(map);
    // L.tileLayer.provider('Thunderforest.Landscape').addTo(map);
    map.on("popupopen", function() {
      console.log("popupOpen");
    });
    map.on("locationfound", function(location) {
      if (!userMarker)
        userMarker = L.userMarker(location.latlng, {
          smallIcon: true,
          pulsing: true,
          accuracy: 0
        }).addTo(map);
      console.log(location.latlng);
      userMarker.setLatLng(location.latlng);
    });
    map.locate({
      watch: true,
      setView: false,
      enableHighAccuracy: true
    });
    map.on("zoomend", function(e) {
      console.log("zoomend");
      if (SHOW_ALL_TRAILS && allSegmentLayer) {
        if (map.getZoom() >= SECONDARY_TRAIL_ZOOM && !(map.hasLayer(allSegmentLayer))) {
          map.addLayer(allSegmentLayer);
        }
        if (map.getZoom() < SECONDARY_TRAIL_ZOOM && map.hasLayer(allSegmentLayer)) {
          map.removeLayer(allSegmentLayer);
        }
      }
    });
    map.on("locationerror", function(errorEvent) {
      console.log("Location Error:");
      console.log(errorEvent.message);
      console.log(errorEvent.code);
    });
  }

  function getOrderedTrailheads(location, callback) {
    console.log("getOrderedTrailheads");
    var callData = {
      loc: location.lat + "," + location.lng,
      type: "GET",
      path: "/trailheads.json?loc=" + location.lat + "," + location.lng
    };
    makeAPICall(callData, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  // get all trailhead info, in order of distance from "location"

  function getOrderedTrailheadsOld(location, callback) {
    console.log("getOrderedTrailheads");
    var nearest_trailhead_query = "select trailheads.*, " +
      "ST_Distance_Sphere(ST_WKTToSQL('POINT(" + location.lng + " " + location.lat + ")'), the_geom) distance " +
      "from " + TRAILHEADS_TABLE + " as trailheads " +
      "ORDER BY distance " + "";

    makeSQLQuery(nearest_trailhead_query, function(response) {
      populateTrailheadArray(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }


  // given the getOrderedTrailheads response, a geoJSON collection of trailheads ordered by distance,
  // populate trailheads[] with the each trailhead's stored properties, a Leaflet marker, 
  // and a place to put the trails for that trailhead

  function populateTrailheadArray(trailheadsGeoJSON) {
    console.log("populateTrailheadArray");
    console.log(trailheadsGeoJSON);
    trailheads = [];
    for (var i = 0; i < trailheadsGeoJSON.features.length; i++) {
      var currentFeature = trailheadsGeoJSON.features[i];
      currentFeatureLatLng = new L.LatLng(currentFeature.geometry.coordinates[1], currentFeature.geometry.coordinates[0]);
      var newMarker = L.circleMarker(currentFeatureLatLng, {
        title: 'test',
        radius: 4
      });

      // adding closure to call trailheadMarkerClick with trailheadID on marker click
      newMarker.on("click", function(trailheadID) {
        return function() {
          trailheadMarkerClick(trailheadID);
        };
      }(currentFeature.properties.id));

      var trailhead = {
        properties: currentFeature.properties,
        marker: newMarker,
        trails: [],
        popupContent: ""
      };
      trailheads.push(trailhead);
    }
  }

  // on trailhead marker click, this is invoked with the id of the trailhead
  // not used for anything but logging at the moment

  function trailheadMarkerClick(id) {
    console.log("trailheadMarkerClick");
    highlightTrailhead(id, 0);
  }

  // get the trailData from the API

  function getTrailData(callback) {
    console.log("getTrailData");
    var callData = {
      type: "GET",
      path: "/trails.json"
    };
    makeAPICall(callData, function(response) {
      populateTrailData(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  // get the trailData from the DB

  function getTrailDataOld(callback) {
    console.log("getTrailData");
    var trail_list_query = "select * from " + TRAILDATA_TABLE + " order by name";
    // Another AJAX call, for the trails
    makeSQLQuery(trail_list_query, function(response) {
      populateTrailData(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  function populateTrailData(trailDataGeoJSON) {
    for (var i = 0; i < trailDataGeoJSON.features.length; i++) {
      trailData[trailDataGeoJSON.features[i].properties.id] = trailDataGeoJSON.features[i];
    }
  }

  function getTrailSegments(callback) {
    console.log("getTrailSegments");
    var callData = {
      type: "GET",
      path: "/trailsegments.json"
    };
    makeAPICall(callData, function(response) {
      trailSegments = response;
      allSegmentLayer = makeAllSegmentLayer(response);
      if (typeof callback == "function") {
        callback();
      }
    });
  }

  function getTrailSegmentsOld(callback) {
    console.log("getTrailSegments");
    var trail_segment_query = "select the_geom, name1, name2, name3, source from " + TRAILSEGMENTS_TABLE;
    makeSQLQuery(trail_segment_query, function(response) {
      trailSegments = response;
      allSegmentLayer = makeAllSegmentLayer(response);

      if (typeof callback == "function") {
        callback();
      }
    });
  }

  function makeAllSegmentLayer(response) {
    allSegmentLayer = L.geoJson(trailSegments, {
      style: function() {
        return {
          color: '#060',
          weight: 2,
          opacity: 0.5,
          clickable: true,
          dashArray: "5,5"
        };
      },
      onEachFeature: function(feature, layer) {
        var popupHTML = "<div class='trail-popup'>";
        if (feature.properties.name1) {
          popupHTML = popupHTML + feature.properties.name1;
        }
        if (feature.properties.name2) {
          popupHTML = popupHTML + "<br>" + feature.properties.name2;
        }
        if (feature.properties.name3) {
          popupHTML = popupHTML + "<br>" + feature.properties.name3;
        }
        popupHTML = popupHTML + "</div>";
        layer.bindPopup(popupHTML);
      }
    });
    return allSegmentLayer;
  }

  // given trailData,
  // populate trailheads[x].trails with all of the trails in trailData
  // that match each trailhead's named trails from the trailhead table.
  // Also add links to the trails within each trailhead popup 

  function addTrailDataToTrailheads(myTrailData) {
    console.log("addTrailDataToTrailheads");
    for (var j = 0; j < trailheads.length; j++) {
      var trailhead = trailheads[j];
      trailhead.trails = [];
      // for each original trailhead trail name
      for (var trailNum = 1; trailNum <= 3; trailNum++) {
        var trailWithNum = "trail" + trailNum;
        if (trailhead.properties[trailWithNum] === "") {
          continue;
        }
        var trailheadTrailName = trailhead.properties[trailWithNum];
        // TODO: add a test for the case of duplicate trail names.
        // Right now this
        // loop through all of the trailData objects, looking for trail names that match
        // the trailhead trailname.
        // this works great, except for things like "Ledges Trail," which get added twice,
        // one for the CVNP instance and one for the MPSSC instance.
        // we should test for duplicate names and only use the nearest one.
        // to do that, we'll need to either query the DB for the trail segment info,
        // or check distance against the (yet-to-be) pre-loaded trail segment info
        $.each(myTrailData, function(trailID, trail) {
          if (trailhead.properties[trailWithNum] == trail.properties.name) {
            trailhead.trails.push(trailID);
          }
        });
      }
    }
    fixDuplicateTrailNames(trailheads);
    makeTrailheadPopups(trailheads);
    mapActiveTrailheads(trailheads);
    makeTrailDivs(trailheads);
  }


  // this is so very wrong and terrible and makes me want to never write anything again.
  // alas, it works for now.
  // for each trailhead, if two or more of the matched trails from addTrailDataToTrailheads() have the same name,
  // remove any trails that don't match the trailhead source

  function fixDuplicateTrailNames(trailheads) {
    console.log("fixDuplicateTrailNames");
    for (var trailheadIndex = 0; trailheadIndex < trailheads.length; trailheadIndex++) {
      var trailhead = trailheads[trailheadIndex];
      var trailheadTrailNames = {};
      for (var trailsIndex = 0; trailsIndex < trailhead.trails.length; trailsIndex++) {
        var trailName = trailData[trailhead.trails[trailsIndex]].properties.name;
        trailheadTrailNames[trailName] = trailheadTrailNames[trailName] || [];
        var sourceAndTrailID = {
          source: trailData[trailhead.trails[trailsIndex]].properties.source,
          trailID: trailData[trailhead.trails[trailsIndex]].properties.id
        };
        trailheadTrailNames[trailName].push(sourceAndTrailID);
      }
      for (var trailheadTrailName in trailheadTrailNames) {
        if (trailheadTrailNames.hasOwnProperty(trailheadTrailName)) {
          if (trailheadTrailNames[trailheadTrailName].length > 1) {
            // remove the ID from the trailhead trails array if the source doesn't match
            for (var i = 0; i < trailheadTrailNames[trailheadTrailName].length; i++) {
              var mySourceAndTrailID = trailheadTrailNames[trailheadTrailName][i];
              if (mySourceAndTrailID.source != trailhead.properties.source) {
                var idToRemove = mySourceAndTrailID.trailID;
                var removeIndex = $.inArray(idToRemove.toString(), trailhead.trails);
                trailhead.trails.splice(removeIndex, 1);
              }
            }
          }
        }
      }
    }
  }

  // given the trailheads,
  // make the popup menu for each one, including each trail present
  // and add it to the trailhead object

  function makeTrailheadPopups(trailheads) {
    for (var trailheadIndex = 0; trailheadIndex < trailheads.length; trailheadIndex++) {
      var trailhead = trailheads[trailheadIndex];
      var $popupContentMainDiv = $("<div>").addClass("trailhead-popup");
      var $popupTrailheadDiv = $("<div>").addClass("trailhead-name").html(trailhead.properties.name).appendTo($popupContentMainDiv);
      for (var trailsIndex = 0; trailsIndex < trailhead.trails.length; trailsIndex++) {
        var trail = trailData[trailhead.trails[trailsIndex]];
        var $popupTrailDiv = $("<div>").addClass("trailhead-trailname trail" + (trailsIndex + 1))
          .attr("data-trailname", trail.properties.name)
          .attr("data-trailid", trail.properties.id)
          .attr("data-trailheadname", trailhead.properties.name)
          .attr("data-trailheadid", trailhead.properties.id)
          .attr("data-index", trailsIndex)
          .append("<a href='#'>").html(trail.properties.name)
          .appendTo($popupTrailheadDiv);
{'fraction_non_alphanumeric': 0.12096752851509779, 'fraction_numerical': 0.004340116971445206, 'mean_word_length': 2.6053811659192827, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '569664', 'n_tokens_mistral': 11333, 'n_tokens_neox': 10987, 'n_words': 2672}