Skip to main content

สร้างแผนที่ Leaflet บน APEX

             สวัสดีครับ วันนี้เราจะมาลองใส่แผนที่ ลงในหน้าจอ ORACLE APEX กัน อันที่จริงแล้ว APEX ได้เตรียมการแสดงผลแบบแผนที่ไว้ให้ เป็นการแสดงผลแบบ Flash ลักษณะแผนที่มีรายละเอียดน้อยและมีรูปแบบค่อนข้างตายตัว ดังรูป

   
                 เราจึงนำเสนอ การใช้แผนที่ Leaflet แทนของ Oracle ที่ให้มากับ APEX ก่อนอื่นมาทำความรู้จักกับ Leaflet ซักนิด Leaflet คือ open-source JavaScript library สำหรับเขียนโปรแกรมแสดงแผนที่ เมื่อเป็น open-source เราสามารถ download ตัว library มาใช้งานได้เลย  การใช้งานต้องมีการเขียนโปรแกรม JavaScript อยู่บ้าง
                 ในบทความนี้เราจะทำการแสดงยอดขายสินค้าในรูปแบบของแผนที่ โดยแสดงเป็นสีในแต่ละเขตของกรุงเทพฯ  (Choropleth Map)  สีอ่อนหมายถึงยอดขายที่มีจำนวนน้อย ไล่ไปจนสีเข้มหมายถึงยอดขายที่มีจำนวนมาก
                   เรามาเริ่มจากการสร้าง Classic Report เพื่อแสดงผลยอดขายในแต่ละเขตก่อน โดยข้อมูลนี้จะเก็บใน Table: PRODUCT_SALES มีข้อมูล ดังรูป


ให้ Create Blank Page จากนั้นทำการสร้าง Classic Report Region โดยทำการกรอก source properties เป็น

1:  select * from product_sales   

ดังรูป



ตอนนี้เราได้รายงานแสดงยอดขายในแต่ละเขตแล้ว

                ก่อนที่จะใส่แผนที่ มีขั้นตอนที่ต้องทำก่อนคือเราจะนำข้อมูลยอดขายแต่ละเขตที่อยู่ใน  Table: PRODUCT_SALES นำมาทำเป็น JSON Object  ก่อนเนื่องจากเราไม่สามารถใช้ JavaScript ดึงข้อมูลจากฐานข้อมูลโดยตรงได้ (การแสดงแผนที่เราต้องใช้ JavaScript)  จึงใช้วิธีสร้างข้อมูลรูปแบบ JSON ด้วย PL/SQL จากนั้นเอาไปเก็บไว้ที่ Text filed ที่ทำการ hidden เอาไว้ 
                ให้สร้าง Static Content  Region แล้วสร้าง Hidden Item ให้ชื่อว่า P6_BUFF_JSON อยู่บน Region ที่เราเพิ่งสร้าง ดังรูป


หลังจากได้พื้นที่ใช้เก็บ JSON แล้ว เราก็จะมาเขียนโปรแกรมสร้าง JSON กัน ด้วยการ Create process แล้วเลือกสถานะแบบ Before Header นำ Code ด้านล่าง นำมาใส่ใน Properties -> Source -> PL/SQL Code

1:  declare   
2:     buff_json VARCHAR2(32000);  
3:     i PLS_INTEGER;  
4:  begin  
5:      i := 0;  
6:      buff_json := '{"bangkok": [';  
7:      for c1 in (select id,sales_amount from product_sales) loop  
8:        if i > 0 then  
9:         buff_json := buff_json||',';  
10:        end if;  
11:        buff_json := buff_json||'{"id": '  || c1.ID  
12:                   ||',"sale": ' || c1.SALES_AMOUNT  
13:                   ||'}';        
14:        i := i+1;               
15:      end loop;  
16:      buff_json := buff_json|| ']}';  
17:      :P6_BUFF_JSON := buff_json;  
18:  end;    

วาง Code ใน properties ดังรูป


