אב

הרפתקאות מפת בתי הספר באתר אורט – פרק ב’, המפה: איך מתחילים

הפוסט הזה הוא חלק מסדרה על הקוד של עמוד בתי הספר של אורט באתר אורט החדש, עמוד שמכיל רשימה של כל בתי הספר של אורט לפי ערים, ומפה עם סימוני כל בתי הספר.
בפרק ההקדמה סיפרתי על הרקע הכללי של האתר ומבנה הסדרה, בפרק הקודם סיפרתי על סינון הערים לפי מחרוזת חיפוש, ובפרק הזה נצלול לחלק של המפה.

איך מטמיעים מפה קיימת

זאת מפת בתי הספר של אורט. היא נבנתה בגוגל מפות, וכמו שאתם רואים, הנקודות כבר נמצאות עליה.

מפת בתי הספר של אורט

כשהפתרונות ברשת לא מתאימים למקרה שלנו

המפה קיימת בגוגל, אבל איך מטמיעים אותה באתר שלנו? מאחר שאנחנו רוצים אינטראקציה עם הערים, הטמעה באמצעות iframe, שהיא ההטמעה הפשוטה והקלה ביותר, לא מועילה לנו, ואנחנו נאלצים להטמיע עם JS, שזה אומר ממש לכתוב קוד. רק אתמול דיברתי בשבח העצלנות, וכבר היום אני מפרה את הכלל החשוב הזה 🙁 .

מאחר שאני יצור עצלן ולא רוצה לכתוב קוד שאני לא חייבת, חיפשתי ברשת פתרונות. התחלתי עם חיפוש בתוספים של וורדפרס, ומשלא מצאתי משהו מתאים עברתי לחפש דוגמאות קוד. מה הייתה הבעיה בתוספים ובקוד שמצאתי? כולם מסבירים איך להטמיע מפה, אבל לא מסבירים איך להטמיע את הסימונים שעליה (סימוני בתי הספר). כי מבחינת גוגל אלה שני אובייקטים שונים – המפה לחוד, והסימונים לחוד.

הרשת מלאה בדוגמאות להטמעת מפה, ובעניין הסימונים – הם מראים איך ליצור נקודות במפה באמצעות קוד. כלומר, המתכנת צריך ליצור אובייקט של סמן, עם ההגדרות של נ”צ, שם, זום וכו’. טוב, אצלנו זה לא היה ריאלי בשום מצב: גם מפני שיש לנו יותר מ-100 נקודות ומי המתכנת המשוגע שיגדיר 100 אובייקטים, וגם מפני שאיננו רוצים להצטרך מתכנת בכל פעם שנרצה להוסיף או להוריד נקודה.

לכן כל חיפושיי ברשת העלו חרס: לא מצאתי תוסף או קוד שמראה איך להטמיע מפה עם הסימונים שעליה באמצעות קוד. כבר הייתי קרובה לייאוש, כשפתאום נכנס לתמונה מנהל היחידה שלנו, אילון קורן, ומצא לי ספר על ה-API של מפות גוגל. עכשיו, בדרך כלל אני סקפטית מאוד לגבי ספרים, מפני שהם תמיד חשודים בעיניי כבלתי מעודכנים (API הוא דבר שמשתנה, ספר – לא), אבל כאמור, כבר מיציתי את כל האפשרויות האחרות, שהחלטתי לתת לספר צ’אנס. אם לא יועיל, לא יזיק.

והפלא ופלא, הספר הועיל והושיע אותי – סוף סוף מצאתי בו פתרון לשימוש בנקודות ממפה קיימת!
(הוא גם עזר לי בעוד תחום: בסנכרון בין המפה ורשימת הערים. אספר על זה באחד הפרקים הבאים בע”ה).

 

כריכת הספר Google Maps API

איך משתמשים במפה קיימת

אחרי שמבינים שמבחינת גוגל יש הפרדה לוגית בין המפה עצמה, לבין סימוני בתי הספר, מבינים שכדי להטמיע מפה קיימת, עם הסימונים עליה, יש לבצע כמה שלבים:

1. להביא מפה – בלי הסימונים שלנו – בקואורדינטות המתאימות ובזום המתאים:

מתחילים עם הבאת המפה הממוקדת בנ”צ של ארץ ישראל. מדובר רק על אובייקט המפה בלבד. איך עושים את זה? ב-HTML יוצרים אלמנט בעל ה-ID של map (ה-id הוא חשוב, כי הוא מועבר ב-JS לאובייקט של גוגל. אפשר להחליף id, אבל חשוב לעדכן את ה-JS), וב-JS קוראים לפונקציית יצירת המפה כדי להכניס את המפה לאלמנט.

