יום רביעי, נובמבר 17

הוספת שכבות על גבי המפה - Overlays

זה הפוסט השני בנושא המפות. בפוסט המפות קודם הצגנו את MapView, את MapActivity וגם ראינו את GeoPoint שהוא class להמרה בין קואורדינטות לשמות מקומות.
מטרת הפוסט הזה היא להסביר איך מציגים סימנים על גבי המפה - overlays. נדגים הצבה של תמונה (האייקון של אנדרואיד) על המפה. כדי להציג את הסימנים, נשתמש ב-class ששמו ItemizedOverlay . אגב, ה-classes האלה שייכים ל-API של Google Maps ואינם מתועדים באתר המפתחים של אנדרואיד.
האפליקציה שלנו תציב 3 אייקונים על המפה ב-3 מקומות - פאריס, לרנקה, לונדון-ארה"ב, לפי קורדינטות קבועות מראש בקוד. (היה אפשר להכניס את המקומות באופן אינטראקטיבי, אבל כדי לא לאבד מיקוד, נעשה את זה בפוסט אחר, כשנעסוק ב- Geocoding).
כדי להוסיף קצת עניין, הוספתי פונקציות למחיקת האייקונים, הוספת האיקונים, מעבר פוקוס התצוגה בין האייקונים, כל זה ע"י לחיצה על מקשים כפי שנראה בהמשך.
הדוגמא הזו מרחיבה את הדוגמא הקודמת (שוב קישור לפוסט הקודם), לכן אשתדל לדלג על החלקים הדומים ולהתרכז במה שקשור ל-overlays.
כרגיל, קישור להורדת הקבצים מופיע למטה בסוף, וכרגיל, זה מאד לא מסובך.
תמונת המפה עם אחד האייקונים שהוצב בלונדון שבקנטאקי מוצגת לפניכם:

נדון רק בקבצי ה-java, אך לא לשכוח את עניין ההרשאות וה-API Key שפורטו בפוסט הקודם. ה-Key כמובן לא משתנה בין הפרויקטים ואפשר להעתיקו מקובץ ה-layout הקודם. לא לשכוח לעדכן גם את ה-manifest.xml.
אגב, אם במקום מפה מופיע רק רקע לבן, הבעיה כנראה ב-API Key.
וניגש לעבודה: שני classes מומשו: האחד subclass של  MapActivity. זהו האקטיביטי הראשי.
ה-class השני מחזיק את התמונה שמשמשת כ-overlay, ומנהל רשימה של נקודות בהן יש להציג את התמונה.  זהו subcalss של ItemizedOverlay. 

נסתכל על השלד של ה-class הראשי MapActivity:
  1. public class myMapOverlay extends MapActivity {
  2.      @Override
  3.     protected void onCreate(Bundle savedInstanceState) {
  4.  
  5.     }
  6.     @Override
  7.     protected boolean isLocationDisplayed() {
  8.         return false;
  9.     }
  10.     @Override
  11.     protected boolean isRouteDisplayed() {
  12.         return false;
  13.     }
  14.    
  15.     private void mLocationGroupSet(){
  16.     }
  17.     private void mLocationClear()
  18.     {
  19.     }
  20.     private void nextMarkPointShow()
  21.     {
  22.          
  23.     }
  24.     public boolean onKeyDown(int keyCode, KeyEvent event) {
  25.      }
  26. }
 יש כאן 7 מתודות.
  1. OnCreate היא העיקרית.
  2.   isLocationDisplayed ו-    isRouteDisplayed הן מתודות שחייבים לממש ב-MapActivity, כפי שהוסבר בפוסט המפות הראשון.
  3. mLocationGroupSet היא מתודה להדגמה, שמוסיפה כמה overlays למפה. 
  4. mLocationClear  היא מתודה להדגמה, שמוחקת את כל ה-overlays.
  5. nextMarkPointShow- היא מתודה להדגמה שמעבירה את הפוקוס של תצוגת המפה בין ה-layouts הקיימים.
  6.  onKeyDown- זהו ה-callback שבו מופעלות מתודות ההדגמה בהתאם למקשים שהוקלקו.