เมื่อ Run Page โปรแกรมจะสร้างข้อมูลยอดขายแบบ JSON เก็บไว้ใน item: P6_BUFF_JSON

                 ขั้นตอนต่อมาใน Region ที่เพิ่งสร้าง เราจะทำการเตรียมพื้นที่การแสดงผลแผนที่ ด้วยการกำหนดขอบเขตและชื่อด้วย Tag: div จาก Code ด้านล่างนี้

1:  <div id="map" style="width:100%;height:430px;"></div>  

ใส่ใน Properties -> Static Region -> Source Text ดังรูป


ตอนนี้เราเตรียมพื้นที่รองรับแผนที่ ให้ชื่อว่า "map" แล้ว

                 ขั้นตอนต่อมาก่อนที่จะใส่แผนที่ ให้ Upload ไฟล์ Leaflet JS Library และ CSS เก็บไว้ใน Application ของเราก่อน (ขั้นตอนนี้ไม่ต้องทำก็ได้ ถ้าเราใช้การเรียก Code แบบ Reference แต่ทำเพื่อให้รู้ว่าเอา Code มาเก็บที่ Sever ของเราก็ได้ )  ก่อนอื่นเราต้องไป Download Leaflet Library จาก Web: http://leafletjs.com/  ได้ไฟล์มาสองไฟล์ คือ leaftlet.js กับ leaflet.css จากนั้นให้ Upload ไฟล์ทั้งสอง โดยไปที่ Shared Components -> Files -> Static Application Files
อย่าลืมใส่ Directory ด้วยครับ (ไฟล์ js ใส่ Directory: js ,ไฟล์ css ใส่ Directory: css) ดังรูป



                อีกไฟล์ที่จะต้อง Upload เป็นข้อมูล GeoJSON ของเขตกรุงเทพฯทั้ง 50 เขต ข้อมูลเขตอยู่ในรูป Polygon แต่ละเขต มี key ที่ชื่อ id ซึ่งตรงกับข้อมูลยอดขายที่เราทำเป็น JSON ก่อนหน้านี้ ข้อมูล GeoJSON นี้ถูกสร้างมาในรูปตัวแปลที่ชื่อว่า geojson ดูจาก Code ด้านล่างนี้ (แสดงแบบย่อนะครับ ฉบับเต็ม 50 เขตยาวมาก)