קריאה לקבצי ה-JS

יש שני קבצים שאני צריכה לקרוא להם בתוסף הזה. הראשון הוא הסקריפט של גוגל:

wp_enqueue_script( 'google-maps-script', "https://maps.googleapis.com/maps/api/js?key={$GOOGLE_MAPS_API}&language=he&region=IL", array( 'schools_and_map_filter' ), '1.0.1.2', true );

והשני הוא הסקריפט שלי:

wp_enqueue_script( 'schools_and_map_filter', plugin_dir_url( __FILE__ ) . 'js/schools_filter.js', array( 'jquery' ), time(), true );
יצירת אלמנט ה-HTML

את האלמנט יצרנו בתוך ווידג’ט טקסט של אלמנטור (היה לנו אלמנטור בעמוד בגלל רשימת בתי הספר שבנויה באמצעות ווידג’ט האקורדיון שלו).

<div id="map"></div>

קוד ה-JS שמכניס את המפה לאלמנט
/**
* Create map and center it in the center of Israel
*/
function createMap(lat = 32.61074307932485, lang = 36.474776492187516) {
    let mapOptions = {
        center: new google.maps.LatLng(lat, lang),
    };
    map = new google.maps.Map(document.getElementById('map'), mapOptions);
}

2. להביא את הסימונים של בתי הספר

סימוני בתי הספר הם למעשה שכבה מעל המפה. כדי להכניס אותם מעל המפה שהבאנו, צריך קודם לייצא אותם ממפת הגוגל (זה נעשה מתוך ממשק ניהול המפה ואין צורך בתכנות), ואז לייבא אותם ב-JS (לזה כבר כן צריך מתכנת).

ייצוא הסימונים לקובץ

כדי לייבא את הסימונים, צריך לייצא את שכבת הסימונים במפת הגוגל לקובץ kmz. הקובץ הזה מכיל את המידע על הסימונים: שם הסימון (למשל “אורט חולון”), הקואורדינטות שלו, התיאור שמופיע בחלונית כשלוחצים על סימון בית הספר, ועוד.

מנגנון הייצוא נמצא כאמור בממשק ניהול המפה, והוא מאפשר לייצא שני סוגי קבצים: kmz או kml. קובץ ה-kml דומה לקובץ XML, ולעומתו קובץ ה-kmz הוא קובץ בינארי. יש לשים לב שלקובץ ה-kml יש חיסרון – אם לסימונים יש איקון מיוחד ולא סטנדרטי, קובץ ה-kml לא מביא את המידע הזה (האזהרה על זה כתובה בחלונית הייצוא).  קובץ ה-kmz לעומתו כן מביא את המידע על האיקונים ולכן השתמשתי בו בפרויקט, כי האיקונים שלנו מעוצבים עם לוגו אורט עליהם.

מה שמקסים בקבצים האלה הוא שהם ממשיכים להתעדכן גם אחרי הייצוא – אם בוחרים את האופציה הזאת בזמן הייצוא (“Keep data up to date with network link…”). זה אומר שאם מוסיפים בתי ספר חדשים; מזיזים, או מוחקים בתי ספר קיימים – הקובץ מביא את המידע המעודכן הזה למפה המוטמעת. אמריקה!

חלונית הייצוא ל-kmz בעריכת מפת גוגל
ייבוא הסימונים בקוד

ל-API של גוגל יש אובייקט בשם kmlLayer שבעזרתו מביאים את קובץ ה-kmz, עם הנקודות עליה.

/**
     * Synchronize with an externally created mao by using the KmlLayer object.
     * We export a kmz file from the external map and upload it to our site uploads folder.
     * We add the Date parameter to get the updated version once a day. We don't do it more frequently because it hurts performance
     */
    function addKmlLayer() {
        let today = new Date();
        kmlLayer = new google.maps.KmlLayer({
            url: `${schools_and_map_filter_ajax_obj.kmz_file}?ver=${today.getDate()}`
        });
        resetMap();
    }

את כתובת קובץ ה-kmz אני שולחת מקובץ ה-php:

wp_localize_script( 'schools_and_map_filter', 'schools_and_map_filter_ajax_obj', array(
'kmz_file' => plugin_dir_url( __FILE__ ) .'js/ort-schools.kmz',
) );

וכך נוצרת לנו מפה עם המיקום הנכון והסימונים של בתי הספר שלנו.

