• fullscreen
  • DataHolder.pde
  • Globe.pde
  • Table.pde
  • WorldVis.pde
  • final float MIN_TO_DEG= 1/60f;     
    final float SEG_TO_DEG= 1/3600f;
    final float SCALE_F= 3e-6;
    
    class DataHolder {  
      String 
      NAME, COUNTRY;
      int 
      VALUE, INDEX;
      float 
      LATITUDE, LONGITUDE;
      PVector 
      START, END;
      boolean 
      hovered=false;
      
      DataHolder (int i){
        INDEX     = i+1;  
        NAME      = t.getString (i+1,0);
        COUNTRY   = t.getString (i+1,1);
        VALUE     = t.getInt    (i+1,2);
        LATITUDE  = parseCoord  (t.getString(i+1,3));
        LONGITUDE = parseCoord  (t.getString(i+1,4));
        START     = polarToCartesian (LATITUDE,LONGITUDE,-1);
        END       = polarToCartesian (LATITUDE,LONGITUDE,VALUE*SCALE_F);
      }
    
      //A custom method to parse GeoNames´s database coordinates 
      float parseCoord(String a){
        //Split the string using whitespace characters as delimiters.
        String[] c= split(a," ");
        //Match a regular expresion in order to exclude any symbol
        for (int i=1;i<c.length;i++) c[i]= match(c[i],"\\d++")[0]; 
        //Transform the coordinates into a single floating value
        float coord= int(c[1]) + int(c[2])*MIN_TO_DEG + int(c[3])*SEG_TO_DEG; 
        //And check orientation: if first char is 'S' or 'W' set a negative sign
        char orientation=c[0].charAt(0);
        if  (orientation==87 || orientation==83) coord*=-DEG_TO_RAD; else coord*=DEG_TO_RAD;
        return coord;  
      }
    
      void setHoveredTo (boolean booleanToSet){
        hovered= booleanToSet;
      }
    
       // returns a Pvector representing the lat, long and altitude in 3d space
       // altitude is relative to the surface of the globe
      PVector polarToCartesian(float lat, float lng, float hght) {
        float shift_lat = lat + HALF_PI;                     // shift the lat by 90 degrees
        float tot_hght  = w.globeRadius + hght;
        
        float x = -tot_hght * sin(shift_lat) * cos(lng);     // -1 needed cause of the orientation of the processing 3d cartesian coordinate system
        float z =  tot_hght * sin(shift_lat) * sin(lng);
        float y =  tot_hght * cos(shift_lat);
        return  new PVector(x,y,z);
      }
      
      void render(PGraphics canvas, boolean buffered){    
        canvas.strokeWeight(LINES_WEIGHT);
        if      (buffered) {canvas.strokeWeight(BUFF_LINES_W); canvas.stroke(INDEX);} 
        else if  (hovered) canvas.stroke(HOVER_COL); 
        else               canvas.stroke(DATA_COL);  
        canvas.line(START.x,START.y,START.z,END.x,END.y,END.z);
      }
          
    }
    
    //A generic class to render textured spheres. The code is extracted from Flink Labs´s work. /////////////////////////
    //At the beginning I started with the "Textured Sphere" example on the GL section of processing examples... /////////
    //Bad election... that code isn´t a good base indeed! It´s all messed up...//////////////////////////////////////////
    //I´ve slightly improved performance, removing unnecessary operations from the render() method.//////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    class Globe {
      
      PImage 
      txtMap;
      int 
      globeRadius; 
      float 
      rWRatio, 
      rHRatio,
      ROTATION_FACTOR=.01*DEG_TO_RAD;
      PVector   
      rotation, 
      rotSpeed;
      
      // Textured sphere implementation 
      float[][] 
      texturedSphereX,
      texturedSphereY, 
      texturedSphereZ, 
      texturedSphereU, 
      texturedSphereV; 
      int   
      texturedSphereDetail;
    
      ////////////////////////////////////////////////////////////////////////CONSTRUCTOR
       
      Globe(int _radius, int _sphereDetail, String _mapFilename) {
        globeRadius = _radius;
        txtMap = loadImage(_mapFilename);
        rWRatio= txtMap.width/globeRadius;
        rHRatio= txtMap.height/globeRadius;
        setTexturedSphereDetail(_sphereDetail); 
        rotation= new PVector(0,HALF_PI);
        rotSpeed= new PVector(0,0);
      }
      
      ////////////////////////////////////////////////////////////////////////////METHODS
      
      void setTexturedSphereDetail(int detail) {   //Set the detail level for textured spheres, constructing the underlying vertex and uv map data  
        
        if (detail == texturedSphereDetail) return; 
        
        texturedSphereDetail = detail; 
        float step = PI / detail; 
        float ustep = .5 / detail; 
        float vstep = 1. / detail; 
        int d1= detail+1;
        int d2= detail*2 +1;
    
        texturedSphereX = new float[d1][d2]; 
        texturedSphereY = new float[d1][d2]; 
        texturedSphereZ = new float[d1][d2]; 
        texturedSphereU = new float[d1][d2]; 
        texturedSphereV = new float[d1][d2]; 
    
        for (int i = 0; i <= detail; i++) { 
          float theta = step * i; 
          float y = cos(theta); 
          float sin_theta = sin(theta); 
          float v = 1.0f - vstep * i; 
    
          for (int j = 0; j <= 2 * detail; j++) { 
            float phi = step * j; 
            float x = sin_theta * cos(phi); 
            float z = sin_theta * sin(phi); 
            float u = 1.0f - ustep * j; 
    
            texturedSphereX[i][j] = x * globeRadius; 
            texturedSphereY[i][j] = y * globeRadius; 
            texturedSphereZ[i][j] = z * globeRadius; 
            texturedSphereU[i][j] = u * txtMap.width; 
            texturedSphereV[i][j] = v * txtMap.height; 
          }   
        } 
      }
    
      void render() {  // draw the sphere
        noStroke();
        int nexti, t2= 2*texturedSphereDetail;
        //
        for (int i = 0; i < texturedSphereDetail; i=nexti) { 
          nexti = i + 1;   
          beginShape(QUAD_STRIP); 
          texture(txtMap); 
            for (int j=0 ; j<=t2 ; j++) {         
              float u  = texturedSphereU[i][j]; 
              float x1 = texturedSphereX[i][j]; 
              float y1 = texturedSphereY[i][j]; 
              float z1 = texturedSphereZ[i][j]; 
              float v1 = texturedSphereV[i][j]; 
              float x2 = texturedSphereX[nexti][j]; 
              float y2 = texturedSphereY[nexti][j]; 
              float z2 = texturedSphereZ[nexti][j]; 
              float v2 = texturedSphereV[nexti][j]; 
              vertex(x1, y1, z1, u, v1); 
              vertex(x2, y2, z2, u, v2); 
            }   
          endShape(); 
        }
      }
      
      void addRotation(int mX,int mY,int pmX,int pmY){
        rotSpeed.x += (pmY-mY)* ROTATION_FACTOR;
        rotSpeed.y -= (pmX-mX)* ROTATION_FACTOR;     
      }
     
      void update(){
        rotation.add  (rotSpeed);
        rotSpeed.mult (.95);  
      }
    }
    
    
    //Table///////////////////////////////almost classic class, extracted from Ben Fry's "Visualizing Data"
    //////////////////////////////////////////////////////////////////////////////////////////////////////
    
    class Table {
      String[][] data;
      int numRows, numCols;
    
      //CONSTRUCTOR
      Table(String nombre) {   
        String[] filas = loadStrings(nombre); 
        numRows = filas.length;
        data = new String[numRows][];
        for (int i = 0; i < filas.length; i++) {
          if (trim(filas[i]).length() == 0) {
            continue;
          }   
          if (filas[i].startsWith("#")) {      //startsWith() doesn't work on processingjs
            continue;
          }   
          data[i] = split(filas[i],",");       //dont use TAB on processingjs
        }       
        numCols=data[0].length;
      }
    
      //METHODS
    
      //Returns number of rows
      int getNumRows() { return numRows; }
    
      //Return number of cols
      int getNumCols() { return numCols; }
    
      //Returns name of a row, specified by index
      String getRowName(int rowIndex) { return getString(rowIndex,0); }
    
      //Returns value as String | be careful with method overloading using processingjs
      //String getString(String rowName, int col) { return getString(getRowIndex(rowName),col); }
      String getString(int rowIndex, int colIndex) { return data[rowIndex][colIndex]; }
    
      //Returns value as Int | be careful, bla, bla..
      //int getInt(String rowName, int col) { return parseInt(getString(rowName,col));}   
      int getInt(int rowIndex, int colIndex) { return parseInt(getString(rowIndex,colIndex)); }
    
      //Returns value as Float | be careful, bla, bla..
      //float getFloat(String rowName, int col) { return parseFloat(getString(rowName,col)); }
      float getFloat(int rowIndex, int colIndex) { return parseFloat(getString(rowIndex,colIndex)); }
    
      //Find file by its name and returns -1 in case of failure
      int getRowIndex(String name) {
        for (int i = 0; i < numRows; i++) {
          if (data[i][0].equals(name)) {
            return i;
          }
        }
        println("I didn't found any row called '"+ name+"!'");
        return -1;
      }
    
      //Returns the sum of all the values in a row, specified by index
      int rowSum (int index) {
        int sum=0;
        for (int i=1;i<numCols;i++) {
          sum+=getInt(index,i);
        }
        return sum;
      }
      
      //Returns the sum of all the values in a column, specified by index
      int colSum (int index) {
        int sum=0;
        for (int i=1;i<numRows;i++) {
          sum+=getInt(i,index);
        } 
        return sum;
      }
      
      //Returns the row with maximum value sum
      int maxRowSum() { 
        int maxSum=0;  
        for (int i=1; i<numRows; i++) {
          if (rowSum(i)>=maxSum) {
            maxSum=rowSum(i);
          }
        }
        return maxSum;
      }
      
      //Returns the total sum of all the values in the table
      int totalSum() {
        int sum=0;  
        for (int i=1; i<numRows; i++) {
          sum+=rowSum(i);
        } 
        return sum;
      }
    } 
    
    //WorldVis///////////////////////////////////////////////////////////////////////////////
    //A generic tool to display some cuantitative data onto Earth surface////////////////////
    //Inspired by Flink Labs visualization on climate change:////////////////////////////////
    //www.flinklabs.com/projects/climatedata/////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////
    //This example display most populated metropolis (on this world :-)//////////////////////
    //Data: cityPopulation + geoNames//////////////////////////////////////////////////////// 
    //http://es.wikipedia.org/wiki/Anexo:Aglomeraciones_urbanas_m%C3%A1s_pobladas_del_mundo//
    /////////////////////////////////////////////////////////////////////////////////////////
    //Custom image based on work from [http://commons.wikimedia.org/wiki/User:Thesevenseas]//
    //Font-family: Lato // Author: Lukasz Dziedzic///////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////Ale González · 60rpm.tv/i
    /////////////////////////////////////////////////////////////////////////////Pubic Domain
    /////////////////////////////////////////////////////////////////////////////////////////
    
    int 
    X,Y;
    final int
    TEXT_COL      = 0xaa000000,
    DATA_COL      = 0x99ff0000,
    HOVER_COL     = 0xffff0000,
    WORLD_TINT    = 0xffffffff,
    LINES_WEIGHT  = 2,
    BUFF_LINES_W  = 6;
    float 
    a,b;
    PGraphics 
    bg, 
    hover;
    PFont 
    h0,h1,h2,h3;
    //
    Globe w;
    Table t;
    DataHolder[] data;
    
    ////////////////////////////////////////////////////////////////////////////////////////
    
    void setup(){
       //Buffers
      size(900,700,P3D); 
      bg= createGraphics(width,height, P2D);    //Buffer for storing the background
      hover=  createGraphics(width,height,P3D); //Color picking buffer
       //Fonts
      h1= loadFont("Lato-Regular-24.vlw");
      h2= loadFont("Lato-Light-24.vlw");
       //General settings
      X=  width/2+100;
      Y= height/2;
      createBackground (bg,X,Y,.1);
      frameRate(30);
      cursor(CROSS);
      textMode(SCREEN);
       //Objects
      w= new Globe(250,24,"w.png");
      t= new Table("coords.csv");
      data= new DataHolder[t.getNumRows()-1];
      for(int i=0;i<data.length;i++) data[i]= new DataHolder(i);   
    }
      
      void createBackground (PGraphics pg, int X, int Y,float f){ 
          int x,y;
          pg.beginDraw();
          pg.smooth();
          for(int i=0;++i<pg.width*pg.height;) 
           {pg.set (x=i%pg.width, y=i/pg.width, (255-round(dist(x,y,X,Y)*f))*0x010101);}
          pg.endDraw();
          background(pg);
      }
    
    ////////////////////////////////////////////////////////////////////////////////////////
    
    void draw(){
      background(bg);
      hover.beginDraw(); hover.background(0); hover.endDraw();
      lights();
      w.update();
      render(X,Y); 
      detectHover();
    }
    
    void render(int x, int y){
      hover.beginDraw();
      pushMatrix();
        translate(x,y);
        hover.translate(x,y);
        pushMatrix();
          rotateX(w.rotation.x);
          rotateY(w.rotation.y);
          hover.rotateX(w.rotation.x);
          hover.rotateY(w.rotation.y);
          fill(WORLD_TINT);
          w.render();
          for (int i=0;i<data.length;i++){
              data[i].render(g,false);
              data[i].render(hover,true);
          }
        popMatrix();
      popMatrix();
      hover.endDraw();
    }
    
    ////////////////////////////////////////////////////////////////////////////////////////
    
    void mouseDragged(){
      if (mouseButton==LEFT)  w.addRotation(mouseX,mouseY,pmouseX,pmouseY);
    }
    
    void detectHover(){
      int c=hover.get(mouseX,mouseY);
      int index= c/0x010101 + 254; 
      for(int i=0;i<data.length;i++){
        if (i==index) {
          data[i].setHoveredTo(true);
          fill(TEXT_COL);
          textFont(h1);
          text(data[i].NAME+", "+data[i].COUNTRY,75,height-175);
          textFont(h2);
          text("Población: "+nfc(data[i].VALUE),75,height-150);
          noFill();
        }else{
          data[i].setHoveredTo(false);}
      }
    }
    

    code

    tweaks (0)

    about this sketch

    This sketch is running as Java applet, exported from Processing.

    license

    advertisement

    Ale

    worldVis

    Add to Faves Me Likey@! 34
    You must login/register to add this sketch to your favorites.

    A generic beta tool that plots over earth surface some cuantitative data, stored in a csv file.
    As example I´ve displayed top 100 most populated metropolis.
    Drag to rotate earth and hover elements to extract some info.

    Credits to a lot of people, mainly Flink Labs for inspiration and code: look inside.

    R.A. Robertson
    1 Feb 2012
    Great!

    The hover is a little too precise, and is hard at time for me to settle on with the mouse. Can't wait to see the final version.
    urzq
    2 Feb 2012
    Nice work :)

    I agree with roberston, it's hard to hover the desired red marker !
    Also, maybe you should make it english ?
    Ale
    3 Feb 2012
    Hey thanks!
    You´re right, I´ve updated the code slightly to improve the hover method. When I got more time I´ll change it properly.
    ^-^
    Pd.: there´s not much to translate there, I think... ;-)
    Eric Engelhard
    14 Feb 2012
    Great sketch and a perfect place to start for a project of mine. Would you please tell me what image you used for the map? Is it publicly available? Thanks!
    Ale
    14 Feb 2012
    Hi Eric!
    Thank you very much, although the hard job was done by Flink Labs. I only refined the graphic output to my own criteria and added a pair of features: the hovering method and the geoNames database parsing function, because it's the perfect place to take the info for a visualization of this kind. I've to say now it's pure alpha (I said beta?) :-)
    I did the base image, and it's licensed on a Creative Commons (look in the code) as the source was licensed in the same way (taken from Wikipedia). The code is on Pubic Domain (Public Domain + code promiscuity)... so use it all as you want... I'd only like to be credited and to know the project once done... (pure curiosity)
    Regards!

    Eric Engelhard
    14 Feb 2012
    Thanks, Ale. I run a publicly funded informatics group for mouse genetics and we are in the process of updating one of our web sites (from http://www.mmrrc.org to ww2.mmrrc.org). I already have a draft sketch of world customers (http://crunchy.komp.org/mbp_world_map_large/), but I like your visualization and the color scheme fits well with the new web site. I'll let you know if/when we use your code and I'll be sure to credit you and apply the appropriate licences. Cheers!
    Ale
    14 Feb 2012
    Mmm... I see this kind of visualization is quite suitable for your needs, cause it may dispose the data without overlapping... :-)
    Good luck with the visualization and regards! ;-)
    Eric Engelhard
    14 Feb 2012
    I have the sketch up and displaying a globe with an alternate image map, but the location vectors are not displaying. Would you please send me the csv file so that I can be sure I have the structure/parsing/value scale correct? &lt;ekengelhard@ucdavis.edu&gt;
    Ale
    14 Feb 2012
    Hi again, Eric...
    You shouldn't post your complete mail... spam is out there... ;-)
    Use download button above to get the complete applet with all its external files (texture and csv)...
    Eduardo Obieta
    22 Mar 2012
    wow¡¡ looks great
    bob
    2 Jan 2013
    Great work man! Hey, i wish to talk to you regarding similar problem. if its okay, is there any other id i can contact you on? You can email me at - shawn144441 (at) gmail(dot)com

    Thanks :)
    Ale
    3 Jan 2013
    Hi Bob, if you have coding doubts you can use processing forum (forum.processing.org), because this way your doubts are more useful to more people. I usually hang around there so I'll see it, but, much better, there are a lot of talented people there willing to have good questions to answer. Anyway, my mail is a(at)60rpm.tv.
    Glad you like it and happy new year. ;-)
    James
    3 Nov 2013
    Hi!

    This is great work. I have had a look at the data you have used. I want to add more to it but when I run it, it doesn't want to work, I am laying it out in the same way you have done and used Geo names for the data. How do you input more information (locations, coordinates etc.) into the data so it works.

    Thanks!
    Ale
    3 Nov 2013
    What kind of error/exception does it yield?
    Nina
    4 Nov 2013
    Hi Ale,

    I tried adding data, too - without success. Do you have any idea how to manage that?
    Thank you for sharing this nice work!
    Ale
    4 Nov 2013
    Hi Nina. As I told James, it'd be easier with more information. What error/exception do you find?
    Pd.: I love sharing so... pleasure is mine! ;)
    Maria
    3 Mar 2014
    Hi Ale,

    Thank you for sharing this very cool sketch! I'd like to try to adapt it for a class project (I'll share the final code here when I'm done), but I' running into some difficulties trying to adapt the code.

    The problem I'm having seems to be one that others have run into - when I try to edit the data in the csv file and the run the sketch, I hit play, get a white screen, and then Processing freezes. I'm able to make very small edits to individual words (e.g. switch the word Madrid for another word), but if I change more than one or two names I run into this problem of the program freezing. I haven't made any changes to the code. Do you have any ideas on what might be causing this? Any thoughts you have would be very much appreciated.

    Thank you in advance,

    Maria
    Ale
    3 Mar 2014
    Hi, Maria! I'm glad you like it and you find it useful. Why don't you mail me sending me the csv file that causes the sketch to break? --&gt; a(at)60rpm.tv
    Maria
    14 Mar 2014
    Hi Ale,

    I apologize for the delay in responding, I missed your message when you posted it. I was able to solve the data problem, and I wanted to loop back to share the final product. I couldn't upload the code as a tweak because I ended up using an external package, which it seems causes problems in the upload. But I do have a video of the final sketch, which you can view here: https://vimeo.com/89132889

    I'd be happy to share the edited code as well. Thank you so much again for sharing this great code with the community!

    Best,
    Maria
    Ale
    15 Mar 2014
    Hi Maria!
    Great work, it looks very nice! Didn't you have any problem with the picking method? It seems that you have more than 255 elements in some of the tabs If you have any problem let me know, I updated my color picking method to a better one, with no limits in the number of elements.
    Anyway, I'm happy of having contributed to your project. Regards!
    : )
    pd.: It'd be nice to have this project credited there, with its url, so more people could access to the source code and use it for their own projects. Thanks!
    Maria
    16 Mar 2014
    Hi Ale,

    That's a great point - I've added the url to the Vimeo video. The only problem I had with the code was an error that comes up when I scroll over bars with missing data (which happens in some cases where I don't have an image for the bar), but I'm still able to run the sketch. Overall it's a great code. Thanks again for sharing!

    Maria
    You need to login/register to comment.