1:  var geojson={  
2:  "type": "FeatureCollection",  
3:  "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },                                          
4:  "features": [  
5:  { "type": "Feature", "properties": { "id":1,"Name": "เขตพระนคร", "value":0}, "geometry": { "type": "Polygon", "coordinates": [ [ [ 100.49705, 13.768606, 0.0 ], [ 100.494947, 13.765605, 0.0 ], [ 100.489841, 13.760895, 0.0 ], [ 100.488167, 13.75781, 0.0 ], [ 100.487523, 13.752058, 0.0 ], [ 100.488467, 13.747472, 0.0 ], [ 100.490742, 13.743387, 0.0 ], [ 100.494733, 13.740219, 0.0 ], [ 100.499969, 13.73876, 0.0 ], [ 100.500398, 13.740928, 0.0 ], [ 100.503531, 13.745013, 0.0 ], [ 100.504518, 13.748765, 0.0 ], [ 100.506148, 13.755643, 0.0 ], [ 100.505676, 13.756476, 0.0 ], [ 100.509152, 13.763479, 0.0 ], [ 100.50559, 13.767647, 0.0 ], [ 100.502715, 13.770899, 0.0 ], [ 100.498896, 13.772691, 0.0 ], [ 100.49705, 13.768606, 0.0 ] ] ] } },  
6:  { "type": "Feature", "properties": { "id":2,"Name": "เขตดุสิต", "value":0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 100.498896, 13.772816, 0.0 ], [ 100.50396, 13.770148, 0.0 ], [ 100.508852, 13.763813, 0.0 ], [ 100.516319, 13.757143, 0.0 ], [ 100.517178, 13.754809, 0.0 ], [ 100.517435, 13.753142, 0.0 ], [ 100.518894, 13.752641, 0.0 ], [ 100.537348, 13.797407, 0.0 ], [ 100.529022, 13.79899, 0.0 ], [ 100.526447, 13.798574, 0.0 ], [ 100.524817, 13.799574, 0.0 ], [ 100.516148, 13.800324, 0.0 ], [ 100.5128, 13.793406, 0.0 ], [ 100.505934, 13.787071, 0.0 ], [ 100.503273, 13.782986, 0.0 ], [ 100.498896, 13.772816, 0.0 ] ] ] } },  
7:  { "type": "Feature", "properties": { "id":3,"Name": "เขตป้อมปราบศัตรูพ่าย", "value":0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 100.504003, 13.74668, 0.0 ], [ 100.515718, 13.737676, 0.0 ], [ 100.515676, 13.742428, 0.0 ], [ 100.517006, 13.749557, 0.0 ], [ 100.517306, 13.752725, 0.0 ], [ 100.517242, 13.75433, 0.0 ], [ 100.516706, 13.755934, 0.0 ], [ 100.516298, 13.757102, 0.0 ], [ 100.509131, 13.763458, 0.0 ], [ 100.505655, 13.756497, 0.0 ], [ 100.506191, 13.755872, 0.0 ], [ 100.50514, 13.751307, 0.0 ], [ 100.504003, 13.74668, 0.0 ] ] ] } },  
8:  ....  
9:  { "type": "Feature", "properties": { "id":48,"Name": "เขตราษฎร์บูรณะ", "value":0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 100.517178, 13.661417, 0.0 ], [ 100.517864, 13.667171, 0.0 ], [ 100.520353, 13.668422, 0.0 ], [ 100.520954, 13.669507, 0.0 ], [ 100.522242, 13.675094, 0.0 ], [ 100.520954, 13.675428, 0.0 ], [ 100.523272, 13.67768, 0.0 ], [ 100.517521, 13.68285, 0.0 ], [ 100.510225, 13.685018, 0.0 ], [ 100.50293, 13.686353, 0.0 ], [ 100.497351, 13.687687, 0.0 ], [ 100.491943, 13.691523, 0.0 ], [ 100.490055, 13.695609, 0.0 ], [ 100.483103, 13.694692, 0.0 ], [ 100.481129, 13.690022, 0.0 ], [ 100.488682, 13.684185, 0.0 ], [ 100.487738, 13.681766, 0.0 ], [ 100.488682, 13.679097, 0.0 ], [ 100.4807, 13.668756, 0.0 ], [ 100.482502, 13.664836, 0.0 ], [ 100.475979, 13.663585, 0.0 ], [ 100.475893, 13.66175, 0.0 ], [ 100.482502, 13.663001, 0.0 ], [ 100.485506, 13.657247, 0.0 ], [ 100.492373, 13.655829, 0.0 ], [ 100.49366, 13.65708, 0.0 ], [ 100.496578, 13.656079, 0.0 ], [ 100.500441, 13.652409, 0.0 ], [ 100.504818, 13.659916, 0.0 ], [ 100.506449, 13.659332, 0.0 ], [ 100.509109, 13.662668, 0.0 ], [ 100.513573, 13.66125, 0.0 ], [ 100.517178, 13.661417, 0.0 ] ] ] } },  
10:  { "type": "Feature", "properties": { "id":49,"Name": "เขตภาษีเจริญ", "value":0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 100.437012, 13.693858, 0.0 ], [ 100.433235, 13.700362, 0.0 ], [ 100.439072, 13.709201, 0.0 ], [ 100.447826, 13.7062, 0.0 ], [ 100.457783, 13.713204, 0.0 ], [ 100.462418, 13.714371, 0.0 ], [ 100.462933, 13.717873, 0.0 ], [ 100.468941, 13.715872, 0.0 ], [ 100.468941, 13.717373, 0.0 ], [ 100.471344, 13.722543, 0.0 ], [ 100.466366, 13.726378, 0.0 ], [ 100.462589, 13.733549, 0.0 ], [ 100.461388, 13.737884, 0.0 ], [ 100.461559, 13.744388, 0.0 ], [ 100.458126, 13.743387, 0.0 ], [ 100.456066, 13.741553, 0.0 ], [ 100.454521, 13.74222, 0.0 ], [ 100.4492, 13.739885, 0.0 ], [ 100.433407, 13.740052, 0.0 ], [ 100.431004, 13.742387, 0.0 ], [ 100.426884, 13.742553, 0.0 ], [ 100.420532, 13.744554, 0.0 ], [ 100.417957, 13.747222, 0.0 ], [ 100.412979, 13.747889, 0.0 ], [ 100.414867, 13.739385, 0.0 ], [ 100.416412, 13.738218, 0.0 ], [ 100.414867, 13.73605, 0.0 ], [ 100.417614, 13.734883, 0.0 ], [ 100.418644, 13.731214, 0.0 ], [ 100.425167, 13.732548, 0.0 ], [ 100.426369, 13.697027, 0.0 ], [ 100.427914, 13.688354, 0.0 ], [ 100.437012, 13.693858, 0.0 ] ] ] } },  
11:  { "type": "Feature", "properties": { "id":50,"Name": "เขตบางแค", "value":0 }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 100.353413, 13.752391, 0.0 ], [ 100.358906, 13.739552, 0.0 ], [ 100.380878, 13.692357, 0.0 ], [ 100.385342, 13.675678, 0.0 ], [ 100.383797, 13.671508, 0.0 ], [ 100.387745, 13.670341, 0.0 ], [ 100.387917, 13.668005, 0.0 ], [ 100.395641, 13.669673, 0.0 ], [ 100.428257, 13.688521, 0.0 ], [ 100.426369, 13.69636, 0.0 ], [ 100.425167, 13.732548, 0.0 ], [ 100.418816, 13.730714, 0.0 ], [ 100.417786, 13.734383, 0.0 ], [ 100.414696, 13.736384, 0.0 ], [ 100.416756, 13.738051, 0.0 ], [ 100.414867, 13.739719, 0.0 ], [ 100.412636, 13.748056, 0.0 ], [ 100.405769, 13.750057, 0.0 ], [ 100.364227, 13.754225, 0.0 ], [ 100.353413, 13.752391, 0.0 ] ] ] } }  
12:  ]  
13:  };  

                      ให้สังเกตว่ามี attribute ชื่อ id คือ key ของเขต และ value คือค่าที่เป็นสีในแต่ละเขต เริ่มต้นเป็น 0 ทั้งหมด ไฟล์นี้ผมตั้งชื่อว่า bkk.js  ให้ Upload ใน Directory: js

                       มาถึงขั้นตอนสุดท้ายแล้ว  เรามาใส่ Code แผนที่กันใน Properties: JavaScript และ CSS ของ Object Page ดูจากรูป


