<commit_before>var firstfile = true;
var lasttimelength = 0;

var map, playStep, fileindex;
var mytime = new Date();
var mintime = (new Date("January 1, 5000")) * 1;
var maxtime = (new Date("January 1, 100")) * 1;
var settime = null;
var maxlat = -90;
var minlat = 90;
var maxlng = -180;
var minlng = 180;
var timelayers = [ ];
var fixlayers = [ ];
var trimvals = [ ];
var oldlines = [ ];
var reader, fileindex, files;

$(document).ready(function(){
  // on tablet or mobile | replace drag-and-drop with upload button
  if( navigator.userAgent.match(/(iPod|iPhone|iPad)/i) || (navigator.userAgent.toLowerCase().indexOf("android") > -1) ){
    switchToMobile();
  }

  // make a Leaflet map
  map = new L.Map('map');
  map.attributionControl.setPrefix('');
  var terrain = 'http://{s}.tiles.mapbox.com/v3/mapmeld.map-ofpv1ci4/{z}/{x}/{y}.png';
  var terrainAttrib = 'Map data &copy; 2013 OpenStreetMap contributors, Tiles &copy; 2013 MapBox';
  //var terrain = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
  //var terrainAttrib = 'Map data &copy; 2013 OpenStreetMap contributors';
  
  terrainLayer = new L.TileLayer(terrain, {maxZoom: 15, attribution: terrainAttrib});
  map.addLayer(terrainLayer);
  map.setView(new L.LatLng(40.484037,-106.825046), 10);

  // set up the file drop
  document.body.addEventListener('dragenter', blockHandler, false);
  document.body.addEventListener('dragexit', blockHandler, false);
  document.body.addEventListener('dragover', blockHandler, false);
  document.body.addEventListener('drop', dropFile, false);

  // set up the jQuery timeline slider
  $("#slidebar").slider({
    orientation: "horizontal",
    range: "min",
    min: (new Date()) - 24 * 60 * 60 * 1000,
    max: (new Date()) * 1,
    value: 500,
    slide: function(event, ui){
      if(playStep){
        window.clearInterval(playStep);
        playStep = null;
      }
      settime = ui.value;
      displayTime(ui.value);
      geotimes(ui.value);
    }
  });
  
  if(myjson.length){
    firstfile = false;
    myjson = $.parseJSON(myjson);
    
    if(myjson.length){
      // load all tracks
      for(var t=0;t<myjson.length;t++){
        $.getJSON("/track/" + myjson[t], function(data){
          processFile({ target: { result: data.xml } });
        });
      }
      return;
    }
    
    var timed = myjson.timed;
    for(var t=0;t<timed.length;t++){
      if(typeof timed[t].times != 'undefined'){
        // moving point
        var movemarker = new L.marker(new L.LatLng(0,0), { clickable: false });
        var moveline = [ ];
        for(var c=0;c<timed[t].coords.length;c++){
          maxlat = Math.max(maxlat, timed[t].coords[c][0]);
          maxlng = Math.max(maxlng, timed[t].coords[c][1]);
          minlat = Math.min(minlat, timed[t].coords[c][0]);
          minlng = Math.min(minlng, timed[t].coords[c][1]);
          mintime = Math.min(mintime, timed[t].times[c]);
          maxtime = Math.max(maxtime, timed[t].times[c]);
          moveline.push(new L.LatLng( timed[t].coords[c][0], timed[t].coords[c][1] ));
          timelayers.push({
            geo: movemarker,
            ll: new L.LatLng( timed[t].coords[c][0], timed[t].coords[c][1] ),
            time: new Date( timed[t].times[c] )
          });
        }
        var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
        oldlines.push(oldline);
        map.addLayer(oldline);
      }
      else{
        // geo with start and/or end
        var timelyr = { };
        if(typeof timed[t].start != 'undefined'){
          mintime = Math.min(mintime, timed[t].start);
          maxtime = Math.max(maxtime, timed[t].start);
          timelyr.start = new Date( timed[t].start );
        }
        if(typeof timed[t].end != 'undefined'){
          mintime = Math.min(mintime, timed[t].end);
          maxtime = Math.max(maxtime, timed[t].end);
          timelyr.end = new Date( timed[t].end );
        }
        if(typeof timed[t].coords[0].length != 'undefined'){
          // Polygon
          for(var c=0;c<timed[t].coords.length;c++){
            maxlat = Math.max(maxlat, timed[t].coords[c][0]);
            maxlng = Math.max(maxlng, timed[t].coords[c][1]);
            minlat = Math.min(minlat, timed[t].coords[c][0]);
            minlng = Math.min(minlng, timed[t].coords[c][1]);
            timed[t].coords[c] = new L.LatLng(timed[t].coords[c][0], timed[t].coords[c][1]);
          }
          timelyr.geo = new L.polygon( timed[t].coords );
        }
        else{
          // Point
          maxlat = Math.max(maxlat, timed[t].coords[0]);
          maxlng = Math.max(maxlng, timed[t].coords[1]);
          minlat = Math.min(minlat, timed[t].coords[0]);
          minlng = Math.min(minlng, timed[t].coords[1]); 
          timelyr.geo = new L.marker( new L.LatLng( timed[t].coords[0], timed[t].coords[1] ) );
        }
        timelayers.push(timelyr);
      }
    }
    var fixed = myjson.fixed;
    if(fixed.geojson.length){
      // static GeoJSON
      L.geoJson( { "type": "FeatureCollection", "features": fixed.geojson } , { onEachFeature: jsonmap });
    }
    // static KML, currently unsupported
    //for(var t=0;t<fixed.kml.length;t++){
    //  // catch minlat, maxlat, minlng, maxlng, mintime, maxtime
    //}
    map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
    updateTimeline();
  }
  else{
    // load default GeoJSON from Chicago
    $.getJSON('/geos/chicago.geojson', function(gj){
      L.geoJson(gj, { onEachFeature: jsonmap });
      map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
      updateTimeline();
    });
  }
  
  // add play button timer
  playStep = null;
  $(".btn-success").on("click", function(){
    if(!playStep){
      if(!settime){
        settime = mintime;
      }
      playStep = setInterval(function(){
        settime = Math.min(maxtime, settime + (maxtime - mintime) / 500 );
        $("#slidebar").slider({ value: settime });
        displayTime(settime);
        geotimes(settime);
      }, 50);
    }
  });

  // trim the gpx
  $("#edittime").on("click", function(){
    if(mintime * 1 < maxtime * 1){
      $("#trimstart").text( (new Date(mintime)).toUTCString() );
      $("#trimend").text( (new Date(maxtime)).toUTCString() );
      //$("#trimslider").html("");
      $("#trimslider").slider({
        range: true,
        min: mintime,
        max: maxtime,
        values: [ mintime, maxtime ],
        slide: function(event, ui) {
          trimvals = ui.values;
          $("#trimstart").text( (new Date( ui.values[0] )).toUTCString() );
          $("#trimend").text( (new Date( ui.values[1] )).toUTCString() );
        }
      });
      $("#timelinetrim").modal('show');
    }
  });
  $("#trimnow").on("click", function(){
    mintime = trimvals[0];
    maxtime = trimvals[1];
    trimGPS();
    updateTimeline();
  });

});

