Skip to content

Instantly share code, notes, and snippets.

@por
Created May 20, 2011 08:58

Revisions

  1. por revised this gist May 20, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion scrobblemap3D.js
    Original file line number Diff line number Diff line change
    @@ -201,7 +201,7 @@ var ScrobbleMap3D = new Class({
    },
    getMoreScrobbles: function() {

    new Request.JSONP({
    new Request.JSON({
    url: this.settings.scrobbleStream,
    onComplete: this.getMoreScrobblesCallback.bind(this)
    }).send();
  2. por revised this gist May 20, 2011. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion scrobblemap3D.js
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ var ScrobbleMap3D = new Class({
    scrobbleInterval: null,
    scrobbleQueue: [],
    settings: {
    scrobbleStream: 'https://staff.last.fm/scrobblemap/locations',
    scrobbleStream: 'sample_data.json',
    placemarkIcon: 'http://static.last.fm/prototyping/scrobblemap/scrobblepin.png',
    logo: {
    href: 'http://static.last.fm/prototyping/scrobblemap/lastfm-logo-white.png',
  3. por revised this gist May 20, 2011. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions sample_data.json
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    [{"user":"mrspeppa","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/63086423.jpg","track":{"artist":"Michael Jackson","name":"They Don't Care About Us"},"location":{"city":"Police","countrycode":"PL","latitude":53.5499992371,"longitude":14.5666999817},"timestamp":"1305882819"},{"user":"kitsune_arisa","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/61490441.jpg","track":{"artist":"In Flames","name":"Leeches"},"location":{"city":"Tomsk","countrycode":"RU","latitude":56.5,"longitude":84.9666976929},"timestamp":"1305883132"},{"user":"xtran","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/4471011.jpg","track":{"artist":"Action Biker","name":"Dance To Keep From Crying"},"location":{"city":"Kista","countrycode":"SE","latitude":59.4500007629,"longitude":17.9167003632},"timestamp":"1305883058"},{"user":"henrynavarre","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/62700273.jpg","track":{"artist":"The Pillows","name":"Hybrid Rainbow"},"location":{"city":"Chaska","countrycode":"US","latitude":44.8054008484,"longitude":-93.6248016357},"timestamp":"1305883074"},{"user":"mykkyr","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/52491325.jpg","track":{"artist":"Grant Hart","name":"You're the Reflection of the Moon on the Water"},"location":{"city":"","countrycode":"FI","latitude":64,"longitude":26},"timestamp":"1305882869"},{"user":"Amazon1969","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/6654115.jpg","track":{"artist":"The Bar-Kays","name":"Humpin'"},"location":{"city":"Utrecht","countrycode":"NL","latitude":52.0833015442,"longitude":5.13329982758},"timestamp":"1305882898"},{"user":"Gatsby0202","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/44332461.jpg","track":{"artist":"Fink","name":"Move On Me"},"location":{"city":"Oxford","countrycode":"GB","latitude":51.75,"longitude":-1.25},"timestamp":"1305882989"},{"user":"xJonnyx","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/45523249.jpg","track":{"artist":"Bon Iver","name":"Hinnom, TX"},"location":{"city":"Manchester","countrycode":"GB","latitude":53.5,"longitude":-2.21670007706},"timestamp":"1305883125"},{"user":"Candlelight89","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/62865905.gif","track":{"artist":"Malcolm Lincoln","name":"Uu Monica"},"location":{"city":"Pobiedziska","countrycode":"PL","latitude":52.4667015076,"longitude":17.2999992371},"timestamp":"1305882869"},{"user":"bluekey","image":"http:\/\/userserve-ak.last.fm\/serve\/126s\/723289.jpg","track":{"artist":"Cold War Kids","name":"Out Of The Wilderness"},"location":{"city":"Vancouver","countrycode":"CA","latitude":49.25,"longitude":-123.133300781},"timestamp":"1305882837"}]
  4. por created this gist May 20, 2011.
    79 changes: 79 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,79 @@
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <meta http-equiv="refresh" content="3600" />
    <title>Scrobble Map</title>
    <link rel="stylesheet" href="scrobblemap.css" type="text/css" media="screen" title="no title" charset="utf-8">
    </head>

    <body>

    <div id="map3d"></div>

    <script src="http://cdn.last.fm/prototyping/scrobblemap/mootools-1.2.4-core.js"></script>
    <script src="http://cdn.last.fm/prototyping/scrobblemap/mootools-1.2.4.4-more.js"></script>

    <script src="http://www.google.com/jsapi?key=ABQIAAAAef4neLxKbGqqMWYSo_SV8BTm1w6-I7rkOK_FGHRvVLZcy0O7EhQeEyUSqJjgTzCZH93mE14rYLfiuQ"></script>
    <script src="scrobblemap3D.js"></script>

    <script>
    // Simple transition
    /*
    var map = new ScrobbleMap3D("map3d");
    map.addEvent('onViewChangeBegin', map.hideBalloon.bind(map));
    map.addEvent('onViewChangeEnd', map.showBalloon.bind(map));
    map.addEvent('onShowScrobble', function(scrobble) {
    this.setPoint(scrobble);
    }.bind(map));
    map.addEvent('onSetPoint', function() {
    this.getView(this.options.defaultView);
    }.bind(map));
    map.start();
    */

    // Complex transition
    var map = new ScrobbleMap3D("map3d", {
    showStatusBar: false,
    scrobbleInterval: 10000,
    waitUntilLoaded: true
    });

    var views = [
    { altitude: 0, heading: -65, tilt: 65, range: 1500 },
    { altitude: 0, heading: 115, tilt: 70, range: 2800 },
    { altitude: 0, heading: 45, tilt: 60, range: 2000 },
    { altitude: 0, heading: -95, tilt: 65, range: 2500 }
    ];

    map.addEvent('onShowScrobble', function(scrobble) {
    this.setPoint(scrobble);
    }.bind(map));
    map.addEvent('onSetPoint', function() {
    this.addEvent('onViewChangeBegin', function() {
    this.removeEvents('onViewChangeBegin');
    this.hideBalloon();
    });
    this.addEvent('onViewChangeEnd', function() {
    this.removeEvents('onViewChangeEnd');
    this.addEvent('onViewChangeEnd', function() {
    this.removeEvents('onViewChangeEnd');
    this.showBalloon();
    });
    var rand = $random(0, views.length-1);
    var view = views[rand];
    this.getView(view);
    });
    this.getView({
    altitude: 0,
    heading: 0,
    tilt: 0,
    range: 5000000
    });
    }.bind(map));
    map.start();

    </script>
    </body>
    59 changes: 59 additions & 0 deletions scrobblemap.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,59 @@
    html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    width: 100%;
    overflow: hidden;
    background-color: transparent;
    }
    body {
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 18px;
    line-height: 24px;
    }
    p, h2 {
    margin: 0;
    padding: 0;
    }
    #map3d {
    background-color: transparent;
    height: 100%;
    width: 100%;
    }
    #mapOverlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    }
    .scrobbleballoon {
    padding: 10px 10px 10px 166px;
    color: #111;
    text-shadow: 0 1px 0 #fff;
    }
    .scrobbleballoon .user-image {
    margin: 0 0 10px -156px;
    display: inline;
    float: left;
    border: 5px solid white;
    -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.2);
    }
    .scrobbleballoon .title {
    font-size: 30px;
    margin-bottom: 0.5em;
    display: block;
    }
    .trackname,
    .artistname {
    font-weight: bold;
    }
    .location {
    color: #666;
    }
    #scrobbleCounter {
    color: #eee;
    float: right;
    position: absolute;
    font-size: 40px;
    color: #eee;
    }
    296 changes: 296 additions & 0 deletions scrobblemap3D.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,296 @@
    /**
    * Scrobble Map 3D
    * @author Tim ([email protected])
    * @version 0.1
    *
    * Requires:
    * <script type="text/javascript" src="http://cdn.last.fm/prototyping/scrobblemap/mootools-1.2.4-core.js"></script>
    * <script type="text/javascript" src="http://cdn.last.fm/prototyping/scrobblemap/mootools-1.2.4.4-more.js"></script>
    * <script src="http://www.google.com/jsapi?key=ABQIAAAAef4neLxKbGqqMWYSo_SV8BTm1w6-I7rkOK_FGHRvVLZcy0O7EhQeEyUSqJjgTzCZH93mE14rYLfiuQ"></script>
    */

    var ScrobbleMap3D = new Class({
    Implements: [Options, Events],
    ge: null,
    placemark: null,
    la: null,
    balloon: null,
    scrobbleInterval: null,
    scrobbleQueue: [],
    settings: {
    scrobbleStream: 'https://staff.last.fm/scrobblemap/locations',
    placemarkIcon: 'http://static.last.fm/prototyping/scrobblemap/scrobblepin.png',
    logo: {
    href: 'http://static.last.fm/prototyping/scrobblemap/lastfm-logo-white.png',
    width: 100,
    height: 30
    },
    balloon: {
    maxWidth: 500,
    minWidth: 500,
    backgroundColor: '#eeeeee',
    closeButtonEnabled: false
    }
    },
    options: {
    language: "en-GB",
    layers: [
    // "LAYER_BORDERS",
    "LAYER_TERRAIN",
    "LAYER_BUILDINGS"
    ],
    showNavigation: false,
    showStatusBar: false,
    showOverviewMap: false,
    showScaleLegend: false,
    showAtmosphere: true,
    showSun: false,
    flyToSpeed: 1,
    scrobbleInterval: 10000, // ms
    defaultView: {
    altitude: 0,
    heading: 0,
    tilt: 45,
    range: 1000000
    },
    waitUntilLoaded: false
    },
    initialize: function(container, options) {

    // Set container
    this.container = container;

    // Set options
    this.setOptions(options);

    // Initialize Google Earth API
    google.load("earth", "1");
    google.setOnLoadCallback(this.init.bind(this));

    },
    init: function() {
    // Create Google Earth instance
    google.earth.createInstance(this.container, this.initCB.bind(this), this.failCB.bind(this));
    },
    initCB: function(instance) {

    this.ge = instance;
    this.ge.getWindow().setVisibility(true);

    // Initialize layers
    this.options.layers.each(function(item, index){
    var layer = eval("this.ge." + item);
    this.ge.getLayerRoot().enableLayerById(layer, true);
    }, this);

    // Options
    this.ge.getNavigationControl().setVisibility(this.options.showNavigation); // Navigation
    this.ge.getOptions().setStatusBarVisibility(this.options.showStatusBar); // Status bar
    this.ge.getOptions().setOverviewMapVisibility(this.options.showOverviewMap); // Overview map
    this.ge.getOptions().setScaleLegendVisibility(this.options.showScaleLegend); // Scale legend
    this.ge.getOptions().setAtmosphereVisibility(this.options.showAtmosphere); // Atmosphere
    this.ge.getSun().setVisibility(this.options.showSun); // Sun

    // Fly to speed
    this.ge.getOptions().setFlyToSpeed(this.options.flyToSpeed);

    // Create logo overlay
    this.createLogoOverlay();

    // Initialize placemark
    this.createPlacemark();

    // Initialize camera
    this.la = this.ge.createLookAt('');

    // Create balloon
    this.createBalloon();

    // Load scrobblestream
    this.getMoreScrobbles();

    // Add listeners
    google.earth.addEventListener(this.ge.getView(), 'viewchangebegin', this.viewChangeBeginHandler.bind(this));
    google.earth.addEventListener(this.ge.getView(), 'viewchangeend', this.viewChangeEndHandler.bind(this));

    },
    failCB: function() {
    console.log('FAIL');
    },
    createLogoOverlay: function() {

    // Create the ScreenOverlay
    var screenOverlay = this.ge.createScreenOverlay('');

    // Specify a path to the image and set as the icon
    var icon = this.ge.createIcon('');
    icon.setHref(this.settings.logo.href);
    screenOverlay.setIcon(icon);

    // Set the ScreenOverlay’s position in the window
    screenOverlay.getOverlayXY().setXUnits(this.ge.UNITS_PIXELS);
    screenOverlay.getOverlayXY().setYUnits(this.ge.UNITS_PIXELS);
    screenOverlay.getOverlayXY().setX(this.settings.logo.width/2+10);
    screenOverlay.getOverlayXY().setY(this.settings.logo.height/2+10);

    // Set the overlay’s size in pixels
    screenOverlay.getSize().setXUnits(this.ge.UNITS_PIXELS);
    screenOverlay.getSize().setYUnits(this.ge.UNITS_PIXELS);
    screenOverlay.getSize().setX(this.settings.logo.width);
    screenOverlay.getSize().setY(this.settings.logo.height);

    // Add the ScreenOverlay to Earth
    this.ge.getFeatures().appendChild(screenOverlay);

    },
    createPlacemark: function() {

    // Initialize placemark
    this.placemark = this.ge.createPlacemark('');

    // Create a style map.
    var styleMap = this.ge.createStyleMap('');

    // Create normal style for style map.
    var normalStyle = this.ge.createStyle('');
    var normalIcon = this.ge.createIcon('');
    normalIcon.setHref(this.settings.placemarkIcon);
    normalStyle.getIconStyle().setIcon(normalIcon);
    normalStyle.getIconStyle().setScale(2.0);

    styleMap.setNormalStyle(normalStyle);

    // Apply stylemap to placemark.
    this.placemark.setStyleSelector(styleMap);

    // Add the placemark to Earth
    this.ge.getFeatures().appendChild(this.placemark);
    },
    createBalloon: function() {

    this.balloon = this.ge.createHtmlStringBalloon('');
    this.balloon.setMinWidth(this.settings.balloon.minWidth);
    this.balloon.setMaxWidth(this.settings.balloon.maxWidth);
    this.balloon.setCloseButtonEnabled(this.settings.balloon.closeButtonEnabled);
    this.balloon.setBackgroundColor(this.settings.balloon.backgroundColor);
    },
    showScrobble: function() {

    // retrieve more scrobbles if the queue is empty or down to the last few
    if (this.scrobbleQueue.length == 0 || this.scrobbleQueue.length == 2) {
    this.getMoreScrobbles();
    }

    if (this.scrobbleQueue.length == 0) {
    return;
    }

    // Be nice and only start when everything has finished loading
    if (this.options.waitUntilLoaded) {
    if(this.isLoading()) {
    console.log('loading...');
    this.stop();
    this.start.delay(1000, this);
    return;
    }
    }

    scrobble = this.scrobbleQueue.shift();
    this.fireEvent('onShowScrobble', [scrobble]);

    },
    getMoreScrobbles: function() {

    new Request.JSONP({
    url: this.settings.scrobbleStream,
    onComplete: this.getMoreScrobblesCallback.bind(this)
    }).send();

    },
    getMoreScrobblesCallback: function(obj) {

    // wipe out any cached scrobbles
    this.scrobbleQueue = [];

    // just take the first 10 or we’ll be too out of date
    for (var i = 0, ilen = 10; i < ilen; i++) {
    this.scrobbleQueue.push(obj[i]);
    }

    },
    setPoint: function(scrobble) {

    var user = scrobble.user;
    var image = scrobble.image;
    var trackname = scrobble.track.name;
    var artistname = scrobble.track.artist;
    var cityname = scrobble.location.city;
    var countrycode = scrobble.location.countrycode;

    var html = '<div class="scrobbleballoon"><img src="'+ image +'" height="126" width="126" alt="' + user + '" class="user-image" /><h2 class="title">'+ user +'</h2><p class="listeningto"><span class="artistname">' + artistname + '</span> &ndash; <span class="trackname">' + trackname + '</span></p><p class="location">Scrobbling now in ' + cityname + ' (' + countrycode + ')' + '</p></div>';

    // this.placemark.setName(user);
    // this.placemark.setDescription(html);

    this.balloon.setContentString(html);
    this.balloon.setFeature(this.placemark);


    var latitude = scrobble.location.latitude;
    var longitude = scrobble.location.longitude

    // Set the placemark’s location
    var point = this.ge.createPoint('');
    point.setLatitude(latitude);
    point.setLongitude(longitude);
    this.placemark.setGeometry(point);

    this.fireEvent('onSetPoint');

    },
    getView: function(view) {
    var point = this.placemark.getGeometry();
    var latitude = point.getLatitude();
    var longitude = point.getLongitude();

    this.la.set(
    latitude,
    longitude,
    view.altitude,
    this.ge.ALTITUDE_RELATIVE_TO_GROUND,
    view.heading,
    view.tilt,
    view.range
    );
    this.ge.getView().setAbstractView(this.la);
    },
    start: function() {
    this.showScrobble();
    $clear(this.scrobbleInterval); // just in case
    this.scrobbleInterval = this.showScrobble.periodical(this.options.scrobbleInterval, this);
    },
    stop: function() {
    $clear(this.scrobbleInterval);
    },
    hideBalloon: function() {
    thisge.setBalloon(null);
    },
    showBalloon: function() {
    if(this.balloon.getContentString() != ''){
    this.ge.setBalloon(this.balloon);
    }
    },
    viewChangeBeginHandler: function() {
    this.fireEvent('onViewChangeBegin');
    },
    viewChangeEndHandler: function() {
    this.fireEvent('onViewChangeEnd');
    },
    getStreamingPercent: function() {
    return this.ge.getStreamingPercent();
    },
    isLoading: function() {
    var pct = map.getStreamingPercent();
    return (pct < 100);
    }
    });