ใน Properties: CSS -> File URLs ให้ใส่

1:  #APP_IMAGES#css/leaflet.css  

กรณี อ้างอิงแบบ Reference ให้ใส่

1:  <link rel="stylesheet" href="https://npmcdn.com/leaflet@0.7.7/dist/leaflet.css" />  

ใน Properties: JavaScript -> File URLs ให้ใส่

1:  #APP_IMAGES#js/leaflet.js  
2:  #APP_IMAGES#js/bkk.js  

กรณี อ้างอิงแบบ Reference ให้ใส่

1:  <script src="https://npmcdn.com/leaflet@0.7.7/dist/leaflet.js"></script>  
2:   #APP_IMAGES#js/bkk.js   

ใน Properties: JavaScript -> Function and Global Variable Declaration ให้ใส่

1:  function getColor(d) {  
2:                 return d > 1000 ? '#800026' :  
3:                     d > 500 ? '#BD0026' :  
4:                     d > 200 ? '#E31A1C' :  
5:                     d > 100 ? '#FC4E2A' :  
6:                     d > 50  ? '#FD8D3C' :  
7:                     d > 20  ? '#FEB24C' :  
8:                     d > 10  ? '#FED976' :  
9:                          '#FFEDA0';  
10:            }  
11:  function style(feature) {  
12:                 return {  
13:                      weight: 2,  
14:                      opacity: 1,  
15:                      color: 'white',  
16:                      dashArray: '3',  
17:                      fillOpacity: 0.7,  
18:                      fillColor: getColor(feature.properties.value)  
19:                 };  
20:            }  