var displayTime = function(t){
  $("#readtime").text( (new Date(t)).toUTCString() );
};

var blockHandler = function(e){
  e.stopPropagation();
  e.preventDefault();
};

var dropFile = function(e){
  e.stopPropagation();
  e.preventDefault();

  files = e.dataTransfer.files;
  if(files && files.length){
    if(firstfile){
      // remove default map
      for(var t=0;t<timelayers.length;t++){
        map.removeLayer(timelayers[t].geo);
      }
      timelayers = [ ];

      // reset defaults
      mintime = (new Date("January 1, 5000")) * 1;
      maxtime = (new Date("January 1, 100")) * 1;
      maxlat = -90;
      minlat = 90;
      maxlng = -180;
      minlng = 180;
    }
    firstfile = false;

    reader = new FileReader();
    reader.onload = processFile;
    fileindex = 0;
    reader.readAsText(files[0]);
  }
};

function processFile(e){
  var injson;
  try{
    injson = $.parseJSON( e.target.result );
  }
  catch(err){
    // XML-based (KML, GPX)
    var xmlf;
    try{
      xmlf = $.parseXML( e.target.result );
    }
    catch(err){
      // neither JSON or XML-based; go for CSV
      var lines = e.target.result.split('\n');
      if(lines.length == 1){
        lines = lines[0].split('\r');
      }
      var lineswithlength = [0, 0];
      var latcol = 0, lngcol = 1, timecol = 2;
      
      for(var i=0;i<lines.length;i++){
        var rawline = replaceAll(lines[i],'"','').split(',');
        // skip lines without any length
        if(rawline.length < 3){
          continue;
        }
        // wait to find three lines with the same length
        if(lineswithlength[0] == 0){
          lineswithlength = [1, rawline.length];
          continue;
        }
        else if(lineswithlength[0] < 3){
          if(rawline.length == lineswithlength[1]){
            // another line agrees that this is the standard length
            lineswithlength[0]++;
            if(lineswithlength[0] >= 3){
              // standard length has just been found; restart count
              i = -1;
              // add buttons and prompt for column-naming
              $("#colnamer").text("latitude");
              setMyButton = function(col){
                latcol = col;
                $("#colbtn" + col).addClass("btn-success").html('&check;');
                $("#colnamer").text("longitude");
                setMyButton = function(col){
                  lngcol = col;
                  $("#colbtn" + col).addClass("btn-success").html('&check;');
                  $("#colnamer").text("time");
                  setMyButton = function(col){
                    timecol = col;
                    $("#colbtn" + col).addClass("btn-success").html('&check;');

                    var movemarker = new L.marker( new L.LatLng(0, 0), { clickable: false } );
                    var moveline = [ ];

                    for(var i=0;i<lines.length;i++){
                      var rawline = replaceAll(lines[i],'"','').split(',');
                      if(rawline.length == lineswithlength[1]){
                        // valid length line
                  
                        // avoid labels rows, where lat / lng / time cannot be decoded
                        if(isNaN(rawline[latcol] * 1) || isNaN(rawline[lngcol] * 1)){
                          continue;
                        }
                        var mytime = new Date();
                        try{
                          if(isNaN(rawline[timecol]) * 1){
                            mytime = new Date( rawline[timecol] );
                          }
                          else{
                            mytime = new Date( rawline[timecol] * 1 );                              
                          }
                        }
                        catch(e){
                          mytime = null;
                        }
                        if(mytime === null){
                          continue;
                        }

                        var mycoord = new L.LatLng( rawline[latcol] * 1.0, rawline[lngcol] * 1.0 );
                        moveline.push(mycoord);
                        maxlat = Math.max(maxlat, mycoord.lat);
                        maxlng = Math.max(maxlng, mycoord.lng);
                        minlat = Math.min(minlat, mycoord.lat);
                        minlng = Math.min(minlng, mycoord.lng);              

                        var mytime;
                        if(isNaN(rawline[timecol]) * 1){
                          mytime = new Date( rawline[timecol] );
                        }
                        else{
                          mytime = new Date( rawline[timecol] * 1 );                              
                        }
                        mintime = Math.min( mintime, mytime * 1 );
                        maxtime = Math.max( maxtime, mytime * 1 );

                        timelayers.push({
                          geo: movemarker,
                          ll: mycoord,
                          time: mytime
                        });
                      }
                    }
                    var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
                    oldlines.push(oldline);
                    map.addLayer(oldline);
                    $(".modal").modal('hide');

                    updateTimeline();
                    fileindex++;
                    if(fileindex < files.length){
                      return reader.readAsText(files[fileindex]);
                    }
                    else{
                      map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
                      return savemap();
                    }
                  };
                };
              };
              var colButtons = "";
              for(var c=0;c<lineswithlength[1];c++){
                colButtons += "<td><a id='colbtn" + c + "' class='btn' href='#' onclick='setMyButton(" + c + ")'>X</a></td>";
              }
              $("#csvguide table").html("<tr>" + colButtons + "</tr>");
            }
            continue;
          }
          else{
            // a line with a different length, resets lineswithlength
            lineswithlength = [1, rawline.length];
            continue;
          }
        }
        else if(rawline.length != lineswithlength[1]){
          // length of standard lines is known; this one does not match
          continue;
        }

        // only lines with confirmed proper length reach this point
        var cols = "";
        for(var c=0;c<rawline.length;c++){
          cols += "<td>" + replaceAll( replaceAll( rawline[c], "<", "&lt;" ), ">", "&gt;") + "</td>";
        }

        $("#csvguide table").append('<tr>' + cols + '</tr>');
      }

      if(lineswithlength[0] >= 3 && lineswithlength[1] >= 3){
        $("#csvguide").modal('show');
        $(".cancelcsv").click(function(e){
          // user canceled this CSV... move on to next files
          fileindex++;
          if(fileindex < files.length){
            return reader.readAsText(files[fileindex]);
          }
          else{
            map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
            return savemap();
          }
        });
        // process continues after using modal to process or cancel CSV
        return;
      }
      else{
        // not a valid CSV... continue to next file
        fileindex++;
        if(fileindex < files.length){
          return reader.readAsText(files[fileindex]);
        }
        else{
          map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
          return savemap();
        }
      }
    }
    var placemarks = xmlf.getElementsByTagName("Placemark");
    if(!placemarks.length){
      // no KML Placemarks, go to GPX reader
      var pts = xmlf.getElementsByTagName("trkpt");
      var times = xmlf.getElementsByTagName("time");
      var moveline = [ ];
      if(pts.length && times.length && pts.length == times.length){
        var movemarker = new L.marker( new L.LatLng(0, 0), { clickable: false } );
        for(var p=0;p<pts.length;p++){
          var mycoord = new L.LatLng( 1.0 * pts[p].getAttribute("lat"), 1.0 * pts[p].getAttribute("lon") );
          moveline.push(mycoord);
          maxlat = Math.max(maxlat, mycoord.lat);
          maxlng = Math.max(maxlng, mycoord.lng);
          minlat = Math.min(minlat, mycoord.lat);
          minlng = Math.min(minlng, mycoord.lng);              

          var mytime = new Date( $(times[p]).text() );
          mintime = Math.min( mintime, mytime * 1 );
          maxtime = Math.max( maxtime, mytime * 1 );

          timelayers.push({
            geo: movemarker,
            ll: mycoord,
            time: mytime
          });
        }
      }
      var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
      oldlines.push(oldline);
      map.addLayer(oldline);
      updateTimeline();
      fileindex++;
      if(fileindex < files.length){
        return reader.readAsText(files[fileindex]);
      }
      else{
        map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
        savemap();
        return;
      }
    }
    // KML loading
    var oldmarker = new L.marker(new L.LatLng(0,0), { clickable: false });
    var oldmoveline = [ ];
    for(var i=0;i<placemarks.length;i++){
      var inkml = placemarks[i];
      var whens = inkml.getElementsByTagName("when");
      var coords = inkml.getElementsByTagName("coord");
      if(whens.length && !coords.length){
        coords = inkml.getElementsByTagName("gx:coord");
        if(!coords.length){
          // old-style <Placemark><TimeSpan><when> syntax
          coords = inkml.getElementsByTagName("coordinates");
          var rawcoord = $(coords[0]).text().split(",");
          var mycoord = new L.LatLng( rawcoord[1] * 1.0, rawcoord[0] * 1.0 );
          oldmoveline.push(mycoord);
          maxlat = Math.max(maxlat, mycoord.lat);
          maxlng = Math.max(maxlng, mycoord.lng);
          minlat = Math.min(minlat, mycoord.lat);
          minlng = Math.min(minlng, mycoord.lng);              

          var mytime = new Date( $(whens[0]).text() );
          mintime = Math.min( mintime, mytime * 1 );
          maxtime = Math.max( maxtime, mytime * 1 );

          timelayers.push({
            geo: oldmarker,
            ll: mycoord,
            time: mytime
          });
          continue;
        }
      }
      var moveline = [ ];
      if(whens.length && coords.length && whens.length == coords.length){
        var movemarker = new L.marker( new L.LatLng(0, 0), { clickable: false } );
        for(var c=0;c<coords.length;c++){
          var rawcoord = $(coords[c]).text().split(" ");
          var mycoord = new L.LatLng( rawcoord[1] * 1.0, rawcoord[0] * 1.0 );
          moveline.push(mycoord);
          maxlat = Math.max(maxlat, mycoord.lat);
          maxlng = Math.max(maxlng, mycoord.lng);
          minlat = Math.min(minlat, mycoord.lat);
          minlng = Math.min(minlng, mycoord.lng);              

          var mytime = new Date( $(whens[c]).text() );
          mintime = Math.min( mintime, mytime * 1 );
          maxtime = Math.max( maxtime, mytime * 1 );
              
          timelayers.push({
            geo: movemarker,
            ll: mycoord,
            time: mytime
          });
        }
        var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
        oldlines.push(oldline);
        map.addLayer(oldline);
      }
      else{
        // check for <begin> and <end> tags
        var begin = inkml.getElementsByTagName("begin");
        var end = inkml.getElementsByTagName("end");
        if(begin.length || end.length){
          // KML placemark with a begin and/or end
          var timefeature = { };
          if(begin.length){
            timefeature.start = new Date( $(begin[0]).text() );
            mintime = Math.min(mintime, timefeature.start * 1);
            maxtime = Math.max(maxtime, timefeature.start * 1);
          }
          if(end.length){
            timefeature.end = new Date( $(end[0]).text() );
            mintime = Math.min(mintime, timefeature.end * 1);
            maxtime = Math.max(maxtime, timefeature.end * 1);
          }
          timefeature.geo = kmlmap(inkml);
          timelayers.push( timefeature );
        }
        else{
          // KML object without a time
          var maps = kmlmap(inkml);
          for(var m=0;m<maps.length;m++){
            map.addLayer( maps[m] );
            fixlayers.push( maps );
          }
        }
      }
    }
    if(oldmoveline.length){
      var oldline = new L.polyline(oldmoveline, { clickable: false });
      oldlines.push(oldline);
      map.addLayer(oldline);
    }
    updateTimeline();
    fileindex++;
    if(fileindex < files.length){
      return reader.readAsText(files[fileindex]);
    }
    else{
      map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
      savemap();
      return;
    }
  }
  L.geoJson(injson, {
    onEachFeature: jsonmap
  });
  updateTimeline();

  fileindex++;
  if(fileindex < files.length){
    reader.readAsText(files[fileindex]);
  }
  else{
    map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
    savemap();
  }
}

