annotatAR

lo-fi augmented reality for the mobile web

Rachel Boyce

Digital Media Capstone Studio, Fall 2015

Harvard Extension School

  "Digital culture potentially makes us members of a shared virtual universe of knowledge, the common fostering of which will make the social tie the most important currency in future society."


Simon Lindgren, New Noise

Computer-mediated communication

Computer-mediated communication

  • Immediate

  • Networked

  • Publicly broadcast

  • Self-curated

  • Multi-sited

Computer-mediated communication

  “Art makes data sticky."

 

Laurie Frick

Vietnam Veterans Memorial, Maya Lin

https://en.wikipedia.org/wiki/Vietnam_Veterans_Memorial

Net_dérive, Petra Gemeinboek

http://www.impossiblegeographies.net/net_derive/

#flowersforfreedom, Ai WeiWei

https://c1.staticflickr.com/3/2928/14597243737_01ab66973a_b.jpg

NO AD, RE+PUBLIC

https://play.google.com/store/apps/details?id=com.republic.NOAD

Invisible Monument,

Halsey Bergund & Lara Baladi

http://invisiblemonument.com/

annotatAR

twitter-augmented reality

AnnotatAR

Twitter API

#HashTag

userid

tweet text

timestamp

Database

Collection

userid

tweet text

timestamp

meteor.js [server]

Mobile site

Website

Information about project

Map of deployments

Data visualization (in future)

Camera input

Tweet overlay

Screen capture

Data download

URL

annotatar.xyz

Server-side