Function getColor เป็นการกำหนดสีเข้มอ่อนตามค่าของ parameter ที่ส่งมา, Function style เป็นการบอกให้ แผนที่ระบายสีในแต่ละเขตตามค่าที่ได้จาก geojson

 ใน Properties: JavaScript -> Execute when Page Loads ให้ใส่

1:  var json = JSON.parse($v('P6_BUFF_JSON'));  
2:  var bkk = json.bangkok;  
3:  for (var i =0; i<geojson.features.length; i++){  
4:       geojson.features[i].properties.value = bkk[i].sale;  
5:  }  
6:  var map = L.map('map', {  
7:                center: [13.814605, 100.489062],   
8:                zoom: 10  
9:                });  
10:  L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);  
11:  var geojsonMap = L.geoJson(geojson, {  
12:                            style: style                 
13:                           }).addTo(map);  

ในบรรทัดที่ 1

 var json = JSON.parse($v('P6_BUFF_JSON'));  

บรรทัดนี้เป็นการนำข้อมูล JSON ที่เราสร้างในขั้นตอนที่แล้วที่เก็บไว้ใน item: P6_BUFF_JSON มาแปลงเป็น JSON Object ด้วย Function  JSON.parse

ในบรรทัดที่ 3-5

for (var i =0; i<geojson.features.length; i++){  
      geojson.features[i].properties.value = bkk[i].sale;  
}  

เป็นการกำหนดค่า value ใน geojson ด้วยค่ายอดขาย (sale) ที่ดึงมาจากฐานข้อมูล

ในบรรทัดที่ 6-13
เป็นการให้แสดงแผนที่ ตามค่าที่ระบุในข้อมูล geojson และแสดงในรูปแบบตาม style ที่กำหนด

                      เมื่อเขียนโปรแกรมเสร็จ ทำการทำลอง Run Page ก็จะได้ผลลัพธ์ดังรูป


                    เราสามารถนำการวิธีนี้ไปปรับเปลี่ยนแสดงแผนที่เป็นจุด Marker หรือ Line ก็ได้ครับ ดูตัวอย่างเพิ่มเติมจาก Web: Leaflet ได้เลยครับ จะเห็นว่าสามารถแสดงผลได้ยังกับ BI ครับ


Resource: Expert Oracle Application Express,2nd Edition, Apress

Comments

Post a Comment

Popular posts from this blog

Oracle APEX คือ อะไร

สำหรับท่านที่เป็นโปรแกรมเมอร์ มาหลายปีดีดัก คงต้องเคยผ่านการเขียนโปรแกรม ด้วยภาษาต่างๆมามาก แต่ละภาษาก็มีเครื่องมือและคอมไพเลอร์ที่ได้รับความนิยมต่างๆกัน  สำหรับผม เริ่มจาก PASCAL แน่นอน คอมไพเลอร์สุดฮิตที่ทุกคนต้องใช้ (เพราะมีใช้อยู่ตัวเดียวบน PC) ก็คือ TURBO PASCAL อันที่จริงก่อนหน้าก็เขียน ทั้ง C และ COBOL บน DEC  ทูลที่ใช้ก็ VI และ command line Compiler ที่ประทับใจคือ rmcobol วิธีคอมไพล์ก็แสนจะง่าย คือ rmcobol ตามด้วยชื่อ file แต่ดันเว้นวรรคผิด เป็น rm cobol file ครับโปรเจคที่ทำเกือบครึ่งเทอมหายวับไปกับตา ที่พล่ามมานี่คงพอเดาอายุคนเขียนได้นะครับ เอาละมาต่อกันดีกว่า  ยุคต่อมาก็ Delphi ของเจ้า Borland ตอนนั้นดังสูสีกับ Visual Basic ของเจ้าสัว Bill Gates เค้าละ แต่ที่เป็นทูลของเจ้าใหญ่ถึงจะไม่เป็น Mass product แต่เนื่องจาก Data Base เขาเป็นที่หนึ่งจึงมีผู้ใช้พอสมควร นั้นก็คือ Oracle Developer  ยุคนี้น่าเป็นยุค Client - Server ทำมาหากินกับเจ้าเครื่องมือเหล่านี้ก็หลายปี และแล้วอินเตอร์เนตก็มา เป็นความสนุก :p ของคนอาชีพนี้ที่ถูกสาปให้ต้องเรียนของใหม่ตลอดเวลา ได้เจ้า J...