function savemap(){
  if(!timelayers.length || lasttimelength == timelayers.length + fixlayers.length){
    // if there is no geotime data or additional points, the map doesn't save
    return;
  }
  lasttimelength = timelayers.length + fixlayers.length;

  var saver = {
    timed: [ ],
    fixed: {
      kml: [ ],
      geojson: [ ]
    }
  };
  var movemarker = null;<commit_msg>use direct download instead of api<commit_after>var firstfile = true;
var lasttimelength = 0;

var map, playStep, fileindex;
var mytime = new Date();
var mintime = (new Date("January 1, 5000")) * 1;
var maxtime = (new Date("January 1, 100")) * 1;
var settime = null;
var maxlat = -90;
var minlat = 90;
var maxlng = -180;
var minlng = 180;
var timelayers = [ ];
var fixlayers = [ ];
var trimvals = [ ];
var oldlines = [ ];
var reader, fileindex, files;

$(document).ready(function(){
  // on tablet or mobile | replace drag-and-drop with upload button
  if( navigator.userAgent.match(/(iPod|iPhone|iPad)/i) || (navigator.userAgent.toLowerCase().indexOf("android") > -1) ){
    switchToMobile();
  }

  // make a Leaflet map
  map = new L.Map('map');
  map.attributionControl.setPrefix('');
  var terrain = 'http://{s}.tiles.mapbox.com/v3/mapmeld.map-ofpv1ci4/{z}/{x}/{y}.png';
  var terrainAttrib = 'Map data &copy; 2013 OpenStreetMap contributors, Tiles &copy; 2013 MapBox';
  //var terrain = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
  //var terrainAttrib = 'Map data &copy; 2013 OpenStreetMap contributors';
  
  terrainLayer = new L.TileLayer(terrain, {maxZoom: 15, attribution: terrainAttrib});
  map.addLayer(terrainLayer);
  map.setView(new L.LatLng(40.484037,-106.825046), 10);

  // set up the file drop
  document.body.addEventListener('dragenter', blockHandler, false);
  document.body.addEventListener('dragexit', blockHandler, false);
  document.body.addEventListener('dragover', blockHandler, false);
  document.body.addEventListener('drop', dropFile, false);

  // set up the jQuery timeline slider
  $("#slidebar").slider({
    orientation: "horizontal",
    range: "min",
    min: (new Date()) - 24 * 60 * 60 * 1000,
    max: (new Date()) * 1,
    value: 500,
    slide: function(event, ui){
      if(playStep){
        window.clearInterval(playStep);
        playStep = null;
      }
      settime = ui.value;
      displayTime(ui.value);
      geotimes(ui.value);
    }
  });
  
  if(myjson.length){
    firstfile = false;
    myjson = $.parseJSON(myjson);
    
    if(myjson.length){
      // load all tracks
      files = [ ];
      for(var t=0;t<myjson.length;t++){
        $.getJSON("/track/" + myjson[t], function(data){
          processFile({ target: { result: data.xml } });
        });
      }
      return;
    }
    
    var timed = myjson.timed;
    for(var t=0;t<timed.length;t++){
      if(typeof timed[t].times != 'undefined'){
        // moving point
        var movemarker = new L.marker(new L.LatLng(0,0), { clickable: false });
        var moveline = [ ];
        for(var c=0;c<timed[t].coords.length;c++){
          maxlat = Math.max(maxlat, timed[t].coords[c][0]);
          maxlng = Math.max(maxlng, timed[t].coords[c][1]);
          minlat = Math.min(minlat, timed[t].coords[c][0]);
          minlng = Math.min(minlng, timed[t].coords[c][1]);
          mintime = Math.min(mintime, timed[t].times[c]);
          maxtime = Math.max(maxtime, timed[t].times[c]);
          moveline.push(new L.LatLng( timed[t].coords[c][0], timed[t].coords[c][1] ));
          timelayers.push({
            geo: movemarker,
            ll: new L.LatLng( timed[t].coords[c][0], timed[t].coords[c][1] ),
            time: new Date( timed[t].times[c] )
          });
        }
        var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
        oldlines.push(oldline);
        map.addLayer(oldline);
      }
      else{
        // geo with start and/or end
        var timelyr = { };
        if(typeof timed[t].start != 'undefined'){
          mintime = Math.min(mintime, timed[t].start);
          maxtime = Math.max(maxtime, timed[t].start);
          timelyr.start = new Date( timed[t].start );
        }
        if(typeof timed[t].end != 'undefined'){
          mintime = Math.min(mintime, timed[t].end);
          maxtime = Math.max(maxtime, timed[t].end);
          timelyr.end = new Date( timed[t].end );
        }
        if(typeof timed[t].coords[0].length != 'undefined'){
          // Polygon
          for(var c=0;c<timed[t].coords.length;c++){
            maxlat = Math.max(maxlat, timed[t].coords[c][0]);
            maxlng = Math.max(maxlng, timed[t].coords[c][1]);
            minlat = Math.min(minlat, timed[t].coords[c][0]);
            minlng = Math.min(minlng, timed[t].coords[c][1]);
            timed[t].coords[c] = new L.LatLng(timed[t].coords[c][0], timed[t].coords[c][1]);
          }
          timelyr.geo = new L.polygon( timed[t].coords );
        }
        else{
          // Point
          maxlat = Math.max(maxlat, timed[t].coords[0]);
          maxlng = Math.max(maxlng, timed[t].coords[1]);
          minlat = Math.min(minlat, timed[t].coords[0]);
          minlng = Math.min(minlng, timed[t].coords[1]); 
          timelyr.geo = new L.marker( new L.LatLng( timed[t].coords[0], timed[t].coords[1] ) );
        }
        timelayers.push(timelyr);
      }
    }
    var fixed = myjson.fixed;
    if(fixed.geojson.length){
      // static GeoJSON
      L.geoJson( { "type": "FeatureCollection", "features": fixed.geojson } , { onEachFeature: jsonmap });
    }
    // static KML, currently unsupported
    //for(var t=0;t<fixed.kml.length;t++){
    //  // catch minlat, maxlat, minlng, maxlng, mintime, maxtime
    //}
    map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
    updateTimeline();
  }
  else{
    // load default GeoJSON from Chicago
    $.getJSON('/geos/chicago.geojson', function(gj){
      L.geoJson(gj, { onEachFeature: jsonmap });
      map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
      updateTimeline();
    });
  }
  
  // add play button timer
  playStep = null;
  $(".btn-success").on("click", function(){
    if(!playStep){
      if(!settime){
        settime = mintime;
      }
      playStep = setInterval(function(){
        settime = Math.min(maxtime, settime + (maxtime - mintime) / 500 );
        $("#slidebar").slider({ value: settime });
        displayTime(settime);
        geotimes(settime);
      }, 50);
    }
  });

  // trim the gpx
  $("#edittime").on("click", function(){
    if(mintime * 1 < maxtime * 1){
      $("#trimstart").text( (new Date(mintime)).toUTCString() );
      $("#trimend").text( (new Date(maxtime)).toUTCString() );
      //$("#trimslider").html("");
      $("#trimslider").slider({
        range: true,
        min: mintime,
        max: maxtime,
        values: [ mintime, maxtime ],
        slide: function(event, ui) {
          trimvals = ui.values;
          $("#trimstart").text( (new Date( ui.values[0] )).toUTCString() );
          $("#trimend").text( (new Date( ui.values[1] )).toUTCString() );
        }
      });
      $("#timelinetrim").modal('show');
    }
  });
  $("#trimnow").on("click", function(){
    mintime = trimvals[0];
    maxtime = trimvals[1];
    trimGPS();
    updateTimeline();
  });

});

var displayTime = function(t){
  $("#readtime").text( (new Date(t)).toUTCString() );
};

var blockHandler = function(e){
  e.stopPropagation();
  e.preventDefault();
};

var dropFile = function(e){
  e.stopPropagation();
  e.preventDefault();

  files = e.dataTransfer.files;
  if(files && files.length){
    if(firstfile){
      // remove default map
      for(var t=0;t<timelayers.length;t++){
        map.removeLayer(timelayers[t].geo);
      }
      timelayers = [ ];

      // reset defaults
      mintime = (new Date("January 1, 5000")) * 1;
      maxtime = (new Date("January 1, 100")) * 1;
      maxlat = -90;
      minlat = 90;
      maxlng = -180;
      minlng = 180;
    }
    firstfile = false;

    reader = new FileReader();
    reader.onload = processFile;
    fileindex = 0;
    reader.readAsText(files[0]);
  }
};

function processFile(e){
  var injson;
  try{
    injson = $.parseJSON( e.target.result );
  }
  catch(err){
    // XML-based (KML, GPX)
    var xmlf;
    try{
      xmlf = $.parseXML( e.target.result );
    }
    catch(err){
      // neither JSON or XML-based; go for CSV
      var lines = e.target.result.split('\n');
      if(lines.length == 1){
        lines = lines[0].split('\r');
      }
      var lineswithlength = [0, 0];
      var latcol = 0, lngcol = 1, timecol = 2;
      
      for(var i=0;i<lines.length;i++){
        var rawline = replaceAll(lines[i],'"','').split(',');
        // skip lines without any length
        if(rawline.length < 3){
          continue;
        }
        // wait to find three lines with the same length
        if(lineswithlength[0] == 0){
          lineswithlength = [1, rawline.length];
          continue;
        }
        else if(lineswithlength[0] < 3){
          if(rawline.length == lineswithlength[1]){
            // another line agrees that this is the standard length
            lineswithlength[0]++;
            if(lineswithlength[0] >= 3){
              // standard length has just been found; restart count
              i = -1;
              // add buttons and prompt for column-naming
              $("#colnamer").text("latitude");
              setMyButton = function(col){
                latcol = col;
                $("#colbtn" + col).addClass("btn-success").html('&check;');
                $("#colnamer").text("longitude");
                setMyButton = function(col){
                  lngcol = col;
                  $("#colbtn" + col).addClass("btn-success").html('&check;');
                  $("#colnamer").text("time");
                  setMyButton = function(col){
                    timecol = col;
                    $("#colbtn" + col).addClass("btn-success").html('&check;');

                    var movemarker = new L.marker( new L.LatLng(0, 0), { clickable: false } );
                    var moveline = [ ];

                    for(var i=0;i<lines.length;i++){
                      var rawline = replaceAll(lines[i],'"','').split(',');
                      if(rawline.length == lineswithlength[1]){
                        // valid length line
                  
                        // avoid labels rows, where lat / lng / time cannot be decoded
                        if(isNaN(rawline[latcol] * 1) || isNaN(rawline[lngcol] * 1)){
                          continue;
                        }
                        var mytime = new Date();
                        try{
                          if(isNaN(rawline[timecol]) * 1){
                            mytime = new Date( rawline[timecol] );
                          }
                          else{
                            mytime = new Date( rawline[timecol] * 1 );                              
                          }
                        }
                        catch(e){
                          mytime = null;
                        }
                        if(mytime === null){
                          continue;
                        }

                        var mycoord = new L.LatLng( rawline[latcol] * 1.0, rawline[lngcol] * 1.0 );
                        moveline.push(mycoord);
                        maxlat = Math.max(maxlat, mycoord.lat);
                        maxlng = Math.max(maxlng, mycoord.lng);
                        minlat = Math.min(minlat, mycoord.lat);
                        minlng = Math.min(minlng, mycoord.lng);              

                        var mytime;
                        if(isNaN(rawline[timecol]) * 1){
                          mytime = new Date( rawline[timecol] );
                        }
                        else{
                          mytime = new Date( rawline[timecol] * 1 );                              
                        }
                        mintime = Math.min( mintime, mytime * 1 );
                        maxtime = Math.max( maxtime, mytime * 1 );

                        timelayers.push({
                          geo: movemarker,
                          ll: mycoord,
                          time: mytime
                        });
                      }
                    }
                    var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
                    oldlines.push(oldline);
                    map.addLayer(oldline);
                    $(".modal").modal('hide');

                    updateTimeline();
                    fileindex++;
                    if(fileindex < files.length){
                      return reader.readAsText(files[fileindex]);
                    }
                    else{
                      map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
                      return savemap();
                    }
                  };
                };
              };
              var colButtons = "";
              for(var c=0;c<lineswithlength[1];c++){
                colButtons += "<td><a id='colbtn" + c + "' class='btn' href='#' onclick='setMyButton(" + c + ")'>X</a></td>";
              }
              $("#csvguide table").html("<tr>" + colButtons + "</tr>");
            }
            continue;
          }
          else{
            // a line with a different length, resets lineswithlength
            lineswithlength = [1, rawline.length];
            continue;
          }
        }
        else if(rawline.length != lineswithlength[1]){
          // length of standard lines is known; this one does not match
          continue;
        }

        // only lines with confirmed proper length reach this point
        var cols = "";
        for(var c=0;c<rawline.length;c++){
          cols += "<td>" + replaceAll( replaceAll( rawline[c], "<", "&lt;" ), ">", "&gt;") + "</td>";
        }

        $("#csvguide table").append('<tr>' + cols + '</tr>');
      }

      if(lineswithlength[0] >= 3 && lineswithlength[1] >= 3){
        $("#csvguide").modal('show');
        $(".cancelcsv").click(function(e){
          // user canceled this CSV... move on to next files
          fileindex++;
          if(fileindex < files.length){
            return reader.readAsText(files[fileindex]);
          }
          else{
            map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
            return savemap();
          }
        });
        // process continues after using modal to process or cancel CSV
        return;
      }
      else{
        // not a valid CSV... continue to next file
        fileindex++;
        if(fileindex < files.length){
          return reader.readAsText(files[fileindex]);
        }
        else{
          map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
          return savemap();
        }
      }
    }
    var placemarks = xmlf.getElementsByTagName("Placemark");
    if(!placemarks.length){
      // no KML Placemarks, go to GPX reader
      var pts = xmlf.getElementsByTagName("trkpt");
      var times = xmlf.getElementsByTagName("time");
      var moveline = [ ];
      if(pts.length && times.length && pts.length == times.length){
        var movemarker = new L.marker( new L.LatLng(0, 0), { clickable: false } );
        for(var p=0;p<pts.length;p++){
          var mycoord = new L.LatLng( 1.0 * pts[p].getAttribute("lat"), 1.0 * pts[p].getAttribute("lon") );
          moveline.push(mycoord);
          maxlat = Math.max(maxlat, mycoord.lat);
          maxlng = Math.max(maxlng, mycoord.lng);
          minlat = Math.min(minlat, mycoord.lat);
          minlng = Math.min(minlng, mycoord.lng);              

          var mytime = new Date( $(times[p]).text() );
          mintime = Math.min( mintime, mytime * 1 );
          maxtime = Math.max( maxtime, mytime * 1 );

          timelayers.push({
            geo: movemarker,
            ll: mycoord,
            time: mytime
          });
        }
      }
      var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
      oldlines.push(oldline);
      map.addLayer(oldline);
      updateTimeline();
      fileindex++;
      if(fileindex < files.length){
        return reader.readAsText(files[fileindex]);
      }
      else{
        map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
        savemap();
        return;
      }
    }
    // KML loading
    var oldmarker = new L.marker(new L.LatLng(0,0), { clickable: false });
    var oldmoveline = [ ];
    for(var i=0;i<placemarks.length;i++){
      var inkml = placemarks[i];
      var whens = inkml.getElementsByTagName("when");
      var coords = inkml.getElementsByTagName("coord");
      if(whens.length && !coords.length){
        coords = inkml.getElementsByTagName("gx:coord");
        if(!coords.length){
          // old-style <Placemark><TimeSpan><when> syntax
          coords = inkml.getElementsByTagName("coordinates");
          var rawcoord = $(coords[0]).text().split(",");
          var mycoord = new L.LatLng( rawcoord[1] * 1.0, rawcoord[0] * 1.0 );
          oldmoveline.push(mycoord);
          maxlat = Math.max(maxlat, mycoord.lat);
          maxlng = Math.max(maxlng, mycoord.lng);
          minlat = Math.min(minlat, mycoord.lat);
          minlng = Math.min(minlng, mycoord.lng);              

          var mytime = new Date( $(whens[0]).text() );
          mintime = Math.min( mintime, mytime * 1 );
          maxtime = Math.max( maxtime, mytime * 1 );

          timelayers.push({
            geo: oldmarker,
            ll: mycoord,
            time: mytime
          });
          continue;
        }
      }
      var moveline = [ ];
      if(whens.length && coords.length && whens.length == coords.length){
        var movemarker = new L.marker( new L.LatLng(0, 0), { clickable: false } );
        for(var c=0;c<coords.length;c++){
          var rawcoord = $(coords[c]).text().split(" ");
          var mycoord = new L.LatLng( rawcoord[1] * 1.0, rawcoord[0] * 1.0 );
          moveline.push(mycoord);
          maxlat = Math.max(maxlat, mycoord.lat);
          maxlng = Math.max(maxlng, mycoord.lng);
          minlat = Math.min(minlat, mycoord.lat);
          minlng = Math.min(minlng, mycoord.lng);              

          var mytime = new Date( $(whens[c]).text() );
          mintime = Math.min( mintime, mytime * 1 );
          maxtime = Math.max( maxtime, mytime * 1 );
              
          timelayers.push({
            geo: movemarker,
            ll: mycoord,
            time: mytime
          });
        }
        var oldline = new L.polyline(moveline, { color: "#000", weight: 1 });
        oldlines.push(oldline);
        map.addLayer(oldline);
      }
      else{
        // check for <begin> and <end> tags
        var begin = inkml.getElementsByTagName("begin");
        var end = inkml.getElementsByTagName("end");
        if(begin.length || end.length){
          // KML placemark with a begin and/or end
          var timefeature = { };
          if(begin.length){
            timefeature.start = new Date( $(begin[0]).text() );
            mintime = Math.min(mintime, timefeature.start * 1);
            maxtime = Math.max(maxtime, timefeature.start * 1);
          }
          if(end.length){
            timefeature.end = new Date( $(end[0]).text() );
            mintime = Math.min(mintime, timefeature.end * 1);
            maxtime = Math.max(maxtime, timefeature.end * 1);
          }
          timefeature.geo = kmlmap(inkml);
          timelayers.push( timefeature );
        }
        else{
          // KML object without a time
          var maps = kmlmap(inkml);
          for(var m=0;m<maps.length;m++){
            map.addLayer( maps[m] );
            fixlayers.push( maps );
          }
        }
      }
    }
    if(oldmoveline.length){
      var oldline = new L.polyline(oldmoveline, { clickable: false });
      oldlines.push(oldline);
      map.addLayer(oldline);
    }
    updateTimeline();
    fileindex++;
    if(fileindex < files.length){
      return reader.readAsText(files[fileindex]);
    }
    else{
      map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
      savemap();
      return;
    }
  }
  L.geoJson(injson, {
    onEachFeature: jsonmap
  });
  updateTimeline();

  fileindex++;
  if(fileindex < files.length){
    reader.readAsText(files[fileindex]);
  }
  else{
    map.fitBounds(new L.LatLngBounds(new L.LatLng(minlat, minlng), new L.LatLng(maxlat, maxlng)));
    savemap();
  }
}

function savemap(){
  files = [ ];

  if(!timelayers.length || lasttimelength == timelayers.length + fixlayers.length){
    // if there is no geotime data or additional points, the map doesn't save
    return;
  }
  lasttimelength = timelayers.length + fixlayers.length;

  var saver = {
    timed: [ ],
    fixed: {
      kml: [ ],
      geojson: [ ]
    }
  };
  var movemarker = null;
{'fraction_non_alphanumeric': 0.1528527370855821, 'fraction_numerical': 0.011372397841171935, 'mean_word_length': 1.8015524805939926, 'pattern_counts': {'":': 4, '<': 81, '<?xml version=': 0, '>': 43, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 12}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4497063', 'n_tokens_mistral': 13587, 'n_tokens_neox': 13110, 'n_words': 2648}