= 2009-02-04 = == 感想與結論 == * 追了一天的"赤壁" Mapplet 原始碼,得到兩個啟示: 1. 要實現不同的連結點選,呈現不同的圖層,赤壁這個 Mapplet 是靠 Hash 來存資料,並且每個動作前面都把 Hash 連結到的 Overlayer 清除掉。 2. 用 jQuery 可以比較簡單地在 Run-Time 為每個連結設定 click 事件的處理方式。 * [補遺] 其實還有其他啟示: 1. 赤壁的 Mapplet 擺在 code.google.com 上,所以"程式跟資料"全部都存在 Google File System 上,調閱的速度也相對快許多。流量全部吃 Google 對外的頻寬。這招狠~好在是 Google 自家員工這樣搞~ 2. 從赤壁的 Mapplet 學到一點,事先規劃資料來源的 Data Model 是很重要的。在寫應用程式之前,應該先思考資料的 Model 然後加入操作流程的思考。 == 原始碼追蹤紀錄 == * 原本 waue 發表的[wiki:oid/WorkLog/GoogleMPL Google Map的網址模組轉換],在官方的名稱為 [http://code.google.com/intl/zh-TW/apis/maps/documentation/mapplets/ Google Mapplets API]。 * Google Mapplet API 跟 Google Map API 最主要的差別在於 Mapplet 是在官方 Google Map 上呈現,而 Google Map API 則可以用在自己的網站上。 {{{ The main difference is that Mapplets run on Google Maps, while the traditional Maps API is used to create maps on other websites. }}} * "赤壁"原始碼 - http://code.google.com/p/redcliff/source/checkout {{{ svn checkout http://redcliff.googlecode.com/svn/trunk/ redcliff-read-only }}} * 赤壁的 Mapplet [[Image(wiki:oid/WorkLog/09-02-04:redcliff.jpg)]] * 在原始碼中,people.json 是用來顯示"主要人物"這個 Tag 的資料來源 [[Image(wiki:oid/WorkLog/09-02-04:people.json.jpg)]] * 在原始碼中,big_event.json 是用來顯示"歷史事件"這個 Tag 的資料來源 [[Image(wiki:oid/WorkLog/09-02-04:big_event.json.jpg)]] * 在原始碼中,event.json 是用來顯示點選"歷史事件"其中一個事件後,顯示各事件的關連資料來源 [[Image(wiki:oid/WorkLog/09-02-04:event.json.jpg)]] * 在原始碼中,location.json 是用來把歷史古城的位置用貼圖方式顯示的經緯度資料來源 [[Image(wiki:oid/WorkLog/09-02-04:location.json.jpg)]] * 在原始碼中,element.json 是用在點選特定古城位置時,顯示關聯事件的資料來源 [[Image(wiki:oid/WorkLog/09-02-04:element.json.jpg)]] * [http://redcliff.googlecode.com/svn/trunk/mapplet/redcliff_tc.xml 赤壁 Mapplet] 原始碼 XML 解析 {{{ #!js # 參考 http://code.google.com/intl/zh-TW/apis/maps/documentation/mapplets/basics.html#Loading_the_Mapplets_API # 必須加入 sharedmap 的 feature 才能讓 mapplet 載入定義在 Content 中的 javascript 程式 @CHARSET "UTF-8"; ### 以下略過 CSS 定義 ### # 初始化的時候,顯示"正在讀取..."的圖示跟字樣


正在讀取...
}}} * 底下先定義 DOM 元件 {{{ # 初始之後要顯示的 DIV 元件 }}} * 主要顯示三個元件,包括"分享給朋友"、"清除地圖上的標誌"、"聲明"[[BR]][[Image(wiki:oid/WorkLog/09-02-04:bottom-button-div1.jpg)]] * 點選"聲明"才會顯示 disclaimer_box 的畫面[[BR]][[Image(wiki:oid/WorkLog/09-02-04:bottom-button-div2.jpg)]] {{{ #!js
}}} * 上面這一段是宣告要載入 Hosting 在 Google 的 jQuery API 1.2.6 版 {{{ #!js }}} * [註] 底下開始會稍微變動一下原始碼的順序,我們先來看 jQuery 的 main 進入點 {{{ #!js $(function(){ G_MAP = new RedcliffMap(); # 呼叫 RedcliffMap() 產生 GMap2 物件 LoadLocation(); # 呼叫 LoadLocation() 從 location.json 讀檔並把資料存進 LOCATION 的 Hash 物件中 # - 呼叫 LoadElement() 從 element.json 讀檔並把資料存進 ELEMENT 的 Hash 物件中,LOAD_STATES=1 # - 呼叫 LoadEvent() 從 event.json 讀檔並把資料存進 EVENT 的 Hash 物件中 # - 呼叫 LoadBigEvent() 從 big_event.json 讀檔並把資料存進 BIG_EVENT 的 Hash 物件中,LOAD_STATES=2 # - 呼叫 LoadPeople() 從 people.json 讀檔並把資料存進 PEOPLE 的 Hash 物件中,LOAD_STATES=3 new TilesSelect(); # new TabManager(['events', 'characters', 'vote'], 'characters'); new Disclaimer(); $('#clear_button').click(function(){ G_MAP.clearOverlays(); }); new CharacterFilter(); _IG_Analytics(UAACCT, "/view/load"); }); }}} * 關於 G_MAP = new !RedcliffMap(); 這行程式 {{{ ### 首先要看的是 RedcliffMap 這個類別物件的建構子(Constructor) ### 以下省略類別中的 method 函數定義,從這裡可以看到 RedcliffMap 定義了 9 個 method ### changeTiles(opacity_val), ### addOverlay(overlay), ### removeOverlay(overlay), ### openInfoWindow(type, id, latlng), ### highLightOverlay(element_ids), ### deHighLightOverlay(), ### setCenter(center, level), ### clearOverlays(), ### updateOverlay(type, id) RedcliffMap.prototype = { changeTiles: function(opacity_val), addOverlay: function(overlay), removeOverlay: function(overlay), openInfoWindow: function(type, id, latlng), highLightOverlay: function(element_ids), deHighLightOverlay: function(), setCenter: function(center, level), clearOverlays: function(), updateOverlay: function(type, id) }; function RedcliffMap(node) { var me = this; # 參考 http://code.google.com/intl/zh-TW/apis/maps/documentation/mapplets/basics.html#GMap2 # GMap2 代表 Google Map 的 map 根元件 this.gmap = new GMap2(); # 設定初始地圖中心經緯度位置 this.gmap.setCenter(new GLatLng(29.833, 113.618), 7, G_PHYSICAL_MAP); # 用 GTileLayerOverlay 來取代原始的地圖底圖 # 參考 http://code.google.com/intl/zh-TW/apis/maps/documentation/overlays.html#Tile_Overlays # 參考 http://code.google.com/intl/zh-TW/apis/maps/documentation/reference.html#GTileLayerOverlay # 建構子定義 GTileLayerOverlay(tileLayer:GTileLayer, opts?:GTileLayerOverlayOptions) this.tileLayerOverlay = new GTileLayerOverlay( # 參考 http://code.google.com/intl/zh-TW/apis/maps/documentation/reference.html#GTileLayer # 建構子定義 GTileLayer(copyrights:GCopyrightCollection, minResolution:Number, # maxResolution:Number, options?:GTileLayerOptions) # 參考 http://code.google.com/intl/zh-TW/apis/maps/documentation/reference.html#GTileLayerOptions # 這裡設定 tileUrlTemplate: URL.tile_url = http://mt.google.cn/mt?v=cnsg1.2&hl=zh-CN&x={X}&y={Y}&z={Z} # 根據 API 文件 Templates 必須是這樣: http://host/tile?x={X}&y={Y}&z={Z}.png new GTileLayer(null, null, null, { tileUrlTemplate: URL.tile_url, isPng:true, opacity:1.0 }) ); this.gmap.addOverlay(this.tileLayerOverlay); }; }}} * 關於 !LoadLocation(); 這行程式 {{{ function LoadLocation() { ### 參考 http://code.google.com/intl/zh-TW/apis/gadgets/docs/legacy/remote-content.html#Fetch_text ### _IG_FetchContent(url, func) 是一個 Mapplet 的 callback 函數,用來定義擷取 url 之後該執行哪個 func 函數 ### 這裡定義要去擷取 URL.location_url , ### 也就是 http://redcliff.googlecode.com/svn/trunk/data_tc/location.json?bpc=12191314 ### [備註] 加上 bpc= 是為了清除 google code 的 cache ,這點在程式碼裡面也有寫上註解 ### _IG_FetchContent(URL.location_url, function(data) { ### 宣告 json 變數等於 callback 函數回傳回來的 data ### 參考 http://www.w3schools.com/jsref/jsref_eval.asp ### location.json 裡的內容 ### [ {"name" : "成都" , "lat" : 30.670992 , "lng" : 104.067993 }, ... ] var json = eval(data); ### 依序把 location.json 裡的 name 跟 location (lat,lng) 設定上去 $.each(json, function(index, location) { ### var LOCATION = new Hash(); 這裡的 LOCATION 物件是一個 Hash (湊雜) ### Hash 類別必須參考原始碼中 Hash.prototype 定義了 5 個 method ### removeItem(in_key), getItem(in_key), setItem(in_key, in_value), hasItem(in_key), getLength() ### 還有 length 跟 item 兩個 field. ### 照 setItem(in_key, in_value) 語法看起來應該會產生一個 二維陣列 (Array) ### 這裡還呼叫了 Location 建構子 ### function Location(raw_location) { ### var me = this; ### this.location = raw_location; ### this.name = location.name; ### this.point = new GLatLng(raw_location.lat, raw_location.lng); ### }; ### 因此這一行的最後結果應該是類似 ### LOCATION.item["成都"].name="成都" ### LOCATION.item["成都"].point=GLatLng(30.670992,104.067993)) LOCATION.setItem(location.name, new Location(location)); }); ### 呼叫 LoadElement() 跟 LoadEvent() LoadElement(); LoadEvent(); }); }; }}} * 從 Element 的 prototype 跟建構子看來,Element 類別包含 id, element, type, event 四個標準 field,如果 JSON 型態是 Point 會兒外多出 point 跟 marker 兩個 field,如果 JSON 的型態是 line 則會多出 hidden_polyline 跟 arrow 兩個 field,而 getHiddenPolylineOverlay(points, weigth, id)、getArrowGroundOverlay(arrow_url, sw, ne)、getMarker(icon_url, point, id) 三個暫時的 inner function 是用來產生 marker, hidden_polyline, arraw 的,drawOnMap()、removeFromMap()兩個 method (其他被註解掉了) {{{ function Element(raw_element) { var me = this; this.id = raw_element.id this.element = raw_element; this.type = raw_element.type; var getHiddenPolylineOverlay = function(points, weigth, id) { var latlngs = new Array(); $.each(points, function(index, point){ latlngs.push(new GLatLng(point.lat, point.lng)); }); var polyline = new GPolygon(latlngs, "#000000", 10, 0.0); GEvent.addListener(polyline, 'click', function(latlng) { G_MAP.openInfoWindow("ELEMENT", id, latlng); }); return polyline; }; var getArrowGroundOverlay = function(arrow_url, sw, ne) { var bound = new GLatLngBounds(new GLatLng(ne.lat, ne.lng), new GLatLng(sw.lat, sw.lng)); var arrow = new GGroundOverlay(CN_BASE + 'arrow/' + arrow_url + '.png', bound); return arrow; } var getMarker = function(icon_url, point, id) { var image = CN_BASE + 'icon/' + icon_url + '.png'; var icon = new GIcon(G_DEFAULT_ICON, image); if (icon_url.length == 2) icon.iconSize = new GSize(45,32); else icon.iconSize = new GSize(25,32); var marker = new GMarker(point, {icon:icon}); GEvent.addListener(marker, 'click', function() { G_MAP.openInfoWindow("ELEMENT", id, point); }); return marker; } if (this.type == 'point') { this.point = new GLatLng(raw_element.lat, raw_element.lng); this.marker = getMarker(raw_element.pic, this.point, this.id); } else { this.hidden_polyline = getHiddenPolylineOverlay(raw_element.hot_points, C_POLYLINE_WEIGHT, this.id); this.arrow = getArrowGroundOverlay(raw_element.arrow, raw_element.arrow_points[0], raw_element.arrow_points[1]); } this.events = raw_element.event_ids; }; Element.prototype = { drawOnMap: function() { if (this.type == 'line') { G_MAP.addOverlay(this.hidden_polyline); G_MAP.addOverlay(this.arrow); } else { G_MAP.addOverlay(this.marker); } }, removeFromMap: function() { if (this.type == 'line') { G_MAP.removeOverlay(this.hidden_polyline); G_MAP.removeOverlay(this.arrow); } else { G_MAP.removeOverlay(this.marker); } } /* highLight: function() { G_MAP.removeOverlay(this.current_overlay); G_MAP.addOverlay(this.highlight_overlay); this.current_overlay = this.highlight_overlay; }, deHighLight: function() { G_MAP.removeOverlay(this.current_overlay); G_MAP.addOverlay(this.overlay); this.current_overlay = this.overlay; }, adjustZoomLevel: function() { } */ }; }}} * 先看過 Element 類別的定義,之後我們回過頭來看 !LoadElement 做些什麼 {{{ function LoadElement() { ### 擷取 http://redcliff.googlecode.com/svn/trunk/data_tc/element.json?bpc=12191328 ### element.json 的兩種型態 ### type="point" ### {"id" : 0 , "type" : "point" , "event_ids" : [0 , 2] , "people" : ["劉備" , "諸葛亮"] , ### "pic" : "G" , "lat" : 32.009524 , "lng" : 111.939524 }, ### type="line" ### {"id" : 3 , "type" : "line" , "event_ids" : [4] , "people" : ["曹操"] , ### "arrow" : "B_yx_xy" , "arrow_points" : [{"lat" : 36.115129 , "lng" : 114.326413} , ### {"lat" : 32.640746 , "lng" : 112.407843}] , "thickness" : 0.12 , ### "hot_points" : [{"lat" : 36.093 , "lng" : 114.304} , {"lat" : 35.219 , "lng" : 114.069} , ### {"lat" : 34.113 , "lng" : 113.563} , {"lat" : 33.149 , "lng" : 112.932} , ### {"lat" : 32.667 , "lng" : 112.431}] , ### "center" : {"lat" : 33.468 , "lng" : 113.167}}, _IG_FetchContent(URL.element_url, function(data) { var json = eval(data); $.each(json, function(index, element) { ### 呼叫 setItem 產生二維陣列 ### 結果應該是 ### ELEMENT.item[0].id = 0 ### ELEMENT.item[0].type = point ### ELEMENT.item[0].point = GLatLng(32.009524,111.939524); ### ELEMENT.item[0].marker = GMarker( GLatLng(32.009524,111.939524), ### GIcon(G_DEFAULT_ICON, http://www.google.cn/staticcn/chibi/icon/G.png) ### 這個 marker 還註冊了 Click 的事件處理函式是去呼叫 G_MAP.openInfoWindow("ELEMENT", 0, GLatLng(32.009524,111.939524)); ELEMENT.setItem(element.id, new Element(element)); }); ## 把 LOAD_STATES 加 1 - 此時 LOAD_STATES = 1 LoadDone(); }) }; function LoadEvent() { ### event.json 的範例 ### {"id" : 0 , "name" : "諸葛亮寓居隆中" , "people" : ["諸葛亮"] , "element_ids" : [0] , ### "search" : ["諸葛亮" , "梁父吟" , "隆中"] , "time" : "建安十二年" , "ad" : "207年" , ### "desc" : "諸葛亮早年與弟弟諸葛均在南陽隆中躬耕隴畝。...略..." , ### "popup" : {"lat" : 32.009524 , "lng" : 111.939524}}, _IG_FetchContent(URL.event_url, function(data) { var json = eval(data); $.each(json, function(index, event) { ### 呼叫 setItem 產生二維陣列 ### 結果應該是 ### EVENT.item[0].id = 0 ### EVENT.item[0].name = "諸葛亮寓居隆中" ### EVENT.item[0].element_ids = [0] ### EVENT.item[0].people = ["諸葛亮"] ### EVENT.item[0].search = ["諸葛亮" , "梁父吟" , "隆中"] ### EVENT.item[0].time = "建安十二年" ### EVENT.item[0].time_ad = "207年" ### EVENT.item[0].desc = "諸葛亮早年與弟弟諸葛均在南陽隆中躬耕隴畝。...略..." ### EVENT.item[0].point = GLatLng(32.009524,111.939524) EVENT.setItem(event.id, new Event(event)); }); LoadBigEvent(); LoadPeople(); }); }; function LoadBigEvent() { ### event.json 的範例 ### {"id" : 0 , "name" : "三顧茅蘆" , "event_ids" : [0 , 1 , 2] , "element_ids" : [0 , 1] , ### "desc" : "諸葛亮在隆中躬耕,...略" , "time" : "建安十二年" , "ad" : "207年" , ### "center" : {"lat" : 32.010 , "lng" : 111.939} ,"pic" : "G" , "images" : [] }, _IG_FetchContent(URL.big_event_url, function(data) { var json = eval(data); $.each(json, function(index, big_event) { ### 呼叫 setItem 產生二維陣列 ### 結果應該是 ### BIG_EVENT.item[0].id = 0 ### BIG_EVENT.item[0].name = "三顧茅蘆" ### BIG_EVENT.item[0].element_ids = [0 , 1] ### BIG_EVENT.item[0].event_ids = [0 , 1 , 2] ### BIG_EVENT.item[0].time = "建安十二年" ### BIG_EVENT.item[0].time_ad = "207年" ### BIG_EVENT.item[0].desc = "諸葛亮在隆中躬耕,...略" ### BIG_EVENT.item[0].pic = "G" ### BIG_EVENT.item[0].is_details_shown = false; ### BIG_EVENT.item[0].details = null; ### BIG_EVENT.item[0].images = [] ### BIG_EVENT.item[0].center = GLatLng(32.010, 111.939) ### BIG_EVENT.item[0].node =
建安十二年

諸葛亮在隆中躬耕,自比管仲樂毅。劉備求賢若渴,得司馬徽、徐庶推薦,三顧茅廬,終於見到諸葛亮。諸葛亮分析天下大勢,定下三分天下之計。

建安十二年
建安十二年
建安十二年
### BIG_EVENT.item[0].is_shown = true; BIG_EVENT.setItem(big_event.id, new BigEvent(big_event)); }); ## 把 LOAD_STATES 加 1 - 此時 LOAD_STATES = 2 LoadDone(); }); }; }}} * 這裡補記一下 !BigEvent 裡面一段神奇的 Code ,從這段程式碼可以感受到 jQuery 可以在 Runtime 把 click 事件註冊上去,這真是一個神奇的事情啊!! {{{ var event_link = $('' + me.name + ''); ### 這裡其實只是產生了 DOM 裡面的一段如 三顧茅蘆 的程式碼 ### 透過 Firebug 去觀察 DOM 也只看到這樣的結果 ### 但是因為 DOM 是由 jQuery 產生的,因此底下這幾行所定義的 click 事件處理函式 ### 會因為不同的連結而有所不同 event_link.click(function(){ if (me.is_details_shown) { me.hideDetails(); } else { me.showDetails(); } ### 呼叫 Redcliff.updateOverlay(type, id) ### 一開始會先用 Redcliff.clearOverlays() 把目前的 Overlay 清掉 ### 如果 type = 'E' , 從 BIG_EVENT 讀出對應的 event_id 跟 element_id ### 如果 type = 'P' , 從 PEOPLE 讀出對應的 event_id 跟 element_id G_MAP.updateOverlay('E', me.id); ### 呼叫 Redcliff.setCenter(center, level) ### 也就是把目前 Google Map 的中心位置設到 GLatLng(32.010, 111.939),比例尺設為 8 G_MAP.setCenter(me.center, 8); ### 參考 http://www.google.com.tw/intl/zh-TW/apis/gadgets/tools.html#Analytics_Gadgets ### 這是為了作 Google Analytics 網址點選統計分析用的 _IG_Analytics(UAACCT, '/click/eventLink/' + me.name); return false; }); link_cell.append(event_link); }}} * 繼續看 !LoadPeople 在做什麼 {{{ function LoadPeople() { ### people.json 的範例 ### {"name" : "曹操" , "zi" : "孟德" , "hao" : null , ### "event_ids" : [3 , 4 , 9 , 10 , 13 , 17 , 19 , 21 , 22 , 23 , 24 ] , ### "element_ids" : [2 , 3 , 9 , 10 , 11 , 14 , 23 , 25 , 29 , 30 , 32 , 34] , ### "birth" : 155 , "death" : 220 , "birthplace" : "沛國譙縣" , "desc" : "曹操生於官宦世家...略" , ### "kindom" : "魏" , "wiki" : "http://zh.wikipedia.org/w/index.php?title=%E6%9B%B9%E6%93%8D&variant=zh-tw" , ### "pic" : "http://laiba.tianya.cn/laiba/images/274/12295005301924842653/A/1/o.png" , ### "center" : {"lat" : 31.224 , "lng" : 112.818}}, _IG_FetchContent(URL.people_url, function(data) { var json = eval(data); $.each(json, function(index, raw_people) { var people = new People(raw_people) ### 呼叫 setItem 產生二維陣列 ### 結果應該是 ### PEOPLE.item["曹操"].id = "曹操" ### PEOPLE.item["曹操"].name = "曹操" ### PEOPLE.item["曹操"].nick = "孟德" ### PEOPLE.item["曹操"].birth = 155 ### PEOPLE.item["曹操"].death = 220 ### PEOPLE.item["曹操"].birthplace = "沛國譙縣" ### PEOPLE.item["曹操"].desc = "曹操生於官宦世家,..略..." ### PEOPLE.item["曹操"].kingdom = "魏" ### PEOPLE.item["曹操"].event_ids = [3 , 4 , 9 , 10 , 13 , 17 , 19 , 21 , 22 , 23 , 24 ] ### PEOPLE.item["曹操"].element_ids = [2 , 3 , 9 , 10 , 11 , 14 , 23 , 25 , 29 , 30 , 32 , 34] ### PEOPLE.item["曹操"].pic = "http://laiba.tianya.cn/laiba/images/274/12295005301924842653/A/1/o.png" ### PEOPLE.item["曹操"].digest = null ### PEOPLE.item["曹操"].event = null; ### PEOPLE.item["曹操"].center = GLatLng(31.224 , 112.818) ### PEOPLE.item["曹操"].node = PeopleNode($('#character_list'), this); ## 在 character_list 底下加入 DOM ### PEOPLE.item["曹操"].is_shown = true; PEOPLE.setItem(people.name, people); PEOPLE_ARRAY.push(people); }); ## 把 LOAD_STATES 加 1 - 此時 LOAD_STATES = 3 LoadDone(); }); }; }}} [[Image(RedcliffMap.png)]]