Google Map API React에서 사용하기
1. 시작하기
지도를 사용해야하는 프로젝트가 있어서,
국내뿐아닌 해외도 보기 쉬운 지도 관련 API를 찾다보니
Google maps보다 더 좋은것을 찾기 힘들어
해당 API와 React를 같이 사용하여 작업해봤습니다.
저와 같은 분들을 위해 React로 Google Map API를 사용하는 방법을
작성 해 보겠습니다.
먼저 API를 사용하기 위해 KEY값이 필요하므로
Google Maps Plat form 사이트에 접속하여 KEY값을 생성합니다.
사이트에 접속 하여 다음과 같이 진행하여 키를 생성합니다.
(설정이 따로 되어있지않으면, 결제 페이지가 나옵니다)
그 후 터미널에 다음과 같이 입력하여
React로 프로젝트를 생성후 Google Map API를 설치합니다.
npm init react-app AppName
npm install --save google-maps-react
yarn install
그 후 컨테이너에서 google-maps-react를 import합니다.
import { Map, GoogleApiWrapper, Marker } from 'google-maps-react';
렌더링할 내용은 다음과 같습니다.
render() {
const mapStyles = {
width: '100%',
height: '100%',
};
return (
<Fragment>
<Map
google={this.props.google}
zoom={18}
style={mapStyles}
initialCenter=
>
</Map>
</Fragment>
);
}
또한 API를 사용하기 위한 KEY값을 설정하기 위해
export에 받아온 값을 저장합니다.
export default GoogleApiWrapper({
apiKey: 'YOUR GOOGLE MAPS API KEY 입력'
})(YourCntName);
2. 사용 방법
- HandlerEvnet
기본적으로 핸들러들의 형태는
on(HandlerEventType) 의 형태로 되어있으며,
다음과 같이 이벤트를 추가 할수 있습니다.
<Map
google={this.props.google}
onClick ={this.onEventChecker}
/>
파라미터는 총 3개입니다.
e는 html element
aug는 Map에 설정된 데이터 값
geo는 좌표값을 받을수 있는 함수를 넘겨줍니다.
onEventChecker = (e, aug, geo) => {
console.log(e);
console.log(aug);
console.log(geo);
}
이러한 핸들러 이벤트를 활용하여
지도에 마커를 추가하는 함수를 생성해 봅시다.
addMarkers = async (e, aug, geoData) => {
// console.log(aug);
const {stores} = this.state;
let stateData = stores;
let latLng;
latLng = {latitude : geoData.latLng.lat(), longitude : geoData.latLng.lng()};
stateData.push(latLng);
await this.setState({
stores: stateData
});
}
그리고 배열을 그려주는 함수를 생성하여
state의 배열이 추가될때마다 마커를 추가합니다.
displayMarkers = () => {
return this.state.stores.map((store, index) => {
return <Marker key={index} id={index} position= label={store.title}
onClick={() => this.removeMarkers(index)} />
})
}
render() {
const mapStyles = {
width: '100%',
height: '100%',
};
return (
<Fragment>
<Map
google={this.props.google}
zoom={18}
style={mapStyles}
initialCenter=
onClick={this.addMarkers}
>
{this.displayMarkers()}
</Map>
</Fragment>
);
}
마커를 그려주는 함수를 잘 보시면,
마커를 클릭시 해당 마커를 지울수 있도록 생성된 걸
확인 할 수 있습니다.
해당 함수의 코드는 다음과 같습니다.
removeMarkers = async (i) => {
const {stores} = this.state;
let stateData = stores;
stateData.splice(i,1);
await this.setState({
stores: stateData
})
}
여기까지 작성한 예제 코드를 실행 한 후
화면은 아래와 같습니다.
해당 화면에서 클릭 이벤트를 실행시..
- 주소 검색
먼저 검색을 할 input과 button을 렌더링합니다.
주소/건물 : <input value={this.state.searchText} onChange={(e) => this.onChangeInput(e)} />
<button onClick={()=>this.onSearchEvent()}>검색</button>
그 후 state에 검색할 데이터를
가지고 있는 searchText를 만들어주며,
검색 함수를 생성합니다.
onChangeInput = (e) => {
this.setState({
searchText: e.target.value
})
}
onSearchEvent = () => {
const {google} = this.props;
const {searchText} = this.state
let geocoder = new google.maps.Geocoder();
geocoder.geocode({
address: searchText,
region: 'ko'
}, function (results, status) {
if (status === google.maps.GeocoderStatus.OK) {
console.log("RESULT OK::통신 성공");
console.log(results);
} else if (status === google.maps.GeocoderStatus.ERROR) {
console.log("Error::server 통신 에러");
} else if (status === google.maps.GeocoderStatus.INVALID_REQUEST) {
console.log("Invalid request::검색어없음");
} else if (status === google.maps.GeocoderStatus.OVER_QUERY_LIMIT) {
console.log("Over query limit::너무 잦은 통신");
} else if (status === google.maps.GeocoderStatus.REQUEST_DENIED) {
console.log("Geo code denied::geoCode 이용불가 API KEY 확인");
} else if (status === google.maps.GeocoderStatus.UNKNOWN_ERROR) {
console.log("Unknown error::알수없는 에러 발생");
} else if (status === google.maps.GeocoderStatus.ZERO_RESULTS) {
console.log("Zero result::찾는 데이터와 일치하는 데이터 존재하지않음.");
} else {
console.log("unknown status error..");
}
})
}
다음은 해당 함수를 통해
부평구를 검색했을때 콜백받은 result의 값입니다.
formatted_address 값이 주소명이며,
좌표를 가져올 수 있는 함수 또한 포함 되어 있음을
확인 할 수 있습니다.
- InfoWindow
좌표에 설명하기 위한 라벨이아닌,
메모같은 정보창을 표시할 수 있는
InfoWindow가 존재합니다.
먼저 InfoWindow를 import 합니다.
import { Map, GoogleApiWrapper, Marker, InfoWindow } from 'google-maps-react';
렌더링 될 때
엘리먼트로 넣어주면 사용이 가능합니다.
<InfoWindow
visible= {Boolean}
position=
content= {String}
onClose= {function}
>
</InfoWindow>
// 예제에 사용되는 매개변수와 타입입니다.
onClose 같은 경우에는 기본 함수가 있으며
visible의 값을 false 로 바꿔줍니다.
이번에 사용할 방법은 Marker를 클릭시
infoWindow를 활성화 시키며,
close시 비활성화 시킬수 있도록 만들어 보겠습니다.
먼저 Marker의 onClick과
InfoWindow의 onClose에 들어갈 함수를 생성합니다.
visibleInfoWindow = async (i) => {
const {stores} = this.state;
let stateData = stores;
stateData[i].bool = !stateData[i].bool
await this.setState({
stores: stateData
})
}
기존에 state의 저장 했던
stores의 키값에는 visible에 들어갈
boolean 값이 없으므로 추가해줍니다.
const latLng = {latitude:lat, longitude:lng, title: address, bool: false};
Marker를 클릭시 InfoWindow가 보여야하므로
onClick을 바꾸며,
InfoWindow의 경우
Marker 를 그리는 함수와 동일 한 방식으로
출력할 수 있도록 그리는 함수를 생성합니다.
displayMarkers = () => {
return this.state.stores.map((store, index) => {
return (
<Marker
key={index}
position=
onClick={() => this.visibleInfoWindow(index)}
/>
);
})
}
displayInfoWindows = () => {
return this.state.stores.map((store, index) => {
return (
<InfoWindow
key={index}
visible={store.bool}
position=
content={store.title}
onClose={()=>this.visibleInfoWindow(index)}
>
</InfoWindow>
);
})
}
그 후 렌더링 되는 Map 안에
다음과 같이 추가합니다
{this.displayInfoWindows()}
예제 코드 실행시 아래와 같은 그림으로 실행됩니다.
해당 마커를 클릭하면..
이렇게 InfoWindow가 출력되는 것을 확인할수 있습니다.
3. 확대/축소 이벤트
이번에는 ref를 활용하여 Map의 데이터를 가져온 후
마우스 휠의 이벤트를 감지하여
state의 값으로 저장하도록 만들어 보겠습니다.
먼저 이벤트를 감지할 수 있도록 세팅합니다.
componentDidMount = () => {
window.addEventListener("DOMMouseScrol", this.onWheelEvent, false);
window.onmousewheel = document.onmousewheel = this.onWheelEvent;
}
그 후 마우스가 감지되었을때 사용할 이벤트인
onWheelEvent 를 생성합니다.
onWheelEvent = (event) =>{
var delta = 0;
if (!event) event = window.event;
if (event.wheelDelta) {
delta = event.wheelDelta/120;
if (window.opera) delta = -delta;
} else if (event.detail) delta = -event.detail/3;
if (delta) this.onWheelHandler(delta);
}
해당 이벤트가 감지되었을때 처리된 값을
받아줄 핸들러를 생성합니다.
onWheelHandler = (delta) => {
let map = this.mapRef.map;
console.log("lat::"+map.center.lat());
console.log("lng::"+map.center.lng());
console.log("zoom::"+map.zoom);
if (delta < 0) {
console.log("wheel down");
}
else {
console.log("wheel up");
};
this.setState({
zoomIndex: map.zoom
});
}
참고로 리액트의 ref 사용법은
다음과 같이 사용됩니다
ref={ref => this.mapRef = ref}
이런식으로 속성값을 태그에 넣어주시면 ref값이 저장되어
해당 엘리먼트를 확인 가능합니다.
이경우 결과 값은 다음 사진과 같습니다.
다만 해당 방식으로 처리할 경우엔 스마트폰의 확대 및 축소
감지를 하지 못하게 되어 원래
사용되던 onZoomChanged 함수를 사용 하였으나,
작동되지않아 해당 방식으로 web에서나마
처리되도록 만들어 보았습니다.