import com.google.maps.Map; import com.google.maps.MapEvent; import com.google.maps.MapType; import com.google.maps.LatLng; import com.google.maps.MapMouseEvent; import com.google.maps.InfoWindowOptions; import com.google.maps.controls.ZoomControl; import com.google.maps.controls.ZoomControlOptions; import com.google.maps.controls.ControlPosition; import com.google.maps.overlays.Marker; import com.google.maps.overlays.MarkerOptions; import com.google.maps.styles.ButtonStyle; import com.google.maps.styles.FillStyle; import com.google.maps.styles.StrokeStyle; import flash.display.MovieClip; const API_KEY:String = "YOUR-GOOGLE-MAPS-API-KEY-HERE"; // Get one from http://code.google.com/apis/maps/signup.html const PROXY:String = "http://whereisinfluxis.paulofierro.com/proxy.php?url="; const JERRY:String = "http://twitter.com/statuses/user_timeline/11419442.rss"; const GRANT:String = "http://twitter.com/statuses/user_timeline/72902114.rss"; var tweetsJerry:Array = []; var tweetsGrant:Array = []; var currentState:String = JERRY; var map:Map; // txtStatus is a TextField on the stage txtStatus.text = ""; jerry.visible = false; grant.visible = false; loadTweets(PROXY + JERRY); /*** Methods ***/ // Load the tweets function loadTweets(url:String):void { if(currentState == JERRY) { jerry.visible = true; grant.visible = false; txtStatus.text = "Loading @jerrychabolla's tweets..."; } else { jerry.visible = false; grant.visible = true; txtStatus.text = "Loading @influxisGrant's tweets..."; } var loader = new URLLoader(); loader.addEventListener(Event.COMPLETE, parseTweets); loader.load(new URLRequest(url)); } // Parse the tweets function parseTweets(event:Event):void { event.currentTarget.removeEventListener(Event.COMPLETE, parseTweets); if(currentState == JERRY) { txtStatus.text = "Parsing @jerrychabolla's tweets..."; } else { txtStatus.text = "Parsing @influxisGrant's tweets..."; } // Set up the namespace for location namespace georss = "http://www.georss.org/georss"; use namespace georss; var xml:XML = new XML(event.target.data); var items:XMLList = xml.channel.item; for(var i:int = 0; i < items.length(); i++) { var item:XML = items[i]; var o:Object = {}; o.title = item.title; o.date = parseRFC822(item.pubDate); o.url = item.link; trace(o.title + " - " + o.date); var location:String = item.georss::point; // We only want tweets with a location if(location) { // Location is "48.71536205 2.28986693" so we split it on the space var loc:Array = location.split(" "); o.latLng = new LatLng(loc[0], loc[1]); if(currentState == JERRY) { tweetsJerry.push(o); } else { tweetsGrant.push(o); } } } if(currentState == JERRY) { currentState = GRANT; loadTweets(PROXY + GRANT); } else { createMap(); } } // Create map function createMap():void { map = new Map(); map.url = "http://paulofierro.com/"; map.x = 0; map.y = 0; map.key = API_KEY; map.setSize(new Point(stage.stageWidth, stage.stageHeight)); map.addEventListener(MapEvent.MAP_READY, onMapReady); addChild(map); } function onMapReady(event:MapEvent):void { if(getLocationOfLatestTweet()) { map.setCenter(getLocationOfLatestTweet(), 9, MapType.NORMAL_MAP_TYPE); } var zoomOptions:ZoomControlOptions = new ZoomControlOptions({ buttonSize: new Point(17, 17), buttonStyle: new ButtonStyle({allStates: {bevelThickness: 0, bevelAlpha: 0.1}}), buttonSpacing: new Point(4, 4), hasScrollTrack: false, position: new ControlPosition(ControlPosition.ANCHOR_TOP_LEFT, 15, 15)}); map.addControl(new ZoomControl(zoomOptions)); var i:int; for(i = 0; i < tweetsJerry.length; i++) { placePin(tweetsJerry[i]); } for(i = 0; i < tweetsGrant.length; i++) { placePin(tweetsGrant[i]); } } function getLocationOfLatestTweet():LatLng { if(tweetsJerry.length == 0 || tweetsGrant.length == 0) { return null; } if(tweetsJerry[0].date > tweetsGrant[0].date) { return tweetsJerry[0].latLng; } return tweetsGrant[0].latLng; } function placePin(item:Object):void { var markerOptions:MarkerOptions = new MarkerOptions({hasShadow: true, clickable:true}); var m:Marker = new Marker(item.latLng); m.addEventListener(MapMouseEvent.CLICK, onMarkerClick); map.addOverlay(m); if(item.latLng == getLocationOfLatestTweet()) { var options:InfoWindowOptions = getInfoWindowOptions(); options.title = item.title; options.content = item.date; m.openInfoWindow(options); } } function getInfoWindowOptions():InfoWindowOptions { var titleFormat:TextFormat = new TextFormat("Helvetica", 14, 0xffffff, true); var contentFormat:TextFormat = new TextFormat("Helvetica", 11, 0xffffff, true); var options:InfoWindowOptions = new InfoWindowOptions({ strokeStyle: {color: 0x000000, alpha: 0}, fillStyle: {color: 0x2b2b2b, alpha: 0.8}, titleFormat: titleFormat, contentFormat: contentFormat, cornerRadius: 6, padding: 0, hasCloseButton: false, hasTail: true, tailWidth: 20, tailHeight: 30, tailOffset: -12, tailAlign: InfoWindowOptions.ALIGN_LEFT, hasShadow: false, width: 600 }); return options; } function onMarkerClick(event:MapMouseEvent):void { var m:Marker = event.feature as Marker; var o:Object = getObjectByLatLng(m.getLatLng()); var options:InfoWindowOptions = getInfoWindowOptions(); options.title = o.title; options.content = o.date; m.openInfoWindow(options); } function getObjectByLatLng(latLng:LatLng):Object { var i:int; var o:Object; for(i = 0; i < tweetsJerry.length; i++) { o = tweetsJerry[i]; if(o.latLng.equals(latLng)) { return o; } } for(i = 0; i < tweetsGrant.length; i++) { o = tweetsGrant[i]; if(o.latLng.equals(latLng)) { return o; } } return null; } /*** Borrowed from import com.adobe.utils.DateUtil without the annoying Flex dependencies ***/ function parseRFC822(str:String):Date { var finalDate:Date; try { var dateParts:Array = str.split(" "); var day:String = null; if (dateParts[0].search(/\d/) == -1) { day = dateParts.shift().replace(/\W/, ""); } var date:Number = Number(dateParts.shift()); var month:Number = Number(getShortMonthIndex(dateParts.shift())); var year:Number = Number(dateParts.shift()); var timeParts:Array = dateParts.shift().split(":"); var hour:Number = int(timeParts.shift()); var minute:Number = int(timeParts.shift()); var second:Number = (timeParts.length > 0) ? int(timeParts.shift()): 0; var milliseconds:Number = Date.UTC(year, month, date, hour, minute, second, 0); var timezone:String = dateParts.shift(); var offset:Number = 0; if (timezone.search(/\d/) == -1) { switch(timezone) { case "UT": offset = 0; break; case "UTC": offset = 0; break; case "GMT": offset = 0; break; case "EST": offset = (-5 * 3600000); break; case "EDT": offset = (-4 * 3600000); break; case "CST": offset = (-6 * 3600000); break; case "CDT": offset = (-5 * 3600000); break; case "MST": offset = (-7 * 3600000); break; case "MDT": offset = (-6 * 3600000); break; case "PST": offset = (-8 * 3600000); break; case "PDT": offset = (-7 * 3600000); break; case "Z": offset = 0; break; case "A": offset = (-1 * 3600000); break; case "M": offset = (-12 * 3600000); break; case "N": offset = (1 * 3600000); break; case "Y": offset = (12 * 3600000); break; default: offset = 0; } } else { var multiplier:Number = 1; var oHours:Number = 0; var oMinutes:Number = 0; if (timezone.length != 4) { if (timezone.charAt(0) == "-") { multiplier = -1; } timezone = timezone.substr(1, 4); } oHours = Number(timezone.substr(0, 2)); oMinutes = Number(timezone.substr(2, 2)); offset = (((oHours * 3600000) + (oMinutes * 60000)) * multiplier); } finalDate = new Date(milliseconds - offset); if (finalDate.toString() == "Invalid Date") { throw new Error("This date does not conform to RFC822."); } } catch (e:Error) { var eStr:String = "Unable to parse the string [" +str+ "] into a date. "; eStr += "The internal error was: " + e.toString(); throw new Error(eStr); } return finalDate; } function getShortMonthIndex(m:String):int { switch(m) { case "Jan": return 0; case "Feb": return 1; case "Mar": return 2; case "Apr": return 3; case "May": return 4; case "Jun": return 5; case "Jul": return 6; case "Aug": return 7; case "Sep": return 8; case "Oct": return 9; case "Nov": return 10; case "Dec": return 11; } return 0; }