การติดตั้ง ORACLE APEX (1/4)

      สวัสดีครับ หายไปนานเลยครับ  ภาระกิจรัดตัวต้องไปช่วยลุงกำนันครับ ชาติต้องมาก่อนอื่นใดครับ บทความนี้เราจะมาเล่าถึงการนำเอา ORACLE APEX มาใช้ในฐานข้อมูลของเราเอง จากบทความที่แล้วที่เราได้ทดลองใช้ ORACLE APEX บน Example Cloud กันบ้างแล้ว ตอนนี้เราจะมาทดลอง Install ลงบนเครื่องเราเองใช้ในหน่วยงานแบบไม่ต้องมี Internet และไม่ต้องไปเสียเงินใช้บน ORACLE Cloud :p ตั้งใจว่าจะไม่เป็นวิชาการมากนักเอาเป็นแบบเน้นทดลองกันเองเลยตามขั้นตอน แต่ก่อนอื่นก็ต้องทำความเข้าใจกันบ้างเพื่อว่าเวลา Install จะได้ไม่งงว่ากำลังทำอะไร ครับ ORACLE Application Express Engine เป็น โปรแกรม Oracle Package ที่ต้องการติดตั้งลงในฐานข้อมูล ORACLE เท่านั้นและแสดงผลเป็น Web Application และการติดตั้งก็มีสองวิธีคือ ใช้ Web Server ของ Database เอง หรือใช้ Web Listener อื่นร่วมด้วยก็ได้  ซื่งบทความนี้จะเสนอเฉพาะวิธีที่สองเท่านั้นครับ เพราะในหน่วยงานส่วนใหญ่น่าจะใช้เป็น แบบ Multi-Tier มากกว่า ภาพประกอบแบบ การใช้ Listener ของ database เองเลยที่เรียกว่า Oracle XML DB Protocol Server with the embedded P...

การใช้งาน APEX กับ Bootstrap Theme

            ในปัจจุปันขณะที่กำลังเขียนบทความนี้ Oracle ได้ออก ORACLE APEX 5 ใน version  Early Adopter 3 แล้ว และคาดว่าจะเป็น version Production ในปีนี้ Version นี้มีการเปลี่ยนแปลงจาก version 4 เป็นอย่างมาก ไม่ว่าจะเป็นเรื่องเครื่องมือสร้างหน้าจอ (Page Designer) ตัวใหม่ที่สะดวกขึ้นดูเหมือนโปรแกรมประเภท visual design ของโปรแกรม desktop ในอดีต ,การปรับเปลี่ยน UI ของตัว APEX เองให้ดูทันสมัยเป็น responsive design และการ support การสร้าง mobile Web ที่ดีขึ้น แต่สิ่งที่ดูน่าตื่นเต้น และเป็นสิ่งที่อาจจะนำความน่าสนใจมาสู่ ORACLE APEX ของนักพัฒนาเพิ่มขึ้น ก็คือการออกแบบ Theme แบบใหม่ที่เรียกว่า Universal Theme ซึ่ง Theme แบบใหม่นี้หน้าตาออกมาทันสมัยมาก เทียบได้กับ font-end framework ดังๆเช่น Ext JS หรือ Bootstrap ลองคิดดูว่าเราสามารถสร้าง Web App หน้าตาทันสมัย แบบ Bootstrap โดยที่แทบจะไม่ต้องเขียนโปรแกรม หรือใช้ framework แบบ Laravel เลย เพียง Click  next ไม่กี่ครั้งก็หน้าจอที่ทำงานได้จริงแล้ว              ...