├── .gitignore ├── Config.pde ├── Functions.pde ├── Move.pde ├── MovesMapper_1_0.pde ├── Notes.pde ├── README.md ├── SampleScreen_1.png ├── SampleScreen_2.png └── SampleScreen_3.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore unneeded files. 2 | .DS_Store 3 | data/ 4 | -------------------------------------------------------------------------------- /Config.pde: -------------------------------------------------------------------------------- 1 | // - - - - - - - - - - - - - - - - - - - - - - - 2 | // PRIVATE GLOBAL VARIABLES 3 | // - - - - - - - - - - - - - - - - - - - - - - - 4 | 5 | // Data 6 | String accessToken = ""; 7 | 8 | // Time Zone 9 | String tzOffsetLabel = "-4:00"; 10 | int tzOffset = -14400; 11 | -------------------------------------------------------------------------------- /Functions.pde: -------------------------------------------------------------------------------- 1 | 2 | // Initialize Empty Checkbox GUI 3 | // - - - - - - - - - - - - - - - - - - - - - - - 4 | void GUIcheckbox(String name_, int xpos_, int ypos_) { 5 | checkbox = cp5.addCheckBox(name_) 6 | .setPosition(xpos_, ypos_) 7 | .setColorForeground(color(190)) 8 | .setColorBackground(color(60)) 9 | .setColorActive(color(255, 128)) 10 | .setColorLabel(color(255)) 11 | .setSize(15, 15) 12 | .setItemsPerRow(7) 13 | .setSpacingColumn(20) 14 | .setSpacingRow(10) 15 | ; 16 | } 17 | 18 | // Initialize Empty Months Dropdown GUI 19 | // - - - - - - - - - - - - - - - - - - - - - - - 20 | void GUIdropdownD1(String name_, int xpos_, int ypos_, String label_) { 21 | d1 = cp5.addDropdownList(name_) 22 | .setPosition(xpos_, ypos_) 23 | .setItemHeight(20) 24 | .setBarHeight(15) 25 | .setColorBackground(color(60)) 26 | .setColorActive(color(255, 128)) 27 | ; 28 | d1.captionLabel().style().marginTop = 3; 29 | d1.valueLabel().style().marginTop = 3; 30 | d1.captionLabel().set(label_); 31 | } 32 | 33 | 34 | // Initialize Empty Years Dropdown GUI 35 | // - - - - - - - - - - - - - - - - - - - - - - - 36 | void GUIdropdownD2(String name_, int xpos_, int ypos_, String label_) { 37 | d2 = cp5.addDropdownList(name_) 38 | .setPosition(xpos_, ypos_) 39 | .setItemHeight(20) 40 | .setBarHeight(15) 41 | .setColorBackground(color(60)) 42 | .setColorActive(color(255, 128)) 43 | ; 44 | d2.captionLabel().style().marginTop = 3; 45 | d2.valueLabel().style().marginTop = 3; 46 | d2.captionLabel().set(label_); 47 | } 48 | 49 | 50 | // Populate Months Dropdown 51 | // - - - - - - - - - - - - - - - - - - - - - - - 52 | void GUImonth(int year_, int month_, int monthSet) { 53 | if (year_ == year()) { 54 | for (int i=1; i<=month_; i++) { 55 | d1.addItem(monthNames[i-1], i); 56 | } 57 | d1.setValue(monthSet); 58 | } 59 | else { 60 | for (int i=1; i<=monthNames.length; i++) { 61 | d1.addItem(monthNames[i-1], i); 62 | } 63 | d1.setValue(1); 64 | } 65 | } 66 | 67 | 68 | // Populate Years Dropdown 69 | // - - - - - - - - - - - - - - - - - - - - - - - 70 | void GUIyear(int year_) { 71 | for (int i=1; i+2011<=year_; i++) { 72 | d2.addItem(str(i+2011), i+2011); 73 | } 74 | d2.setValue(year_); 75 | } 76 | 77 | 78 | // Populate Days Dropdown 79 | // - - - - - - - - - - - - - - - - - - - - - - - 80 | void GUIday(int year_, int month_, int day_) { 81 | if (month_ == month() && year_ == year()) { 82 | for (int i=1; i<=day(); i++) { 83 | checkbox.addItem(str(i), i); 84 | } 85 | checkbox.toggle(day_-1); 86 | } 87 | else { 88 | if (int(d2.getValue()) % 4 == 0) { 89 | for (int i=1; i<=int(monthDaysLeap[month_-1]); i++) { 90 | checkbox.addItem(str(i), i); 91 | } 92 | checkbox.toggle(0); 93 | } 94 | else { 95 | for (int i=1; i<=int(monthDaysStandard[month_-1]); i++) { 96 | checkbox.addItem(str(i), i); 97 | } 98 | checkbox.toggle(0); 99 | } 100 | } 101 | } 102 | 103 | 104 | // GUI Event Listener 105 | // - - - - - - - - - - - - - - - - - - - - - - - 106 | void controlEvent(ControlEvent theEvent) { 107 | if (theEvent.isFrom(checkbox)) { 108 | listDates(); 109 | for (int i=0; i 2) { 119 | checkbox.remove(); 120 | GUIday(int(d2.getValue()), 1, 1); 121 | d1.clear(); 122 | GUImonth(int(d2.getValue()), month(), 1); 123 | } 124 | else if (theEvent.isFrom("d1-month") && frameCount > 2) { 125 | checkbox.remove(); 126 | GUIcheckbox("checkbox", 150, height-135); 127 | GUIday(int(d2.getValue()), int(d1.getValue()), 1); 128 | } 129 | } 130 | 131 | 132 | // Format Events into Array of Dates 133 | // - - - - - - - - - - - - - - - - - - - - - - - 134 | void listDates() { 135 | movesDates = new int[0]; 136 | for (int i=0; i 0) ? 86400 : 0; 70 | dateTime = split(dateTime[1], 'Z'); 71 | path_time = append(path_time, int(dateTime[0])); 72 | // convert path_time to minutes 73 | hours = floor(path_time[path_time.length-1]*0.0001); 74 | mins = floor((path_time[path_time.length-1] - hours*10000)*0.01); 75 | secs = path_time[path_time.length-1] - hours*10000 - mins*100; 76 | time_offset = hours*3600 + mins*60 + secs + tzOffset; 77 | path_time[path_time.length-1] = (time_offset > 0) ? time_offset : time_offset + date_offset; 78 | } 79 | } 80 | } 81 | catch (Exception e) { 82 | // println("error "+e); 83 | } 84 | try { 85 | JSONObject place = segment.getJSONObject("place"); 86 | // println("Start Time: " + segment.getString("startTime")); 87 | // println("* " + place.getString("name")); 88 | // println("* Type: " + place.getString("type")); 89 | // println("* ID: " + place.getInt("id")); 90 | JSONObject location = place.getJSONObject("location"); 91 | // println("* Lat: " + location.getFloat("lat")); 92 | // println("* Long: " + location.getFloat("lon")); 93 | // build unique places array 94 | place_name = append(place_name, place.getString("name")); 95 | place_long = append(place_long, location.getFloat("lon")); 96 | place_lat = append(place_lat, location.getFloat("lat")); 97 | // compute place_time in minutes 98 | PlaceDateTime = split(segment.getString("startTime"), 'T'); 99 | place_date = PlaceDateTime[0]; 100 | date_offset = ((int(place_date) - int(jsonDate)) < 0) ? -86400 : ((int(place_date) - int(jsonDate)) > 0) ? 86400 : 0; 101 | PlaceDateTime = split(PlaceDateTime[1], 'Z'); 102 | place_time = append(place_time, int(PlaceDateTime[0])); 103 | // convert path_time to minutes 104 | hours = floor(place_time[place_time.length-1]*0.0001); 105 | mins = floor((place_time[place_time.length-1] - hours*10000)*0.01); 106 | secs = place_time[place_time.length-1] - hours*10000 - mins*100; 107 | time_offset = hours*3600 + mins*60 + secs + tzOffset; 108 | place_time[place_time.length-1] = (time_offset > 0) ? time_offset : time_offset + date_offset; 109 | } 110 | catch (Exception e) { 111 | // println("error "+e); 112 | } 113 | } 114 | } 115 | 116 | float min_path_lat = 90; 117 | float min_path_long = 180; 118 | 119 | float min_place_lat = 90; 120 | float min_place_long = 180; 121 | 122 | float max_path_lat = -90; 123 | float max_path_long = -180; 124 | 125 | float max_place_lat = -90; 126 | float max_place_long = -180; 127 | 128 | if (path_lat.length > 0 && path_long.length > 0) { 129 | min_path_lat = min(path_lat); 130 | max_path_lat = max(path_lat); 131 | 132 | min_path_long = min(path_long); 133 | max_path_long = max(path_long); 134 | } 135 | 136 | if (place_lat.length > 0 && place_long.length > 0) { 137 | min_place_lat = min(place_lat); 138 | max_place_lat = max(place_lat); 139 | 140 | min_place_long = min(place_long); 141 | max_place_long = max(place_long); 142 | } 143 | 144 | min_lats.set(jsonDate, min(min_path_lat, min_place_lat)); 145 | max_lats.set(jsonDate, max(max_path_lat, max_place_lat)); 146 | 147 | min_longs.set(jsonDate, min(min_path_long, min_place_long)); 148 | max_longs.set(jsonDate, max(max_path_long, max_place_long)); 149 | } 150 | 151 | void display() { 152 | // Draw Paths 153 | noFill(); 154 | for (int i=0; i 0 && path_time[i] < TimeOfDay && path_time[i] > 0) { 156 | if (path_type[i].equals("cyc") && cycle.booleanValue()) { 157 | stroke(0, 255, 255, 100); 158 | line(loc_x[i], loc_y[i], loc_x[i-1], loc_y[i-1]); 159 | } 160 | else if (path_type[i].equals("run") && run.booleanValue()) { 161 | stroke(255, 0, 255, 100); 162 | line(loc_x[i], loc_y[i], loc_x[i-1], loc_y[i-1]); 163 | } 164 | else if (path_type[i].equals("wlk") && walk.booleanValue()) { 165 | stroke(255, 255, 0, 100); 166 | line(loc_x[i], loc_y[i], loc_x[i-1], loc_y[i-1]); 167 | } 168 | else if (path_type[i].equals("trp") && transportation.booleanValue()) { 169 | stroke(255, 40); 170 | line(loc_x[i], loc_y[i], loc_x[i-1], loc_y[i-1]); 171 | } 172 | } 173 | } 174 | // Draw Places 175 | noStroke(); 176 | for (int i=0; i 0 ) { 180 | place_loc_x = map(place_long[i], min_long, max_long, margin + space_long, canvasSize-margin - space_long); 181 | place_loc_y = map(place_lat[i], max_lat, min_lat, top_margin + space_lat, canvasSize-margin - space_lat); 182 | fill(255); 183 | ellipse(place_loc_x, place_loc_y, placeSize, placeSize); 184 | fill(255, 150); 185 | textSize(9); 186 | text(place_name[i], place_loc_x+placeSize, place_loc_y+3); 187 | placesDrawn.set(place_name[i], "true"); 188 | } 189 | } 190 | } 191 | 192 | 193 | void mapBounds() { 194 | float max_lats_array[] = new float[0]; 195 | float min_lats_array[] = new float[0]; 196 | float max_longs_array[] = new float[0]; 197 | float min_longs_array[] = new float[0]; 198 | for (int i=0; i lat_delta) { 233 | space_long = 0; 234 | space_lat = (canvasSize - margin - top_margin) * (1 - lat_delta / long_delta) / 2; 235 | } 236 | else { 237 | space_lat = 0; 238 | space_long = (canvasSize - 2 * margin) * (1 - long_delta / lat_delta) / 2; 239 | } 240 | } 241 | 242 | void jsonFetch(String fetchDate) { 243 | String thisYear = str(year()); 244 | String thisMonth = str(month()); 245 | String thisDay = str(day()); 246 | if (int(thisMonth) <= 9) { 247 | thisMonth = "0" + thisMonth; 248 | } 249 | if (int(thisDay) <= 9) { 250 | thisDay = "0" + thisDay; 251 | } 252 | String thisDate = thisYear + thisMonth + thisDay; 253 | File f = new File(dataPath( fetchDate + ".json" )); 254 | if ( !f.exists() || fetchDate.equals(thisDate) ) { // Call API if data doesn't exist 255 | try { 256 | println("Querying… " + fetchDate); 257 | apiCall = "https://api.moves-app.com/api/v1/user/storyline/daily/" + fetchDate + "?trackPoints=true&access_token=" + accessToken; 258 | JSONArray result = loadJSONArray( apiCall ); 259 | if (result != null) { // unsure if this call works 260 | saveJSONArray(result, dataPath(fetchDate + ".json")); 261 | } 262 | } 263 | catch (Exception e) { 264 | println("No Data"); 265 | } 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /MovesMapper_1_0.pde: -------------------------------------------------------------------------------- 1 | // - - - - - - - - - - - - - - - - - - - - - - - 2 | // MOVES MAPPER v1.0 3 | // Nicholas Felton — September 3, 2013 4 | // - - - - - - - - - - - - - - - - - - - - - - - 5 | 6 | 7 | // - - - - - - - - - - - - - - - - - - - - - - - 8 | // LIBRARIES 9 | // - - - - - - - - - - - - - - - - - - - - - - - 10 | import controlP5.*; 11 | import java.util.Map; 12 | 13 | 14 | // - - - - - - - - - - - - - - - - - - - - - - - 15 | // GLOBAL VARIABLES 16 | // - - - - - - - - - - - - - - - - - - - - - - - 17 | 18 | // General 19 | int canvasSize = 800; // 550 minimum value 20 | 21 | // Data 22 | String date, year, month, day; 23 | int[] movesDates; 24 | String apiCall; 25 | HashMap moves = new HashMap(); 26 | 27 | // Draw 28 | int placeSize = 6; 29 | FloatDict max_lats = new FloatDict(); 30 | FloatDict min_lats = new FloatDict(); 31 | FloatDict max_longs = new FloatDict(); 32 | FloatDict min_longs = new FloatDict(); 33 | float max_lat, min_lat, max_long, min_long; 34 | float mapOffset; 35 | StringDict placesDrawn = new StringDict(); 36 | int margin = 30; 37 | int top_margin = margin + 50; 38 | 39 | // GUI 40 | ControlP5 cp5; 41 | DropdownList d1, d2; 42 | CheckBox checkbox; 43 | Button labels, walk, run, cycle, transportation; 44 | String[] monthNames = { 45 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" 46 | }; 47 | String[] monthDaysStandard = { 48 | "31", "28", "31", "30", "31", "30", "31", "31", "30", "31", "30", "31" 49 | }; 50 | String[] monthDaysLeap = { 51 | "31", "29", "31", "30", "31", "30", "31", "31", "30", "31", "30", "31" 52 | }; 53 | int TimeOfDay; 54 | String TimeString = "00:00:00"; 55 | 56 | 57 | // - - - - - - - - - - - - - - - - - - - - - - - 58 | // SETUP 59 | // - - - - - - - - - - - - - - - - - - - - - - - 60 | void setup() { 61 | size(canvasSize, canvasSize+150); 62 | smooth(); 63 | hint(ENABLE_STROKE_PURE); 64 | cp5 = new ControlP5(this); 65 | 66 | // Instantiate Date GUI 67 | GUIcheckbox("checkbox", 150, height-135); 68 | GUIdropdownD1("d1-month", 20, height-95, "MONTH"); 69 | GUIdropdownD2("d2-year", 20, height-120, "YEAR"); 70 | 71 | // Populate Date GUI 72 | GUImonth(year(), month(), month()); 73 | GUIyear(year()); 74 | GUIday(year(), month(), day()); 75 | 76 | // Additional GUI 77 | labels = cp5.addButton("labels") 78 | .setPosition(width-120, height-135) 79 | .setSize(100, 20) 80 | .setHeight(15) 81 | .setColorForeground(color(190)) 82 | .setColorBackground(color(60)) 83 | .setColorActive(color(255, 128)) 84 | .setSwitch(true) 85 | .setOn() 86 | ; 87 | 88 | walk = cp5.addButton("walk") 89 | .setPosition(width-120, height-110) 90 | .setSize(100, 20) 91 | .setHeight(15) 92 | .setColorForeground(color(45)) 93 | .setColorBackground(color(60)) 94 | .setColorActive(color(255, 128)) 95 | .setSwitch(true) 96 | .setOn() 97 | ; 98 | 99 | run = cp5.addButton("run") 100 | .setPosition(width-120, height-85) 101 | .setSize(100, 20) 102 | .setHeight(15) 103 | .setColorForeground(color(190)) 104 | .setColorBackground(color(60)) 105 | .setColorActive(color(255, 128)) 106 | .setSwitch(true) 107 | .setOn() 108 | ; 109 | 110 | cycle = cp5.addButton("cycle") 111 | .setPosition(width-120, height-60) 112 | .setSize(100, 20) 113 | .setHeight(15) 114 | .setColorForeground(color(190)) 115 | .setColorBackground(color(60)) 116 | .setColorActive(color(255, 128)) 117 | .setSwitch(true) 118 | .setOn() 119 | ; 120 | 121 | transportation = cp5.addButton("transportation") 122 | .setPosition(width-120, height-35) 123 | .setSize(100, 20) 124 | .setHeight(15) 125 | .setColorForeground(color(190)) 126 | .setColorBackground(color(60)) 127 | .setColorActive(color(255, 128)) 128 | .setSwitch(true) 129 | .setOn() 130 | ; 131 | 132 | // add a vertical slider 133 | cp5.addSlider("TimeOfDay") 134 | .setPosition(margin, margin) 135 | .setSize(width-(2*margin), 10) 136 | .setRange(0, 86400) 137 | .setValue(86400) 138 | .setColorForeground(color(190)) 139 | .setColorBackground(color(60)) 140 | .setColorActive(color(255, 128)) 141 | ; 142 | 143 | // reposition the Label for controller 'slider' 144 | cp5.getController("TimeOfDay").getValueLabel().setText(TimeString).align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0); 145 | cp5.getController("TimeOfDay").getCaptionLabel().align(ControlP5.RIGHT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0); 146 | } 147 | 148 | 149 | // - - - - - - - - - - - - - - - - - - - - - - - 150 | // DRAW LOOP 151 | // - - - - - - - - - - - - - - - - - - - - - - - 152 | 153 | void draw() { 154 | frame.setTitle(int(frameRate) + " fps / " + frameCount + " frames"); 155 | background(40); 156 | placesDrawn.clear(); 157 | timeLabel(); 158 | 159 | // Draw Moves 160 | for (int i=0; i