package org.thinkname.ap.main; import jade.util.Logger; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.thinkname.ap.R; import org.thinkname.ap.contact.Contact; import org.thinkname.ap.contact.ContactListChanges; import org.thinkname.ap.contact.ContactLocation; import org.thinkname.ap.contact.ContactManager; import org.thinkname.ap.msn.MsnSessionManager; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Paint.FontMetrics; import android.location.Location; import com.google.android.maps.GeoPoint; import com.google.android.maps.MapController; import com.google.android.maps.MapView; import com.google.android.maps.Overlay; import com.google.android.maps.Projection; /** * Defines the overlay used to draw information over the map view. * It allows drawing contacts position and provides functionalities for * automatic zooming and centering of the map. */ public class ContactsPositionOverlay extends Overlay { /** * Instance of the Jade logger for debugging. */ private final Logger myLogger = Logger.getMyLogger(this.getClass().getName()); /** * Upper threshold, used for automatic zooming adjustment. It is a squared distance defined as a percentage of screen width */ private int UPPER_THRESHOLD = 0; /** * Lower threshold, used for automatic zooming adjustment. It is a squared distance defined as a percentage of screen width */ private int LOWER_THRESHOLD = 0; /** * Controller used to perform map zooming and centering */ private MapController mapController; /** * Paint object, used for drawing. */ private Paint myPaint; /** * Bitmap object that shows the yellow paddle. */ private Bitmap ylwPaddle; /** The blue paddle. */ private Bitmap bluePaddle; /** * Bitmap object that shows the blue baloon. */ private Bitmap blueBaloon; /** * Bitmap object that shows the highlight when selecting a contact in map mode */ private Bitmap highlight; /** * Resource object, stored for making quicker access to resource files */ private Resources appRes; /** * Map containing data describing the online contacts to be drawn and their status (checked/unchecked) */ private Map contactPositionMap; /** * Percentage defining the scroll area width with respect to screen width */ private static final float SCROLL_AREA_WIDTH_RATIO = 0.70f; /** * Percentage defining the scroll area height with respect to screen height */ private static final float SCROLL_AREA_HEIGHT_RATIO= 0.70f; /** * Percentage with respect to screen width for defining the upper threshold. */ private static final float UPPER_THRESHOLD_RATIO = 0.46f; /** * Percentage with respect to screen width for defining the lower threshold */ private static final float LOWER_THRESHOLD_RATIO = 0.35f; /** * Final width of the scrolling area in Pixel */ private int SCROLL_AREA_WIDTH=-1; /** * Final height of the scrolling area in Pixel */ private int SCROLL_AREA_HEIGHT=-1; /** * Map view object on which we draw the overlay */ private MapView myMapView; /** * Width of the map view on which we draw */ private int WIDTH=-1; /** * Height of the map view on which we draw */ private int HEIGHT=-1; /** * Constant used to for choosing zoom level. It means that zoom to max level is required */ private static final int ZOOM_MAX=0; /** * Constant used to for choosing zoom level. It means that zoom should be dynamically recomputed */ private static final int RECOMPUTE_ZOOM=1; /** * Constant used to for choosing zoom level. It means zoom shall not be recomputed */ private static final int NO_ZOOM=2; /** * The scrolling area (when exiting this area, scrolling is recomputed) */ private Rect scrollingArea; private Context ctn; private boolean focusState=false; /** * Instantiates a new contacts position overlay. * * @param cont current application context * @param myMapView the map view on which the overlay is drawn * @param ctn the file for accessing resources */ public ContactsPositionOverlay(Context cont, MapView myMapView, Resources ctn){ this.ctn = cont; mapController = myMapView.getController(); //根据联网状态显示放大缩小 //if(!MainActivity.CONNECTSTATE.equals("CONNECT")) //myMapView.setBuiltInZoomControls(true); appRes= ctn; myPaint = new Paint(); this.myMapView = myMapView; scrollingArea= new Rect(); contactPositionMap = new HashMap() ; ylwPaddle = BitmapFactory.decodeResource(appRes,R.drawable.ylw_circle); highlight = BitmapFactory.decodeResource(appRes,R.drawable.checked); blueBaloon = BitmapFactory.decodeResource(appRes,R.drawable.bluemessage); bluePaddle = BitmapFactory.decodeResource(appRes,R.drawable.blu_circle); } /** * Check if scrolling of the map view is needed or not. This basically means that * the view should be centered. * * @return true centering is needed and false otherwise */ private boolean scrollingIsNeeded(){ Collection pointList = contactPositionMap.values(); for (Iterator iterator = pointList.iterator(); iterator.hasNext();) { ContactLayoutData contactLayoutData = iterator.next(); if (contactLayoutData.isVisible && !scrollingArea.contains(contactLayoutData.positionOnScreen.x, contactLayoutData.positionOnScreen.y)){ return true; } } return false; } /** * Checks if zoom level should be recomputed according to current position of contacts on the map. * It basically checks if the max squared distance between the midpoint and one of the contacts is greater than the given threshold. * If only one contact is drawn, zoom level shall be set to max. * * @param params object containing the parameters computed from the current map view. * @return value that indicates how zoom level should be changed (ZOOM_MAX, NO_ZOOM, RECOMPUTE_ZOOM) */ private int zoomChangeIsNeeded(PointClusterParams params){ int retval = NO_ZOOM; int currentNumberOfPoints = getContactsOnline(); //If we have just one point left, we need to zoom to max level if (currentNumberOfPoints == 1 && myMapView.getZoomLevel() < 21){ retval = ZOOM_MAX; } else if (currentNumberOfPoints > 1){ //If we have many points compute the max squared distance from the midpoint int maxDistSquared = getMaxDistSquared(contactPositionMap.values(), params.midpointOnScreen); //if we are in the too far or too near range if (maxDistSquared < LOWER_THRESHOLD || maxDistSquared > UPPER_THRESHOLD){ retval = RECOMPUTE_ZOOM; } } return retval; } /** * Performs scrolling by centering the map on the point cluster using the set of parameters * * @param params the set of parameters computed from point cluster */ private void doScrolling(PointClusterParams params){ mapController.setCenter(params.midpointOnMap);//设置中心位置 } /** * Adjust the zoom level depending on cluster point parameters (coordinate max span in average) * * @param params the point cluster parameters * @param howToZoom integer value coming from zoomChangeIsNeeded() */ private void doZoom(PointClusterParams params, int howToZoom){ if (howToZoom == ZOOM_MAX) mapController.setZoom(16); if (howToZoom == RECOMPUTE_ZOOM) mapController.zoomToSpan(params.coordMaxSpan[0],params.coordMaxSpan[1]); } /** * Draws all the online contacts on the map. * * @param c the canvas we use to draw * @param p the paint object we use to draw */ private void drawOnlineContacts(Canvas c, Paint p){ //myLogger.log(Logger.INFO, "Start drawing all contacts!!"); FontMetrics fm = p.getFontMetrics(); int bluePaddleOffsetY= bluePaddle.getHeight(); int bluePaddleOffsetX= bluePaddle.getWidth()/2; int ylwPaddleOffsetY= ylwPaddle.getHeight(); int ylwPaddleOffsetX= ylwPaddle.getWidth()/2; int blueBaloonOffsetY = 25; int blueBaloonOffsetX = 4; myPaint.setTextSize(18); Set allParticipants = MsnSessionManager.getInstance().getAllParticipantIds(); int color=0; int iconToTextOffsetY= 5; for (Iteratoriterator = contactPositionMap.values().iterator(); iterator.hasNext();) { ContactLayoutData cData = (ContactLayoutData) iterator.next(); int bitmapOriginX=0; int bitmapOriginY=0; Bitmap bitmapToBeDrawn = null; if (cData.isVisible){ if (cData.isMyContact){ //204, 153, 102, 0 color = R.color.chat_dark_orange; //myLogger.log(Logger.INFO, "Drawing my contact: position on screen (" + cData.positionOnScreen[0] + ";" + cData.positionOnScreen[1] + ")"); bitmapOriginX = cData.positionOnScreen.x - ylwPaddleOffsetX; bitmapOriginY = cData.positionOnScreen.y - ylwPaddleOffsetY; //if(!focusState)//只有非聚焦状态才是自身,不改变颜色 bitmapToBeDrawn = ylwPaddle; } else { //Here blueBaloon for people you're chatting with if(allParticipants.contains(cData.idContact)){ bitmapOriginX = cData.positionOnScreen.x-blueBaloonOffsetX; bitmapOriginY = cData.positionOnScreen.y-blueBaloonOffsetY; bitmapToBeDrawn = blueBaloon; } else{ bitmapOriginX = cData.positionOnScreen.x-bluePaddleOffsetX; bitmapOriginY = cData.positionOnScreen.y-bluePaddleOffsetY; bitmapToBeDrawn = bluePaddle; } color = R.color.BuleDark; } int textOriginX = cData.positionOnScreen.x; int textOriginY = bitmapOriginY - iconToTextOffsetY; RectF rect = new RectF(textOriginX - 2, textOriginY + (int) fm.top - 2, textOriginX +this.getStringLength(cData.name, myPaint) + 2,textOriginY + (int) fm.bottom + 2); //Draws the debugging rct for collision //Draw the right bitmap icon if (cData.isChecked){ c.drawBitmap(highlight, bitmapOriginX, bitmapOriginY, myPaint); } else { c.drawBitmap(bitmapToBeDrawn, bitmapOriginX, bitmapOriginY, myPaint); } //Change color for background rectangle myPaint.setColor(Color.argb(204, 204,204, 0)); c.drawRoundRect(rect, 4.0f, 4.0f, myPaint);// Rect(rect, myPaint); //ChangeColor for text myPaint.setColor(color); c.drawText(cData.name,textOriginX, textOriginY, myPaint); myPaint.setARGB(204, 153, 102, 0); } } } /** * Overrides Overlay.draw() to draw the scene */ @Override public void draw(Canvas canvas, MapView mapView, boolean shadow) { Projection p = mapView.getProjection(); updateOnScreenPosition(p); //Compute params needed for further computations on the point cluster int onlineContacts = getContactsOnline(); if(!MainActivity.mapMoveFlag) { if (onlineContacts > 0){ PointClusterParams params = extractParams(p, onlineContacts); //Things we do just the first time if (WIDTH == -1){ initialize(p, mapView.getWidth(), mapView.getHeight()); int howToZoom = zoomChangeIsNeeded(params); doScrolling(params); doZoom(params, howToZoom); } else { //if any pixel is out our scrolling area if (scrollingIsNeeded()){ //change map center doScrolling(params); } int howToZoom = zoomChangeIsNeeded(params); if (howToZoom != NO_ZOOM) { doZoom(params,howToZoom); } } //Draw all the contacts drawOnlineContacts(canvas, myPaint); } }else{ if (onlineContacts > 0){ PointClusterParams params = extractParams(p, onlineContacts); //Things we do just the first time if (WIDTH == -1){ initialize(p, mapView.getWidth(), mapView.getHeight()); int howToZoom = zoomChangeIsNeeded(params); //doScrolling(params); //doZoom(params, howToZoom); } else { //if any pixel is out our scrolling area if (scrollingIsNeeded()){ //change map center //doScrolling(params); } int howToZoom = zoomChangeIsNeeded(params); if (howToZoom != NO_ZOOM) { //doZoom(params,howToZoom); } } //Draw all the contacts drawOnlineContacts(canvas, myPaint); } } } /** * Initialize all the constants values needed for drawing contacts and * performing zooming and scrolling. * * @param calculator {@link PixelCalculator} needed for retrieving map view size and projecting map points on screen */ private void initialize( Projection p, int width, int height ) { WIDTH = width; HEIGHT = height; SCROLL_AREA_HEIGHT = (int) (HEIGHT * SCROLL_AREA_HEIGHT_RATIO); SCROLL_AREA_WIDTH = (int) (WIDTH * SCROLL_AREA_WIDTH_RATIO); scrollingArea.top = (HEIGHT - SCROLL_AREA_HEIGHT)/2; scrollingArea.bottom = scrollingArea.top + SCROLL_AREA_HEIGHT; scrollingArea.left = (WIDTH - SCROLL_AREA_WIDTH)/2; scrollingArea.right = scrollingArea.left + SCROLL_AREA_WIDTH; int tmpThresh = (int ) (WIDTH * UPPER_THRESHOLD_RATIO); UPPER_THRESHOLD = tmpThresh * tmpThresh; tmpThresh = (int) (WIDTH * LOWER_THRESHOLD_RATIO); LOWER_THRESHOLD = tmpThresh * tmpThresh; } /** * Recomputes on screen positions of all the contacts after changing their location on the map * * @param calc the pixel calculator */ private void updateOnScreenPosition(Projection calc){ for (ContactLayoutData cData : contactPositionMap.values()) { if (cData.isVisible){ calc.toPixels(new GeoPoint(cData.latitudeE6, cData.longitudeE6),cData.positionOnScreen); } } } /** * Retrieves the number of contacts that currently are online * * @return number of online contacts */ private int getContactsOnline(){ int online =0; for (ContactLayoutData mapEntry : contactPositionMap.values()){ if (mapEntry.isVisible){ online++; } } return online; } /** * Computes a set of parameters from the the current contacts locations. *

* These are in particular: *

    *
  • cluster midpoint on the map (back projection of midpoint on screen) *
  • cluster midpoint on screen (average of contact positions on screen) *
  • max longitude span *
  • max latitude span *
* These parameters are useful for automatic zooming and centering and are computed during each draw cycle * * @param calc the {@link PixelCalculator} needed for computations * * @return class carrying results of computation */ private PointClusterParams extractParams(Projection p, int contactsOnLine){ int maxLat = Integer.MIN_VALUE; int minLat= Integer.MAX_VALUE;; int maxLong= Integer.MIN_VALUE;; int minLong =Integer.MAX_VALUE;; PointClusterParams params = new PointClusterParams(); params.midpointOnScreen = new int[2]; params.midpointOnScreen[0] = 0; params.midpointOnScreen[1] = 0; params.coordMaxSpan = new int[2]; for (Iterator iterator = contactPositionMap.values().iterator(); iterator.hasNext();) { ContactLayoutData clData = (ContactLayoutData) iterator.next(); if (clData.isVisible){ params.midpointOnScreen[0] += clData.positionOnScreen.x; params.midpointOnScreen[1] += clData.positionOnScreen.y; maxLat = (clData.latitudeE6> maxLat)? clData.latitudeE6 : maxLat; maxLong = (clData.longitudeE6 > maxLong)? clData.longitudeE6 : maxLong; minLong = (clData.longitudeE6 < minLong)? clData.longitudeE6 : minLong; minLat = (clData.latitudeE6 < minLat)? clData.latitudeE6 : minLat; //we need to zoom in another way if we have a single point if (maxLat == minLat){ params.coordMaxSpan[0] = -1; params.coordMaxSpan[1] = -1; } else { params.coordMaxSpan[0] = maxLat -minLat; params.coordMaxSpan[1] = maxLong - minLong; } } } params.midpointOnScreen[0] /= contactsOnLine; params.midpointOnScreen[1] /= contactsOnLine; params.midpointOnMap = p.fromPixels(params.midpointOnScreen[0], params.midpointOnScreen[1]); return params; } /** * Computes the max squared distance between the position of each contact on the screen and the midpoint. * * @param points locations of the online contacts in screen coordinates * @param midpoint the midpoint in screen coordinate * * @return the max squared distance */ private int getMaxDistSquared(Collection points,int[] midpoint){ int maxDist =0; //For each point for (Iterator iterator = points.iterator(); iterator.hasNext();) { ContactLayoutData contactLayoutData = iterator.next(); //Compute distance squared int distX = midpoint[0] - contactLayoutData.positionOnScreen.x; int distY = midpoint[1] - contactLayoutData.positionOnScreen.y; int distSq = distX*distX + distY*distY; if (distSq > maxDist) maxDist = distSq; } return maxDist; } /** * Collects and stores a set of parameters useful for automatic adjustment of zoom level * and automatic map centering. */ private class PointClusterParams { /** * Max span of latitude and longitude as a coordinate pair in microdegrees */ public int[] coordMaxSpan; /** * Midpoint on the map (computed as the back projection of the midpoint on screen) */ public GeoPoint midpointOnMap; /** * The midpoint on screen obtained as the average of the contact's on-screen positions. */ public int[] midpointOnScreen; } /** * Provides the status of the contacts as drawn on the screen. * Mantains a collection of info useful for drawing contacts data on map */ private class ContactLayoutData{ /** * The position on screen in pixel. */ public Point positionOnScreen; /** * The latitude in microdegrees. */ public int latitudeE6; /** * The longitude in microdegrees. */ public int longitudeE6; /** * The altitude in microdegrees. */ public int altitudeE6; /** * The name of the contact that should be drawn as a label */ public String name; /** * true if the contact is selected on map, false otherwise */ public boolean isChecked; /** * true if this is my contact, false otherwise */ public boolean isMyContact; /** * Flag that means that the contact should be shown on map (online) */ public boolean isVisible; /** * The contact id. */ public String idContact; /** * Instantiates a new contact layout data. * * @param cname the name of the contact * @param idcontact the contact id * @param contactLoc the contact location on the map * @param visible true if contact should be drawn, false otherwise */ public ContactLayoutData(String cname, String idcontact, Location contactLoc, boolean visible){ this.name = cname; this.idContact= idcontact; isMyContact = false;// positionOnScreen = new Point(); latitudeE6 = (int)(contactLoc.getLatitude() * 1E6); longitudeE6 = (int) (contactLoc.getLongitude() * 1E6); altitudeE6=(int)(contactLoc.getAltitude()*1E6); isVisible = visible; } //TODO:重载方法,焦点定位 public ContactLayoutData(String cname, String idcontact, Location contactLoc, boolean visible,int foucs){ this.name = cname; this.idContact= idcontact; isMyContact = true;// positionOnScreen = new Point(); latitudeE6 = (int)(contactLoc.getLatitude() * 1E6); longitudeE6 = (int) (contactLoc.getLongitude() * 1E6); altitudeE6=(int)(contactLoc.getAltitude()*1E6); isVisible = visible; } /** * Update the contact location on map with new data * * @param latitude the latitude in microdegrees * @param longitude the longitude in microdegrees * @param altitude the altitude in microdegrees */ public void updateLocation(int latitude, int longitude, int altitude){ latitudeE6 = latitude; longitudeE6 = longitude; altitudeE6 = altitude; } } /** * Returns the length of a string on screen in pixel drawn with the given Paint object. * * @param name string to be drawn * @param paint the Paint object * * @return the string length in pixel */ private int getStringLength (String name, Paint paint) { float [] widthtext= new float[name.length()]; float sumvalues=0; paint.getTextWidths(name, widthtext); for(int n=0; n getSelectedItems(){ List ids = new ArrayList(); for (ContactLayoutData cdata : contactPositionMap.values()) { if (cdata.isChecked){ ids.add(cdata.idContact); } } return ids; } /** * Unchecks all contacts. */ public void uncheckAllContacts(){ for (ContactLayoutData data : contactPositionMap.values()) { data.isChecked=false; myMapView.invalidate(); } } /** * Initialize the adapter by populating it with all available contacts and by creating the info * for each item. Every other modification shall be incremental. * */ public final void initializePositions(){ Map localContactMap = ContactManager.getInstance().getAllContacts(); Contact myContact = ContactManager.getInstance().getMyContact(); ContactLocation myCloc = ContactManager.getInstance().getMyContactLocation(); ContactLayoutData myCl = new ContactLayoutData(myContact.getName(),myContact.getPhoneNumber(),myCloc,true); myCl.isMyContact=true; myCl.isVisible = isValid(myCloc); contactPositionMap.put(myCl.idContact, myCl); for (Map.Entry contactEntry : localContactMap.entrySet()) { String phoneNum = contactEntry.getKey(); Contact currentC = contactEntry.getValue(); //empty location for invisible contact ContactLayoutData cdata = new ContactLayoutData( currentC.getName(),phoneNum, new ContactLocation( ((JChatApplication)((Activity) ctn).getApplication()).getProperty(JChatApplication.LOCATION_PROVIDER) ) , false); contactPositionMap.put(phoneNum, cdata); } focusState=false;//非聚焦状态改为false } public final void initializePositionsFocus(String id){ //取到所以联系人 Map localContactMap = ContactManager.getInstance().getAllContacts(); //取到自己的 Contact myContact = ContactManager.getInstance().getMyContact(); ContactLocation myCloc = ContactManager.getInstance().getMyContactLocation(); ContactLayoutData myCl = new ContactLayoutData(myContact.getName(),myContact.getPhoneNumber(),myCloc,true); //不以自己为中心 contactPositionMap.clear();//清空联系人,然后重新初始化 myCl.isMyContact=false; myCl.isVisible = isValid(myCloc); contactPositionMap.put(myCl.idContact, myCl); for (Map.Entry contactEntry : localContactMap.entrySet()) { String phoneNum = contactEntry.getKey(); Contact currentC = contactEntry.getValue(); if(phoneNum.equals(id)) { //重载,可见 ContactLayoutData cdata = new ContactLayoutData( currentC.getName(),phoneNum, new ContactLocation( ((JChatApplication)((Activity) ctn).getApplication()) .getProperty(JChatApplication.LOCATION_PROVIDER) ) , true,1); contactPositionMap.put(phoneNum, cdata); }else{ //empty location for invisible contact ContactLayoutData cdata = new ContactLayoutData( currentC.getName(),phoneNum, new ContactLocation( ((JChatApplication)((Activity) ctn) .getApplication()).getProperty(JChatApplication.LOCATION_PROVIDER) ) , false); contactPositionMap.put(phoneNum, cdata); } } focusState=true;//聚焦状态改为true } /** * Update all contacts location based on the changes notified by the agent (contacts added and contacts removed) * * @param changes list of changes * @param locationMap * @param contactMap */ public void update(ContactListChanges changes, Map contactMap, Map locationMap){ ContactLocation cMyLoc = ContactManager.getInstance().getMyContactLocation(); //myLogger.log(Logger.INFO, "It's time for updating the contactsPositionOverlay!!!!!"); //Removed contacts for ( String removedId : changes.contactsDeleted) { contactPositionMap.remove(removedId); } //Added contacts for ( String addedId : changes.contactsAdded) { ContactLayoutData newData = new ContactLayoutData(contactMap.get(addedId).getName(),addedId,locationMap.get(addedId),true); newData.isVisible = isValid(locationMap.get(addedId)); contactPositionMap.put(addedId, newData); } //update all others for (ContactLayoutData cData : contactPositionMap.values()) { ContactLocation lastLocation= null; Contact curContact = null; if (cData.isMyContact) { //update contact visibility //if(!focusState){ lastLocation = cMyLoc; cData.isVisible = isValid(lastLocation); //myLogger.log(Logger.INFO, "Ok... Ready to update location of my contact!!!!!"); cData.updateLocation((int)(lastLocation.getLatitude()*1E6), (int)(lastLocation.getLongitude()*1E6), (int)(lastLocation.getAltitude()*1E6)); } else { curContact = contactMap.get(cData.idContact); if (curContact != null && curContact.isOnline()){ lastLocation = locationMap.get(cData.idContact); if (isValid(lastLocation)){ cData.isVisible = true; cData.updateLocation((int)(lastLocation.getLatitude()*1E6), (int)(lastLocation.getLongitude()*1E6), (int)(lastLocation.getAltitude()*1E6)); } } else { cData.isVisible = false; } } } } private boolean isValid(Location loc){ return (loc.getLatitude() != Double.POSITIVE_INFINITY && loc.getLongitude() != Double.POSITIVE_INFINITY && loc.getAltitude() != Double.POSITIVE_INFINITY); } }