วิธีสร้างแผนที่ด้วย React และ Leaflet
เผยแพร่แล้ว: 2022-03-10การรับข้อมูลจากไฟล์ CSV หรือ JSON ไม่เพียงแต่ซับซ้อน แต่ยังเป็นเรื่องน่าเบื่ออีกด้วย การแสดงข้อมูลเดียวกันในรูปแบบของเครื่องช่วยการมองเห็นทำได้ง่ายกว่า ในบทความนี้ เราจะนำเสนอสถานที่เกิดเหตุเพลิงไหม้ที่ไม่ใช่ทางการแพทย์ที่ SF Fire Department ตอบสนองบนแผนที่
สำหรับบทช่วยสอนนี้ เราจะใช้เครื่องมือต่อไปนี้:
- แผ่นพับ
ไลบรารี JavaScript สำหรับแผนที่แบบโต้ตอบ - ปฏิกิริยา
ไลบรารี JavaScript สำหรับสร้างอินเทอร์เฟซผู้ใช้ - React-แผ่นพับ
ส่วนประกอบที่ทำปฏิกิริยาสำหรับแผนที่แผ่นพับ
ใบปลิวคืออะไร?
Leaflet.js อยู่ที่ประมาณ 27,000 ดวง เป็นหนึ่งในไลบรารี JavaScript แบบโอเพนซอร์สชั้นนำสำหรับแผนที่เชิงโต้ตอบที่เหมาะกับอุปกรณ์พกพา ใช้ประโยชน์จาก HTML5 และ CSS3 บนเบราว์เซอร์สมัยใหม่ในขณะที่สามารถเข้าถึงได้จากเบราว์เซอร์รุ่นเก่าด้วย ทั้งหมดนี้สนับสนุนแพลตฟอร์มเดสก์ท็อปและมือถือหลักทั้งหมด
แผ่นพับมีน้ำหนักประมาณ 38KB และใช้งานได้ดีกับสิ่งพื้นฐาน สำหรับส่วนขยายเพิ่มเติม สามารถขยายได้ด้วยปลั๊กอินจำนวนมาก
หนังสือพิมพ์จำนวนมาก รวมทั้ง NPR, Washington Post, Boston Globe และอื่นๆ และองค์กรอื่นๆ ใช้ Leaflet สำหรับโครงการข้อมูลเชิงลึก
ตัวอย่างเช่น San Francisco Chronicle ได้ทำโครงการที่เรียกว่า California Fire tracker ซึ่งเป็นแผนที่แบบโต้ตอบที่ให้ข้อมูลเกี่ยวกับไฟป่าที่ลุกไหม้ทั่วแคลิฟอร์เนียโดยใช้ Leaflet พวกเขาไม่เพียงแต่ระบุที่มาของไฟเท่านั้น แต่ยังแสดงให้เราเห็นถึงวิถีของมันด้วย
เนื่องจากนี่คือบทแนะนำเบื้องต้น เราจึงจะทำเครื่องหมายเฉพาะตำแหน่งของเหตุการณ์ไฟไหม้และแสดงรายละเอียดบางอย่างเกี่ยวกับสถานที่นั้น
ก่อนเข้าสู่ React เรามาทำความเข้าใจพื้นฐานของ Leaflet กันก่อน สำหรับสิ่งนี้ เราจะสร้างตัวอย่างง่ายๆ ที่เราจะตั้งค่าแผนที่ Leaflet การทำงานกับเครื่องหมาย และป๊อปอัป
ขั้นแรก ให้สร้างไฟล์ index.html และ app.js ในโฟลเดอร์ /project
และเชื่อมโยงไฟล์หลังกับไฟล์ index.html ของเรา
ในการเริ่มต้นใช้งาน Leaflet เราจำเป็นต้องเชื่อมโยง Leaflet CSS และ Leaflet JS ในแท็กส่วนหัวของเรา สิ่งหนึ่งที่ต้องจำไว้คือ Leaflet CSS มาก่อน Leaflet JS แค่นั้นแหละสำหรับ Leaflet
มีอีกสิ่งหนึ่งที่เราต้องเพิ่มลงในไฟล์ index.html — คอนเทนเนอร์ที่จะเก็บแผนที่ของเรา
<div></div>
ก่อนที่เราจะลืม ให้ความสูงกับ div ของเราก่อน
#mapid { height: 1000px; }
ตอนนี้มาถึงส่วนที่สนุก ไม่ว่าคุณจะตัดสินใจสร้างไฟล์ JavaScript ใหม่หรือดำเนินการต่อในแท็กสคริปต์ ตรวจสอบให้แน่ใจว่าได้เพิ่ม <div id="mapid">
ลงใน dom ก่อนเรียก L.map('mapid')
คุณอาจจะถามว่า “แต่ทำไม” นั่นเป็นเพราะมันจะทำให้คุณมีข้อผิดพลาดหากคุณผูกแผนที่กับคอนเทนเนอร์ที่ยังไม่มีอยู่
Uncaught Error: Map container not found
การสร้างแผนที่
ตอนนี้เข้าสู่ส่วนที่สนุก ในการเริ่มต้นแผนที่ เราส่ง div ของเราไปที่ L.map()
พร้อมตัวเลือกบางอย่าง
const myMap = L.map('mapid', { center: [37.7749, -122.4194], zoom: 13 })
ไปทีละขั้นตอนเพื่อทำความเข้าใจกับสิ่งที่เกิดขึ้น เราใช้คลาส Map ของ Leaflet API เพื่อสร้างแผนที่บนหน้า เราส่งผ่านพารามิเตอร์สองตัวไปยังคลาสนี้:
- เราส่งผ่านตัวแปรสตริงที่แสดงถึง
DOM
ID - ตัวเลือกอ็อบเจกต์ตามตัวอักษรพร้อมตัวเลือกแผนที่
มีตัวเลือกมากมายที่เราสามารถส่งไปยังชั้นเรียนของเราได้ แต่สองตัวเลือกหลักคือศูนย์กลางและซูม ศูนย์กำหนดศูนย์กลางทางภูมิศาสตร์เริ่มต้นของแผนที่ในขณะที่การซูมระบุระดับการซูมแผนที่เริ่มต้น ทั้งคู่ไม่ได้กำหนดไว้โดยค่าเริ่มต้น
สำหรับศูนย์ เราผ่านในพิกัดของซานฟรานซิสโก มีสถานที่มากมายที่เราสามารถดำเนินการไปข้างหน้าและย้อนกลับ geocoding แต่สำหรับการค้นหาพื้นฐานเช่นนี้ เราสามารถ google ได้
โดยปกติ ค่าการซูมจะขึ้นอยู่กับสิ่งที่คุณต้องการแสดง คุณต้องการที่จะแสดงเมืองหรือรัฐ? ประเทศหรือทวีป? ไปข้างหน้าและลองใช้ค่าการซูมเพื่อให้ได้แนวคิดที่ดีขึ้น สำหรับตัวอย่างนี้ เราเลือก 13 เนื่องจากแสดงทั้งเมือง
อีกวิธีหนึ่งในการเริ่มต้นแผนที่คือการใช้ setView() ใช้อาร์เรย์ของพิกัดและจำนวนเต็มสำหรับระดับการซูม
const myMap = L.map('map').setView([37.7749, -122.4194], 13);
ตามค่าเริ่มต้น การโต้ตอบของเมาส์และการสัมผัสทั้งหมดบนแผนที่จะเปิดใช้งาน และมีการควบคุมการซูมและการระบุแหล่งที่มา
การสร้างเลเยอร์
ต่อไป เราจะเพิ่มเลเยอร์ไทล์ลงในแผนที่ของเรา ในกรณีของเรา มันคือเลเยอร์ไทล์ Mapbox Streets เราสามารถผนวกชั้นไทล์ประเภทต่างๆ ต่อท้ายด้วยการสร้างคลาส TileLayer
ในการสร้างเลเยอร์ไทล์ เราจำเป็นต้องตั้งค่าเทมเพลต URL สำหรับรูปภาพไทล์ ข้อความแสดงที่มา และระดับการซูมสูงสุดของเลเยอร์ เทมเพลต URL เป็นสิ่งที่ทำให้เราเข้าถึงเลเยอร์ไทล์ที่ต้องการจากผู้ให้บริการได้ เนื่องจากเราใช้ Static Tiles API ของ Mapbox เราจึงต้องขอโทเค็นการเข้าถึง
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', { attribution: 'Map data © <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery (c) <a href="https://www.mapbox.com/">Mapbox</a>', maxZoom: 18, id: 'mapbox/streets-v11', accessToken: 'your.mapbox.access.token' }).addTo(mymap);
ณ จุดนี้ ถ้าเราเปิด index.html ในเบราว์เซอร์ เราน่าจะเห็นแผนที่ของซานฟรานซิสโก ปักหมุดบนแผนที่กันเถอะ
เครื่องหมายและแวดวง
เรามีแผนที่และเลเยอร์แล้ว แต่ไม่ได้ชี้เฉพาะเจาะจง หากต้องการชี้ไปยังตำแหน่งใดตำแหน่งหนึ่งบนแผนที่ Leaflet ให้เครื่องหมายระบุตำแหน่งแก่เรา
ในการปักหมุดสถานที่ เราสร้างตัวอย่างเครื่องหมายโดยใช้คลาส Marker ส่งพิกัด และเพิ่มลงในแผนที่ ที่นี่เราใช้พิกัดของ Twin Peaks ในเมือง
const marker = L.marker([37.7544, -122.4477]).addTo(mymap);
ในทำนองเดียวกัน เราสามารถผูกวงกลมกับแผนที่โดยใช้คลาส Circle
เราส่งต่อตัวเลือกเสริมสองสามอย่าง เช่น รัศมี สี และอื่นๆ สำหรับเครื่องหมาย circle
เราจะผ่านพิกัดของประภาคาร Point Bonita Lighthouse
const circle = L.circle([37.8157, -122.5295], { color: 'gold', fillColor: '#f03', fillOpacity: 0.5, radius: 200 }).addTo(mymap);
ป๊อปอัพ
ทั้งหมดนี้เป็นสิ่งที่ดี แต่ถ้าเราต้องการส่งข้อมูลเพิ่มเติมเกี่ยวกับสถานที่ เราทำสิ่งนี้โดยใช้ป๊อปอัป
circle.bindPopup("I am pointing to Point Bonita Lighthouse"); marker.bindPopup("I am pointing to Twin Peaks");
เมธอด bindPopup ใช้ในเนื้อหา HTML ที่ระบุและผนวกเข้ากับเครื่องหมาย ดังนั้นป๊อปอัปจึงปรากฏขึ้นเมื่อคุณคลิกที่มาร์กเกอร์
React-แผ่นพับ
ตอนนี้เรารู้วิธีสร้างแผนที่แล้ว และเพิ่มเครื่องหมายโดยใช้ Leaflet และ vanilla JavaScript มาดูกันว่าเราจะบรรลุผลลัพธ์แบบเดียวกันด้วย React ได้อย่างไร เราจะไม่สร้างแอปพลิเคชันเดียวกันแต่สร้างแอปพลิเคชันขั้นสูงแทน
งานแรกสำหรับเราคือการได้รับโทเค็นการเข้าถึงจากพอร์ทัล San Francisco Open Data เป็นพอร์ทัลออนไลน์ที่เราสามารถค้นหาชุดข้อมูลนับร้อยจากเมืองและเคาน์ตี้ของซานฟรานซิสโก ฉันตัดสินใจใช้ทรัพยากรนี้ แต่มีแหล่งข้อมูลอื่นอีกมากมายที่เราสามารถใช้แทนได้
เข้าถึงคีย์ API
- สร้างบัญชีและลงชื่อเข้าใช้พอร์ทัล
- คลิกที่ลิงค์จัดการที่ด้านล่างขวา
- คลิกที่ Create New API Key และตั้งชื่อ
- คัดลอกรหัสคีย์และรหัสลับของคุณ คุณต้องใช้สิ่งนี้เพื่อเข้าถึงข้อมูล
สำหรับสิ่งนี้ เราจะใช้ React-Leaflet – react components สำหรับแผนที่ Leaflet มาสร้างแอปตอบโต้กันเถอะ
npx create-react-app react-fire-incidents cd react-fire-incidents
จากนั้นมาติดตั้ง react-leaflet
และ Leaflet โดยเรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัลของเรา:
npm install react-leaflet leaflet
App.js
มาสร้างโฟลเดอร์ /components
ภายใน src
กันเถอะ ภายในส่วนประกอบ มาสร้างไฟล์ชื่อ Map.js กัน นี่คือที่ที่แผนที่ของเราจะอาศัยอยู่ ตอนนี้ มาแก้ไข App.js โดยลบโค้ดที่ไม่จำเป็นและนำเข้าโมดูลจาก react-leaflet axios
และ Map.js ที่สร้างขึ้นใหม่
import React, { Component, Fragment } from 'react'; import axios from 'axios'; import Map from './components/Map'
ในคลาสแอพของเรา เราจะกำหนดอาร์เรย์ในสถานะที่เรียกว่าเหตุการณ์ — เมื่อเพจโหลด เราจะพุชข้อมูลของเราลงในอาร์เรย์นี้
class App extends Component { state = { incidents: [], } render() { return ( <div> </div> ); } } export default App;
ต่อไป เราจะทำการร้องขอ GET เมื่อส่วนประกอบติดตั้ง เรามีโทเค็นของแอป แต่เรายังต้องการจุดสิ้นสุด เราจะหาจุดสิ้นสุดได้ที่ไหน
ไปที่พอร์ทัลแล้วคลิกเรียกดูข้อมูล ในแถบค้นหา มาค้นหาเหตุการณ์ไฟไหม้กัน ผลลัพธ์แรกที่ปรากฏขึ้นคือสิ่งที่เราต้องการ เมื่อเราคลิกลิงก์แล้ว เราจะสามารถรับ URL ได้โดยคลิกปุ่ม API ที่ด้านบนขวา
เราจะส่งต่อปลายทางไปยังคำขอ GET ของเรา และส่งผ่านขีดจำกัดและโทเค็นของแอปเป็นพารามิเตอร์ ข้อมูลดั้งเดิมมีบันทึกนับพันรายการ แต่เพื่อให้ง่ายขึ้น เราจึงจำกัดข้อมูลไว้ที่ 500 รายการ เราอัปเดตอาร์เรย์เหตุการณ์ด้วยผลลัพธ์ของเรา
เมื่อเราได้รับข้อมูลแล้ว เราจะอัปเดตสถานะของเรา
async componentDidMount() { const res = await axios.get('https://data.sfgov.org/resource/wr8u-xric.json', { params: { "$limit": 500, "$$app_token": YOUR_APP_TOKEN } }) const incidents = res.data; this.setState({incidents: incidents }); };
นี่คือลักษณะของ App.js ของเรา
class App extends Component { state = { incidents: [], } async componentDidMount() { const res = await axios.get('https://data.sfgov.org/resource/wr8u-xric.json', { params: { "$limit": 500, "$$app_token": YOUR_APP_TOKEN } }) const incidents = res.data; this.setState({incidents: incidents }); }; render() { return ( <Map incidents={this.state.incidents}/> ); } } export default App;
Map.js
เนื่องจากเรารู้วิธีสร้างแผนที่ Leaflet แล้ว ส่วนนี้จะค่อนข้างง่าย เราจะนำเข้าส่วนประกอบ Map
, TileLayer
, Marker
, Popup
จาก react-leaflet
import React, { Component } from 'react' import { Map, TileLayer, Marker, Popup } from 'react-leaflet'
หากเราจำจากตัวอย่างก่อนหน้านี้ เราต้องการพิกัดและระดับการซูมเพื่อเริ่มต้นแผนที่ ในคลาส Map
ของเรา เรากำหนดมันในสถานะของเราโดยใช้ตัวแปร lat
, lng
และ zoom
export default class Map extends Component { state = { lat: 37.7749, lng: -122.4194, zoom: 13, } render() { return ( <div></div> ) } }
จากนั้นเราจะตรวจสอบว่าอาร์เรย์เหตุการณ์ของเราว่างเปล่าหรือไม่ ถ้ามันว่างเปล่า เราจะส่งคืนข้อความว่า “กำลังโหลดข้อมูล”; มิฉะนั้นเราจะส่งคืนแผนที่
ในองค์ประกอบ Map
ของ react-leaflet
เราจะส่งพิกัดศูนย์กลางและระดับการซูมไปพร้อมกับรูปแบบบางอย่าง ในองค์ประกอบ TileLayer
เราจะส่งแอตทริบิวต์และ URL ที่คล้ายกับตัวอย่างก่อนหน้านี้
render() { return ( this.props.incidents ? <Map center={[this.state.lat, this.state.lng]} zoom={this.state.zoom} style={{ width: '100%', height: '900px'}} > <TileLayer attribution='© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> </Map> : 'Data is loading...' ) } }
ต่อไป เราจะวนรอบ props.incident
ของเรา และส่งผ่านพิกัดของทุกเหตุการณ์ไปยังองค์ประกอบ Marker เนื่องจาก React เตือนเราให้ส่งรหัสไปยังทุกรายการในอาร์เรย์ เราจะส่งต่อรหัสไปยัง Marker ด้วยเช่นกัน
ภายในองค์ประกอบ Marker
เราส่งผ่านองค์ประกอบ Popup
ฉันได้เพิ่มข้อมูลเกี่ยวกับเหตุการณ์ที่เกิดขึ้นภายในป๊อปอัป
<Map center={[this.state.lat, this.state.lng]} zoom={this.state.zoom} style={{ width: '100%', height: '900px'}}> <TileLayer attribution='© <a href="https://osm.org/copyright">OpenStreetMap</a> contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> { this.props.incidents.map(incident => { const point = [incident['point']['coordinates'][1], incident['point']['coordinates'][0]] return ( <Marker position={point} key={incident['incident_number']} > <Popup> <span>ADDRESS: {incident['address']}, {incident['city']} - {incident['zip_code']}</span> <br/> <span>BATTALION: {incident['battalion']}</span><br/> </Popup> </Marker> ) }) } </Map>
และนี่ก็คือ ถ้าเราเปิดแอปของเรา และถ้าทุกอย่างเป็นไปด้วยดี เราควรจะเห็นแผนที่ของซานฟรานซิสโกที่มีเครื่องหมาย 500 อันชี้เราไปยังตำแหน่งที่เกิดเพลิงไหม้ หากเราคลิกที่เครื่องหมายใดเครื่องหมายหนึ่ง ป๊อปอัปจะปรากฏขึ้นพร้อมข้อมูลเพิ่มเติมเกี่ยวกับเหตุการณ์นั้น
ห่อ
แม้ว่าเราจะครอบคลุมมาก แต่นี่เป็นเพียงพื้นฐาน Leaflet เป็นเครื่องมือที่ทรงพลังมาก และเราสามารถสร้างแผนที่ประเภทต่างๆ ได้มากมาย หากคุณต้องการเล่น ลองเพิ่มเลเยอร์อื่นหรือไอคอนที่กำหนดเอง หรือบางทีคุณอาจต้องการสร้าง Choropleth Map แบบโต้ตอบ