สวัสดีครับ วันนี้เราจะมาลองใส่แผนที่ ลงในหน้าจอ 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 มีข้อมูล ดังรูป
ในบทความนี้เราจะทำการแสดงยอดขายสินค้าในรูปแบบของแผนที่ โดยแสดงเป็นสีในแต่ละเขตของกรุงเทพฯ (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 ที่เราเพิ่งสร้าง ดังรูป
ก่อนที่จะใส่แผนที่ มีขั้นตอนที่ต้องทำก่อนคือเราจะนำข้อมูลยอดขายแต่ละเขตที่อยู่ใน 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
ขอบคุณครับ
ReplyDelete