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 JERRY:String = "http://search.twitter.com/search.atom?q=+from%3Ajerrychabolla"; const GRANT:String = "http://search.twitter.com/search.atom?q=+from%3Ainfluxisgrant"; 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(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 namespaces namespace atom = "http://www.w3.org/2005/Atom"; namespace twitter = "http://api.twitter.com/"; namespace georss = "http://www.georss.org/georss"; use namespace atom; var xml:XML = new XML(event.target.data); var items:XMLList = xml.entry; for(var i:int = 0; i < items.length(); i++) { var item:XML = items[i]; var o:Object = {}; o.title = item.title; o.date = parseW3CDTF(item.published); o.url = item.link.@href; use namespace twitter; use namespace georss; var location:String = item.twitter::geo.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(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 parseW3CDTF(str:String):Date { var finalDate:Date; try { var dateStr:String = str.substring(0, str.indexOf("T")); var timeStr:String = str.substring(str.indexOf("T")+1, str.length); var dateArr:Array = dateStr.split("-"); var year:Number = Number(dateArr.shift()); var month:Number = Number(dateArr.shift()); var date:Number = Number(dateArr.shift()); var multiplier:Number; var offsetHours:Number; var offsetMinutes:Number; var offsetStr:String; if (timeStr.indexOf("Z") != -1) { multiplier = 1; offsetHours = 0; offsetMinutes = 0; timeStr = timeStr.replace("Z", ""); } else if (timeStr.indexOf("+") != -1) { multiplier = 1; offsetStr = timeStr.substring(timeStr.indexOf("+")+1, timeStr.length); offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":"))); offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length)); timeStr = timeStr.substring(0, timeStr.indexOf("+")); } else // offset is - { multiplier = -1; offsetStr = timeStr.substring(timeStr.indexOf("-")+1, timeStr.length); offsetHours = Number(offsetStr.substring(0, offsetStr.indexOf(":"))); offsetMinutes = Number(offsetStr.substring(offsetStr.indexOf(":")+1, offsetStr.length)); timeStr = timeStr.substring(0, timeStr.indexOf("-")); } var timeArr:Array = timeStr.split(":"); var hour:Number = Number(timeArr.shift()); var minutes:Number = Number(timeArr.shift()); var secondsArr:Array = (timeArr.length > 0) ? String(timeArr.shift()).split(".") : null; var seconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0; var milliseconds:Number = (secondsArr != null && secondsArr.length > 0) ? Number(secondsArr.shift()) : 0; var utc:Number = Date.UTC(year, month-1, date, hour, minutes, seconds, milliseconds); var offset:Number = (((offsetHours * 3600000) + (offsetMinutes * 60000)) * multiplier); finalDate = new Date(utc - offset); if (finalDate.toString() == "Invalid Date") { throw new Error("This date does not conform to W3CDTF."); } } 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; }