はじめに
ReactでMapを使用し以下要件を満たす必要があった
・ マップスタイルの変更
・ ピンスタイルの変更
・ ピン間に線を描画する
・ ピンをクリックすると吹き出しが表示される
要件を満たすためにReactで使用可能なMapをレンダリングできるライブラリをざっと調査した
ライブラリ
ライブラリ | ピン変更 | バルーン機能 | ルーティング | 備考 |
---|---|---|---|---|
google-maps-react | Markerコンポーネントを提供している | infoWindowで変更可能 | polylineで設定可能 | weekly download 24,085 |
google-map-react | 子コンポ-ネント内に画像やスタイルを適用したコンポーネントで対応 | 子コンポーネントで吹き出しコンポーネントを配置しonChildClickで表示する | onGoogleApiLoaded時に描画可能 | weekly download 69,369 |
react-google-maps | componentのcoordinates内で変更可能 | infoWindowをカスタム | onLoaded時に描画可能 | ・weekly download 2,414 UI componentを豊富に提供 |
2gis-maps-react | 細かいアイコンの設定が可能 | popuupで設定可能 | polylineで線を引ける | ・no longer supported ・weekly download 15 |
react-map-gl | markerで設定可能 | popuupで設定可能 | overlay機能を仕様しcanvasに線を引ける | ・mapboxを使用可能 ・weekly download 34,397 |
今回は単純にコミュニティがアクティブかどうか、公式サンプルが豊富かという視点でgoogle-map-reactを使用することにした |
また、マップスタイルの変更としてsnazzymapsが豊富なサンプルを提供しているので今回使用することにした
実装
google-map-reactでピンを一個だけマップ上に表示したい場合はとても簡単に実装できる
import * as React from 'react'
import styled from 'styled-components'
import logo from './logo.svg'
import GoogleMapReact from 'google-map-react'
const App = () => (
<Wrapper>
<Header>
<Logo src={logo} />
<H1>Welcome to React</H1>
</Header>
<GoogleMapWrapper>
<GoogleMapReact
bootstrapURLKeys={{
key: {'ここにgoogle map apiのkeyをドウゾ'}
}}
defaultCenter={{
lat: 43.0582954,
lng: 141.3466919
}}
defaultZoom={15}
>
<Pin
lat={43.0582954}
lng={141.3466919}
>
おおおんどおり
</Pin>
</GoogleMapReact>
</GoogleMapWrapper>
</Wrapper>
)
const Wrapper = styled.div`
text-align: center;
`
const Header = styled.header`
background-color: ${Color.Primary};
height: 150px;
padding: 20px;
color: white;
`
const Logo = styled.img`
animation: App-logo-spin infinite 20s linear;
height: 80px;
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`
const H1 = styled.h1`
font-size: 1.5em;
`
const Pin = styled.div<{
lat: number,
lng: number
}>`
`
const GoogleMapWrapper = styled.div`
height: 100vh;
width: 100%;
`
export default App
これを適用すると大通公園に"おおおんどおり"と表示されてるはずだ
サンプルを参考にするだけでとても簡単にピンを配置できる
マップスタイル変更
Googleマップのデザインを以下のように変更したいケースがあると思う
実はsnazzymapというサイトを使えば簡単に実装できる
import * as React from 'react'
import styled from 'styled-components'
import { Color } from './brand'
import logo from './logo.svg'
import GoogleMapReact, { MapOptions, Maps } from 'google-map-react'
const App = () => {
const createMapOptions = (maps: Maps): MapOptions => {
return {
mapTypeControlOptions: {
position: maps.ControlPosition.TOP_RIGHT,
},
mapTypeControl: false,
zoomControl: false,
scaleControl: false,
streetViewControl: false,
fullscreenControl: false,
styles: [
{
featureType: 'water',
elementType: 'geometry',
stylers: [
{
color: '#e9e9e9',
},
{
lightness: 17,
},
],
},
{
featureType: 'landscape',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 20,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.fill',
stylers: [
{
color: '#ffffff',
},
{
lightness: 17,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.stroke',
stylers: [
{
color: '#ffffff',
},
{
lightness: 29,
},
{
weight: 0.2,
},
],
},
{
featureType: 'road.arterial',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 18,
},
],
},
{
featureType: 'road.local',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
featureType: 'poi',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 21,
},
],
},
{
featureType: 'poi.park',
elementType: 'geometry',
stylers: [
{
color: '#dedede',
},
{
lightness: 21,
},
],
},
{
elementType: 'labels.text.stroke',
stylers: [
{
visibility: 'on',
},
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
elementType: 'labels.text.fill',
stylers: [
{
saturation: 36,
},
{
color: '#333333',
},
{
lightness: 40,
},
],
},
{
elementType: 'labels.icon',
stylers: [
{
visibility: 'off',
},
],
},
{
featureType: 'transit',
elementType: 'geometry',
stylers: [
{
color: '#f2f2f2',
},
{
lightness: 19,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.fill',
stylers: [
{
color: '#fefefe',
},
{
lightness: 20,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.stroke',
stylers: [
{
color: '#fefefe',
},
{
lightness: 17,
},
{
weight: 1.2,
},
],
},
],
}
}
return (
<Wrapper>
<Header>
<Logo src={logo} />
<H1>Welcome to React</H1>
</Header>
<GoogleMapWrapper>
<GoogleMapReact
bootstrapURLKeys={{
key: ''
}}
defaultCenter={{
lat: 43.0582954,
lng: 141.3466919
}}
defaultZoom={15}
options={createMapOptions}
>
<Pin
lat={43.0582954}
lng={141.3466919}
>
おおおんどおり
</Pin>
</GoogleMapReact>
</GoogleMapWrapper>
</Wrapper>
)
}
const Wrapper = styled.div`
text-align: center;
`
const Header = styled.header`
background-color: ${Color.Primary};
height: 150px;
padding: 20px;
color: white;
`
const Logo = styled.img`
animation: App-logo-spin infinite 20s linear;
height: 80px;
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`
const H1 = styled.h1`
font-size: 1.5em;
`
const Pin = styled.div<{
lat: number,
lng: number
}>`
`
const GoogleMapWrapper = styled.div`
height: 100vh;
width: 100%;
`
export default App
createMapOptionsのfullscreenControl optionの下にstyles optionがある
このstyles optionにsnazzymapで取得したソースコードをコピペするだけで実装できる
ちなみに今回はSubtle Grayscaleを適用した
ピンスタイルの変更
前述通りピンを1個だけ描画したい場合はとても簡単にできるが複数ピンのスタイルを変更し描画したい場合はどうすればいいのかともしかしたらなるかもしれない
これも簡単に実装できる
複数ピンの描画位置に関しては、GoogleMapReactの1階層子コンポーネントでlatやlngを元に相対位置をよしなに算出してくれるので子コンポーネントにスタイルを適用するだけで実装ができる
import * as React from 'react'
import styled from 'styled-components'
import { Color } from './brand'
import logo from './logo.svg'
import GoogleMapReact, { MapOptions, Maps } from 'google-map-react'
const App = () => {
const pins: {
lat: number,
lng: number,
name: string
}[] = [
{
lat: 43.0543412,
lng: 141.355018,
name: 'お姉さんレーベル'
},
{
lat: 43.0543451,
lng: 141.3528293,
name: '姉キャバ ジャックローズ'
}
]
const createMapOptions = (maps: Maps): MapOptions => {
return {
mapTypeControlOptions: {
position: maps.ControlPosition.TOP_RIGHT,
},
mapTypeControl: false,
zoomControl: false,
scaleControl: false,
streetViewControl: false,
fullscreenControl: false,
styles: [
{
featureType: 'water',
elementType: 'geometry',
stylers: [
{
color: '#e9e9e9',
},
{
lightness: 17,
},
],
},
{
featureType: 'landscape',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 20,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.fill',
stylers: [
{
color: '#ffffff',
},
{
lightness: 17,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.stroke',
stylers: [
{
color: '#ffffff',
},
{
lightness: 29,
},
{
weight: 0.2,
},
],
},
{
featureType: 'road.arterial',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 18,
},
],
},
{
featureType: 'road.local',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
featureType: 'poi',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 21,
},
],
},
{
featureType: 'poi.park',
elementType: 'geometry',
stylers: [
{
color: '#dedede',
},
{
lightness: 21,
},
],
},
{
elementType: 'labels.text.stroke',
stylers: [
{
visibility: 'on',
},
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
elementType: 'labels.text.fill',
stylers: [
{
saturation: 36,
},
{
color: '#333333',
},
{
lightness: 40,
},
],
},
{
elementType: 'labels.icon',
stylers: [
{
visibility: 'off',
},
],
},
{
featureType: 'transit',
elementType: 'geometry',
stylers: [
{
color: '#f2f2f2',
},
{
lightness: 19,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.fill',
stylers: [
{
color: '#fefefe',
},
{
lightness: 20,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.stroke',
stylers: [
{
color: '#fefefe',
},
{
lightness: 17,
},
{
weight: 1.2,
},
],
},
],
}
}
return (
<Wrapper>
<Header>
<Logo src={logo} />
<H1>Welcome to React</H1>
</Header>
<GoogleMapWrapper>
<GoogleMapReact
bootstrapURLKeys={{
key: ''
}}
defaultCenter={{
lat: 43.0543451,
lng: 141.3528293
}}
defaultZoom={15}
options={createMapOptions}
>
{
pins.map((pin: {
lat: number,
lng: number,
name: string
}) => (
<Pin
lat={pin.lat}
lng={pin.lng}
>
{pin.name}
</Pin>
))
}
</GoogleMapReact>
</GoogleMapWrapper>
</Wrapper>
)
}
const Wrapper = styled.div`
text-align: center;
`
const Header = styled.header`
background-color: ${Color.Primary};
height: 150px;
padding: 20px;
color: white;
`
const Logo = styled.img`
animation: App-logo-spin infinite 20s linear;
height: 80px;
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`
const H1 = styled.h1`
font-size: 1.5em;
`
const Pin = styled.div<{
lat: number,
lng: number
}>`
`
const GoogleMapWrapper = styled.div`
height: 100vh;
width: 100%;
`
export default App
お姉さんレーベルと姉キャバ ジャックローズが表示されてることが確認できると思う
あとは、Pinコンポーネントのstyleを変更すればデザインは簡単に変更可能だ
ピン間に線を描画する
GoogleMapReactではGoogle map apiのloaded時に上乗せ描画できるapiを提供している
例えば線を描画するpolylineがある
今回はpolylineを使用しでピン間に線を引く
import * as React from 'react'
import styled from 'styled-components'
import { Color } from './brand'
import logo from './logo.svg'
import GoogleMapReact, { MapOptions, Maps } from 'google-map-react'
interface PinProps {
lat: number,
lng: number,
name: string
}
const App = () => {
const pins: PinProps[] = [
{
lat: 43.0543412,
lng: 141.355018,
name: 'お姉さんレーベル'
},
{
lat: 43.0543451,
lng: 141.3528293,
name: '姉キャバ ジャックローズ'
}
]
const apiLoaded = (map: any, maps: any, pins: any) => {
const path = new maps.Polyline({
path: pins.map((p: PinProps) => ({
lat: p.lat,
lng: p.lng
})),
geodesic: true,
strokeColor: '#356859',
strokeOpacity: 1,
strokeWeight: 3,
})
path.setMap(map)
}
const createMapOptions = (maps: Maps): MapOptions => {
return {
mapTypeControlOptions: {
position: maps.ControlPosition.TOP_RIGHT,
},
mapTypeControl: false,
zoomControl: false,
scaleControl: false,
streetViewControl: false,
fullscreenControl: false,
styles: [
{
featureType: 'water',
elementType: 'geometry',
stylers: [
{
color: '#e9e9e9',
},
{
lightness: 17,
},
],
},
{
featureType: 'landscape',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 20,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.fill',
stylers: [
{
color: '#ffffff',
},
{
lightness: 17,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.stroke',
stylers: [
{
color: '#ffffff',
},
{
lightness: 29,
},
{
weight: 0.2,
},
],
},
{
featureType: 'road.arterial',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 18,
},
],
},
{
featureType: 'road.local',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
featureType: 'poi',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 21,
},
],
},
{
featureType: 'poi.park',
elementType: 'geometry',
stylers: [
{
color: '#dedede',
},
{
lightness: 21,
},
],
},
{
elementType: 'labels.text.stroke',
stylers: [
{
visibility: 'on',
},
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
elementType: 'labels.text.fill',
stylers: [
{
saturation: 36,
},
{
color: '#333333',
},
{
lightness: 40,
},
],
},
{
elementType: 'labels.icon',
stylers: [
{
visibility: 'off',
},
],
},
{
featureType: 'transit',
elementType: 'geometry',
stylers: [
{
color: '#f2f2f2',
},
{
lightness: 19,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.fill',
stylers: [
{
color: '#fefefe',
},
{
lightness: 20,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.stroke',
stylers: [
{
color: '#fefefe',
},
{
lightness: 17,
},
{
weight: 1.2,
},
],
},
],
}
}
return (
<Wrapper>
<Header>
<Logo src={logo} />
<H1>Welcome to React</H1>
</Header>
<GoogleMapWrapper>
<GoogleMapReact
bootstrapURLKeys={{
key: ''
}}
defaultCenter={{
lat: 43.0543451,
lng: 141.3528293
}}
defaultZoom={15}
options={createMapOptions}
onGoogleApiLoaded={({ map, maps }) => apiLoaded(map, maps, pins)}
>
{
pins.map((pin: {
lat: number,
lng: number,
name: string
}) => (
<Pin
lat={pin.lat}
lng={pin.lng}
>
{pin.name}
</Pin>
))
}
</GoogleMapReact>
</GoogleMapWrapper>
</Wrapper>
)
}
const Wrapper = styled.div`
text-align: center;
`
const Header = styled.header`
background-color: ${Color.Primary};
height: 150px;
padding: 20px;
color: white;
`
const Logo = styled.img`
animation: App-logo-spin infinite 20s linear;
height: 80px;
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`
const H1 = styled.h1`
font-size: 1.5em;
`
const Pin = styled.div<{
lat: number,
lng: number
}>`
`
const GoogleMapWrapper = styled.div`
height: 100vh;
width: 100%;
`
export default App
お姉さんレーベルと姉キャバ ジャックローズの間に緑色の線が引かれていることが確認できるだろう
ピンをクリックすると吹き出しが表示される
GoogleMapReactは子コンポーネントのクリック時にイベント取得するonChildClickというapiを提供している
onChildClickはイベント発生時に該当するコンポーネントのkeyと子コンポーネント自体の情報(childProps)を返す
基本吹き出しを表示する際はクリックイベントのkeyとmapのindexが同じなら表示するで実装できるので、基本childPropsは使用しなくていいだろう
import * as React from 'react'
import styled from 'styled-components'
import { useState } from 'react'
import { Color } from './brand'
import logo from './logo.svg'
import GoogleMapReact, { MapOptions, Maps } from 'google-map-react'
interface PinProps {
lat: number,
lng: number,
name: string
}
const App = () => {
const [currentKey, setCurrentKey] = useState(-1)
const pins: PinProps[] = [
{
lat: 43.0543412,
lng: 141.355018,
name: 'お姉さんレーベル'
},
{
lat: 43.0543451,
lng: 141.3528293,
name: '姉キャバ ジャックローズ'
}
]
const apiLoaded = (map: any, maps: any, pins: any) => {
const path = new maps.Polyline({
path: pins.map((p: PinProps) => ({
lat: p.lat,
lng: p.lng
})),
geodesic: true,
strokeColor: '#356859',
strokeOpacity: 1,
strokeWeight: 3,
})
path.setMap(map)
}
const changeBalloon = (key: string) => {
const keyNumber = Number(key)
if (currentKey === keyNumber) {
setCurrentKey(-1)
} else {
setCurrentKey(keyNumber)
}
}
const createMapOptions = (maps: Maps): MapOptions => {
return {
mapTypeControlOptions: {
position: maps.ControlPosition.TOP_RIGHT,
},
mapTypeControl: false,
zoomControl: false,
scaleControl: false,
streetViewControl: false,
fullscreenControl: false,
styles: [
{
featureType: 'water',
elementType: 'geometry',
stylers: [
{
color: '#e9e9e9',
},
{
lightness: 17,
},
],
},
{
featureType: 'landscape',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 20,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.fill',
stylers: [
{
color: '#ffffff',
},
{
lightness: 17,
},
],
},
{
featureType: 'road.highway',
elementType: 'geometry.stroke',
stylers: [
{
color: '#ffffff',
},
{
lightness: 29,
},
{
weight: 0.2,
},
],
},
{
featureType: 'road.arterial',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 18,
},
],
},
{
featureType: 'road.local',
elementType: 'geometry',
stylers: [
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
featureType: 'poi',
elementType: 'geometry',
stylers: [
{
color: '#f5f5f5',
},
{
lightness: 21,
},
],
},
{
featureType: 'poi.park',
elementType: 'geometry',
stylers: [
{
color: '#dedede',
},
{
lightness: 21,
},
],
},
{
elementType: 'labels.text.stroke',
stylers: [
{
visibility: 'on',
},
{
color: '#ffffff',
},
{
lightness: 16,
},
],
},
{
elementType: 'labels.text.fill',
stylers: [
{
saturation: 36,
},
{
color: '#333333',
},
{
lightness: 40,
},
],
},
{
elementType: 'labels.icon',
stylers: [
{
visibility: 'off',
},
],
},
{
featureType: 'transit',
elementType: 'geometry',
stylers: [
{
color: '#f2f2f2',
},
{
lightness: 19,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.fill',
stylers: [
{
color: '#fefefe',
},
{
lightness: 20,
},
],
},
{
featureType: 'administrative',
elementType: 'geometry.stroke',
stylers: [
{
color: '#fefefe',
},
{
lightness: 17,
},
{
weight: 1.2,
},
],
},
],
}
}
return (
<Wrapper>
<Header>
<Logo src={logo} />
<H1>Welcome to React</H1>
</Header>
<GoogleMapWrapper>
<GoogleMapReact
bootstrapURLKeys={{
key: ''
}}
defaultCenter={{
lat: 43.0543451,
lng: 141.3528293
}}
defaultZoom={15}
options={createMapOptions}
onGoogleApiLoaded={({ map, maps }) => apiLoaded(map, maps, pins)}
onChildClick={(key: string) => changeBalloon(key)}
>
{
pins.map((
pin: {
lat: number,
lng: number,
name: string
},
index: number) => (
<Pin
lat={pin.lat}
lng={pin.lng}
>
{pin.name}
{
index === currentKey &&
'こんにちは'
}
</Pin>
))
}
</GoogleMapReact>
</GoogleMapWrapper>
</Wrapper>
)
}
const Wrapper = styled.div`
text-align: center;
`
const Header = styled.header`
background-color: ${Color.Primary};
height: 150px;
padding: 20px;
color: white;
`
const Logo = styled.img`
animation: App-logo-spin infinite 20s linear;
height: 80px;
@keyframes App-logo-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`
const H1 = styled.h1`
font-size: 1.5em;
`
const Pin = styled.div<{
lat: number,
lng: number
}>`
`
const GoogleMapWrapper = styled.div`
height: 100vh;
width: 100%;
`
export default App
それぞれの子コンポーネントをクリックすると"こんにちは"が表示されるだろう
onGoogleApiLoadedのmapsからgoogle apiのdraw機能を扱うことができる
参照
useStateがよくわからねえという方は自分で勉強してください
#まとめ
今回使用したライブラリの使用事例の記事がなかなか見つからなかったので、今回記事にし公開することにした
これからReactでgoogle mapの使用を検討している方に少しでも参考になれば幸いだ
今回作成したサンプルはこちら