נתחיל עם ה-onCreate שכידוע מופעלת פעם אחת עם יצירת האובייקט. יש כאן 3 שורות חשובות: שורות 7-9.
שורות 1-6 מוכרות מהפוסט הקודם. אלה היו הפעולות ליצירת המפה.
ושורות 10-12 מציבות overlay על המפה לפי קורדינטות נתונות.
שורות 13-15, כמו בפוסט הקודם, ממרכזות את התצוגה על הנקודה.
  1. protected void onCreate(Bundle savedInstanceState) {
  2.         super.onCreate(savedInstanceState);
  3.         setContentView(R.layout.main);
  4.         mapView = (MapView) findViewById(R.id.mapview);
  5.         mapView.setBuiltInZoomControls(true);
  6.         mapView.setClickable(true);
  7.         Drawable drawable=getResources().getDrawable(R.drawable.icon);
  8.         itemizedoverlay = new myItemizedOverlay(drawable);
  9.         mapView.getOverlays().add(itemizedoverlay);
  10.         GeoPoint point = new GeoPoint((int)(37.129474*1000000),(int)(-84.083576*1000000));
  11.         OverlayItem overlayitem = new OverlayItem(point, "London", "London");
  12.         itemizedoverlay.mlocationAdd(overlayitem);
  13.         mapView.getController().setCenter(point);
  14.         mapView.getController().setZoom(15);
  15.         itemizedoverlay.setFocus(overlayitem);
  16.     }

נעתיק את שורות 7-9 לשם הנוחות:
  1.         Drawable drawable=getResources().getDrawable(R.drawable.icon);
  2.         itemizedoverlay = new myItemizedOverlay(drawable);
  3.         mapView.getOverlays().add(itemizedoverlay);

שורה 1 (8 במקור למעלה): שליפת ה-id של התמונה שתשמש כ-overlay  מ-class R.
שורה 2: יצירת אובייקט מסוג myItemizedLayout, אותו נראה בהמשך. ה-constructor שלו מקבל את ה-icon אותו יצרנו בשורה 7 כפרמטר.
שורה 3: הוספת אובייקט ה-overlay שיצרנו בשורה 8 למפה.

נסתכל על שורות 10-12:
  1.    GeoPoint point = new GeoPoint((int)(37.129474*1000000),(int)(-84.083576*1000000));
  2.         OverlayItem overlayitem = new OverlayItem(point, "London", "London");
  3.         itemizedoverlay.mlocationAdd(overlayitem)

שורה 10 (שורה 1 כאן): (גם היא שיגרתית והוצגה בדוגמא הקודמת) יצירת אובייקט מסוג GeoPoint המשמש ליצוג נקודות על המפה. כאן הוא מקבל את הנקודה שנמצאת בלונדון, קנטאקי.
שורה 11: יצירת Instance של האובייקט OverlayItem. ה-Constructor שלו מקבל 3 פרמטרים: את מיקום הנקודה (אובייקט ה- GeoPoint), כותרת (Title), וטקסט נילווה (snippet).
שורה  12 (שורה 3 כאן): מוסיפה את ה-overlayitem לתוך ה-overlay. המתודה שעושה את זה היא mlocationAdd אותה מימשנו באובייקט itemizedoverlay, ועוד נגיע אליה (אגב, היא ממש אלמנטרית).

זהו עיקר ה-class.  נציג רק את ה-callback שמופעל עם לחיצת המקשים ה- onKeyDown. הוא לא קשור ישירות לנושא המפות אבל זו הזדמנות להדגים אותו:


  1. public boolean onKeyDown(int keyCode, KeyEvent event) {
  2.         if ( keyCode == KeyEvent.KEYCODE_A) {
  3.             mLocationGroupSet();
  4.             return true;
  5.           }
  6.         if ( keyCode == KeyEvent.KEYCODE_C) {
  7.             mLocationClear();
  8.             return true;
  9.           }
  10.         if ( keyCode == KeyEvent.KEYCODE_S) {
  11.             nextMarkPointShow();
  12.             return true;
  13.           }
  14.          return false;
  15.     }
 במימוש שלנו, אנו מתעניינים ב3 מקשים: A, C ו-S.
לחיצה על A תגרום להוספה של overlays בשתי נקודות על המפה. זה מתבצע במתודה mLocationGroupSet. המתודה מוסיפה overlays בפאריס ובלארנקה. היא פשוטה לקריאה והבנה.
לחיצה על C תגרום למחיקת כל ה-overlays מהמפה. זה מתבצע במתודה mLocationClear. מדובר בשורת קוד אחת שמפעילה מתודה של ה-class השני.
כל לחיצה על המקש S תעביר overlays אחר מתוך אלה שהכנסנו למרכז המפה:

  1. if(!(itemizedoverlay.mIsEmpty()))
  2.         {
  3.             Integer    numOfMArks = itemizedoverlay.size();
  4.             idxOfMarkToShow = (idxOfMarkToShow+1)%numOfMArks;
  5.             mapView.getController().setCenter(itemizedoverlay.getItem(idxOfMarkToShow).getPoint());
  6.             mapView.getController().setZoom(10);
  7.         }