Meteor.startup(function () {

    Hashtags.remove({});
    Tweets.remove({});

   //**** hashtags **/
   Meteor.call("addHashtag", 42.37, -71.12, "harvardclimate");
   Meteor.call("addHashtag", 48.86, 2.37, "cop21");
   Meteor.call("addHashtag", 48.86, 2.38, "cop21");
   Meteor.call("addHashtag", 40.71, -74.01, "ows");
   Meteor.call("addHashtag", 40.70, -74.01, "occupywallstreet");
   Meteor.call("addHashtag", 40.76, -73.99, "riseupoctober");
   Meteor.call("addHashtag", 40.73, -74, "NYCStandsWithMinneapolis");
   Meteor.call("addHashtag", 40.73, -73.99, "Justice4Jamar");
   Meteor.call("addHashtag", 42.35, -71.06, "occupyboston");
   Meteor.call("addHashtag", 42.35, -71.05, "occupyboston");
   var hashtagsCursor = Hashtags.find();
   hashtagsCursor.map(function(h){
    console.log("pushing: ", h.hashtag);
    hashtags.push(h.hashtag); 
  })


   Twit = new TwitMaker({
    consumer_key:         Meteor.settings.twitter.consumer_key
    , consumer_secret:      Meteor.settings.twitter.consumer_secret
    , access_token:         Meteor.settings.twitter.access_token
    , access_token_secret:  Meteor.settings.twitter.access_token_secret
  });

    //*** REST

    var handleTweets = Meteor.bindEnvironment(function(err, data, response) {
      console.log(data);
      console.log("***********************", err, "***********************");
      for(var i = 0; i < data.statuses.length; i++){
        Meteor.call("addTweet", data.statuses[i].text, data.search_metadata.query, data.statuses[i].created_at);
      }
    });


    //**************************************************//
    // ******  uncomment to turn the rest on: ****** //
    hashtags.map(function(h){
      hashtag = h;
      Twit.get('search/tweets',
      {
       q: h,
       count: 50
     }, handleTweets);

Client-side: hashtag subscription

  try {
    if (navigator.geolocation){
      navigator.geolocation.getCurrentPosition(function(position){
        navLat = position.coords.latitude;
        navLon = position.coords.longitude;
        navLat = Math.round(100*navLat)/100;
        navLon = Math.round(100*navLon)/100;
        console.log("lat: ", navLat, " long: ", navLon, " accuracy: ", position.coords.accuracy);
        $("#dynamsg").append('<p>latitude: '+navLat+' longitude: '+navLon+' accuracy: <span id="acc">'+position.coords.accuracy+'</span></p>');
        Meteor.call("findHashtag", navLat, navLon, function(err, result){
          if(result){
            hashtag = result;
            console.log("subscribing to: ", hashtag);
            Meteor.subscribe("tweets", hashtag);
            Session.set("hashtag", hashtag);
          }
          else{
            console.log("no hashtag found! subscribing to #occupy");
            Meteor.subscribe("tweets", "occupy");
            Session.set("hashtag", occupy);
          }
        });
      });
    }
    else{
      navLat = 0;
      navLon = 0;
      console.log("no geolocation detected");
    }
  } catch(err){
    console.log("geolocation error: ", err);
  }

Client-side: render video & tweets

 try{
    navigator.getUserMedia = navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
    window.URL = window.URL || window.mozURL || window.webkitURL;

      navigator.getUserMedia({ audio: false, 'video': true}, 
      function(stream){
        video.src = window.opera ? stream : window.URL.createObjectURL(stream);
        video.play();
      }, 
      function(err){
        console.err("video capture error: ", err);
      })

    } catch(err){
      console.log("navigator.getUserMedia error: ", err);
    }

    video.style.position = "absolute";
    video.style.visibility = "hidden";

  setInterval(function(){
    if(ffmobile){
      var wh = window.innerHeight - video.height;
      context.save();
      context.scale(1, -1);
      context.translate(0,-wh);
      var img = context.drawImage(video, 0, -video.height, window.innerWidth, window.innerHeight);
      context.restore();
      renderTweets();
    }
    else if(mobile){
      renderWarning("Browser not supported.");
    }
    else{
      var img = context.drawImage(video, 0, 0, window.innerWidth, window.innerHeight);
      renderTweets();
    }


//*********

var renderTweets = function(){
  var tweets = Tweets.find({}, {sort: {createdAt: -1}}).fetch();
  if(!tweets.length) {
    console.log("no tweets");
    return;
  }
  var ageMax = 0;
  tweets.map(function(data){
     var age = parseInt(Date.now() - data.tweetCreatedAt);
     if (ageMax < age){
       ageMax = age;
     }
   });
  var i = 1;
  tweets.map(function(data){
    i+=1;
    var age = parseInt(Date.now() - data.tweetCreatedAt);
    var fsizeMax = 50,
    fsizeMin = 0;
    if (age > ageMax){ age = ageMax };
    // the scale needs to be non-linear...
    var fsize = Math.floor((((fsizeMin-fsizeMax)*age)/ageMax)+fsizeMax);
    alphaMax = 1.0;
    alphaMin = 0;
    var alpha = (((alphaMin-alphaMax)*age)/ageMax)+alphaMax;
    fsize = 30;
    var alpha = 0.9;
    context.font = fsize+'px "Walter Turncoat"';
    context.fillStyle = 'rgba('+data.color.r+','+data.color.g+','+ data.color.b+','+ alpha+')';
    context.fillText(data.text, data.xPos+offset.x, data.yPos+offset.y);
  }); // end tweets.map
  $("#hashtag").html("<p>"+Session.get("hashtag")+"</p>");
  $("#tweetBtn").href("https://twitter.com/home?status=%23"+Session.get("hashtag")+"%20%23annotatAR");
}; // end renderTweets

  What do we achieve with lo-fi augmented reality for the mobile web?

  Détournment or re-taking of public space with its corresponding virtual settlement

    Provide an outlet for participation and action in a non-disruptive, discursive context

  Amplify dissenting voices from people who are geographically and demographically diverse that unite around ideological issues

Utilize emerging web technologies like Meteor.js for non-business purposes

    - Data on the wire

    - Quick deployment

    - Participation in the development community 

Developing for a moving target

can i use getUserMedia?

http://caniuse.com/#search=getusermedia

Chrome

getUserMedia deprecation without HTTPS

 

does not prompt user to select camera on mobile device, defaults to selfie-cam

 

 

Firefox

inconsistent documentation and surprising bugs

 

iOS

no native support for WebRTC

 

 

https://www.flickr.com/photos/tensafefrogs/728581345/

"A rhizome never ceases to connect semiotic chains, organizations of power, and events in the arts, sciences, and social struggles.

 

The rhizome is something altogether different, a map and not a tracing

 

Make maps, not tracings."

 

Gilles Deleuze & Félix Guattari, On the Line

Thank you!

Made with Slides.com