הערה: באתר שלנו יש מנגנון קשינג, מה שאומר שעל אף שקובץ ה-kmz אמור להתעדכן כל הזמן, בפועל כשהוא מובא מהקש הוא לא מכיל מידע מעודכן. הפתרון הראשון שחשבתי עליו הוא להוסיף מספר גרסה, שיהיה הזמן כרגע. כך נוודא שתמיד עולה קובץ חדש ומעודכן. הבעיה היא שזה יצר בעיית ביצועים. ניסיתי שמספר הגרסה יהיה רק התאריך של היום, בלי השעה, וכך הקובץ יתעדכן רק פעם ביום, אבל גם זה לא עובד מלהיב. אז השארנו בלי מספר גרסה, והקובץ מתעדכן בהתאם לריקון הקש.

עדכון יולי 2020: לאחרונה התברר לי שקובץ ה-kml לא תמיד נטען מיד, ולכן כשמנסים להשתמש בו לפני שהוא נטען, בעצם לא רואים את המרקרים. לקח לי למלא זמן לגלות שזה מה שגרם למרקרים שלנו להיעלם מהמפה לכמה חודשים(!), עד שמצאתי את התשובה הזאת מקבוצת גוגל נידחת משנת 2010 וזה מה שהציל אותנו. למעשה צריך לחכות ל-event ששמו tilesloaded (והוא לא מתועד בשום תיעוד רשמי של גוגל) ורק אז אפשר לבדוק אם השכבה עלתה עם הפונקציה kmlLayer.getMetadata(). ולכן כעת הקוד שלנו נראה כך:

function addKmlLayer() {
    let today = new Date();
    kmlLayer = new google.maps.KmlLayer({
        url: `${schools_and_map_filter_ajax_obj.kmz_file}?ver=${today.getDate()}`
    });
    // The way to know if the kmlLayer loaded correctly is to check the Metadata. It appears that getMetadata only returns an
    // object after the layer has finished rendering, and this can be done using the tilesloaded  event. Taken from here:
    // https://groups.google.com/g/google-maps-js-api-v3/c/tzooDZ1jqYE?pli=1
    google.maps.event.addListenerOnce(map, 'tilesloaded', function () {
        let layerMetadata = kmlLayer.getMetadata();
        if (layerMetadata !== 'undefined') {
            KmlLayerExists = true;
        }
        resetMap();
    });
}

 

אתחול המפה

טוב, יש לנו אלמנט HTML, יש לנו פונקציה שיוצרת מפה ופונקציה שמביאה את שכבת הסימונים. מה עושים איתם?

יצרתי פונקציה בשם initMap והכנסתי אליה את הקריאות לפונקיציות. לפונקציה הזאת אני קוראת מיד עם עליית העמוד, בפונקציה של jQuery(document).ready:

    function initMap() {

        createMap();

        addKmlLayer();

        createInfoWindowObject();

        /***********/
        /* EVENTS */
        /***********/

        /* When the map bounds are changed, change the city list to reflect the new bounds */
        google.maps.event.addListener(map, 'bounds_changed', boundsChangedHandler);

        /* When one of the markers is clicked, open a styled popup, using the InfoWindow object created earlier */
        kmlLayer.addListener('click', kmlLayerClickedHandler);

        /* event taken from here: https://stackoverflow.com/questions/6777721/google-maps-api-v3-infowindow-close-event-callback/6777885#6777885 */
        google.maps.event.addListener(infowindow, 'closeclick', closeClickHandler);

        /* Clicking the map closes anything that had to do with clicking a marker */
        google.maps.event.addListener(map, 'click', mapClickHandler);

    }

אובייקט המפה מוצהר מחוץ לפונקציה הזאת, ומקבל את הערך שלו בפונקציה createMap שהראיתי לעיל. פונקציית addKmlLayer נדונה גם היא לעיל. הפונקציה createInfoWindowObject תידון בפרק ה’.
חוץ מהקריאות לפונקציות האלה, מיקמתי את כל אירועי המפה בתוך פונקציית initMap, כי הם זקוקים לאובייקט המפה. ארחיב עליהם כל אחד בעיתו ובזמנו.

***

הנושא הבא הוא  סנכרון בין המפה לרשימת הערים. כדי להכין את הקרקע, כלומר על מנת שבכל לחיצה על עיר המפה תדע להתמקד בעיר הזאת, היינו צריכים להכין תשתית. על ההכנה הזאת בפרק הבא.

איך הסתנכרנה המפה עם רשימת הערים? ואיך נפתחת חלונית מעוצבת כשלוחצים על בית ספר במפה? על כל אלה ועוד, בפרקים הבאים.

2 תגובות על “הרפתקאות מפת בתי הספר באתר אורט – פרק ב’, המפה: איך מתחילים

כתבו תגובה

כתובת הדוא"ל שלכם לא תוצג.