שורה 1: אם לא מוגדרים overlays, כלום לא יתבצע.
שורה 3: שליפת מספר ה-overlays שהוכנסו למפה.
שורה 4: הגדלת האינדקס idxOfMarkToShow - הוא מצביע על  ה-overlay הבא ברשימה. ההגדלה נעשית  מודולו %numOfMArks, כך שהאינקס ינוע בין 0 ל- numOfMArks-1.
שורה 5: ממרכזת את המפה סביב ה-layout עליו אנו מצביעים.
שורה 6: זום נקבע ל 10, (באופן שרירותי לגמרי).

  עד כאן ה-class העיקרי.
נעבור ל-class שני -  myItemizedOverlay. כמו שציינו, הוא מקבל את התמונה שתוצג כ-overlay. הוא מנהל רשימה של Overlay Items - הנקודות על המפה בהן תוצג תמונת ה-overlay.
כמות הקוד שנידרשנו להוסיף כאן מאד מצומצמת:


  1. constructor, 
  2. שלוש מתודות שחייבים לממש (לעשות override), 
  3. ושלוש מתודות שירות קטנות שכתבתי, באורך של כשורה כל אחת.
הנה ה-class כולו, הסברים מיד:

  1. public class myItemizedOverlay extends ItemizedOverlay<OverlayItem>{
  2.     private List<OverlayItem> locations = new ArrayList<OverlayItem>();
  3.     private Drawable mdrawable;
  4.     public myItemizedOverlay(Drawable drawable)
  5.     {
  6.         super(drawable);
  7.         this.mdrawable=drawable;
  8.     }
  9.     @Override
  10.     public void draw(Canvas canvas, MapView mapView, boolean shadow) {
  11.         super.draw(canvas, mapView, shadow);
  12.         boundCenterBottom(mdrawable);
  13.     }
  14.     @Override
  15.     protected OverlayItem createItem(int i) {
  16.         return locations.get(i);
  17.     }
  18.     @Override
  19.     public int size() {
  20.         return locations.size();
  21.     }
  22.     public void mlocationAdd(OverlayItem mark){
  23.         locations.add(mark);
  24.         populate();
  25.     }
  26.     public void mLocationClear(){
  27.         locations.clear();
  28.     }
  29.     public boolean mIsEmpty(){
  30.         return(locations.isEmpty());
  31.     }
  32. }

תחילה ה-constructor, שמופעל כמובן עם יצירת Instance של האוביקט (new myItemizedOverlay)

הנה ה- constructor בכבודו:
  1.  public myItemizedOverlay(Drawable drawable)
  2.     {
  3.         super(drawable);
  4.         this.mdrawable=drawable;
  5.     }
 הוא מקבל כפרמטר את האובייקט אותו רוצים להציג על גבי המפה.
שורה 3: קריאה ל-constructor האב.
שורה 4: שמירת הפרמטר, כדי שנוכל להשתמש בו במתודות אחרות גם כן.
זהו כל ה-constructor.

    3  המתודות אותן חייבים לממש ב-class:

    1. draw - לציור האובייקט.
    2. createItem
    3. ו-size , שמחזיר את מספר ה-overlays של האובייקט. (עשינו בה שימוש במתודה -nextMarkPointShow של ה-class השני).

    ובסוף הוספנו 3 מתודות עזר, בהן השתמשנו ב-class השני. הן פשוט עוטפות מתודות של אובייקט רשימת ה-overlays עצמו - locations, אליו לא ניתן לגשת מבחוץ.
    void mlocationAdd : מוסיפה עוד מיקום לרישמת ה-overlays.
    mLocationClear: מנקה את כל הנקודות מרשימת ה-overlays.
    mIsEmpty - בודק אם רשימת ה-overlay ריקה.

    המתודות האלה מוציאות אינפורמציה מרשימת ה-overlays  שיצרנו: locations. היות שהגדרנו את locations כ-private אין אפשרות לגשת אליו ישירות מבחוץ, ולכן "נאלצנו" לעטוף אותו במתודות שרות. אפשר היה לחשוף את  locations ע"י הגדרתו כ-public וכך לחסוך את הצורך בכתיבת מצודת מעטפת לכל צורך.

    סיימנו את החלק השני של נושא המפות. הדגמנו שימוש ב-overlays. כמו שהבטחתי,  לא סיימנו עם המפות!


    אין תגובות:

    הוסף רשומת תגובה