投稿日:2021年4月23日
今回はGoogleMapReactコンポーネント上にマーカーを配置する方法について実践を交えて詳しく解説しています。
今回はマップにマーカーを配置する機能を作って見ます。
この機能の作成に必要な知識はマーカーのコンポーネントとGoogleMapReactのonClickプロパティです。
まずはそこを簡単に解説しておきましょう。
GoogleMapReactコンポーネントにはマーカーを子要素として渡すことができます。子要素として渡すコンポーネントにはマーカーを配置する場所として緯度経度をlat : numberとlng : numberで渡す必要があります。
子要素に渡したコンポーネントはGoogleMapReactコンポーネント上での描画が自動的に管理されます。
GoogleMapReactコンポーネントにはonClickというプロパティに関数を渡すことでマップをクリックした場合の処理を追加することができます。
onClickに渡した関数にはマップのクリック時に以下のようにevent : GoogleMapReact.ClickEventValueを渡すように実装されていて、クリック位置の情報を取得できます。
[...]
_onClick = (...args) =>
this.props.onClick &&
!this.childMouseDownArgs_ &&
new Date().getTime() - this.childMouseUpTime_ > K_IDLE_CLICK_TIMEOUT &&
this.dragTime_ === 0 &&
this.props.onClick(...args);
_onMapClick = (event) => {
if (this.markersDispatcher_) {
// support touch events and recalculate mouse position on click
this._onMapMouseMove(event);
const currTime = new Date().getTime();
if (currTime - this.dragTime_ > K_IDLE_TIMEOUT) {
if (this.mouse_) {
this._onClick({
...this.mouse_,
event,
});
}
this.markersDispatcher_.emit('kON_CLICK', event);
}
}
};
[...]
return (
<div
style={this.props.style}
onMouseMove={this._onMapMouseMove}
onMouseDownCapture={this._onMapMouseDownCapture}
onClick={this._onMapClick}
>
<GoogleMapMap registerChild={this._registerChild} />
{IS_REACT_16 && overlay && createPortal(this._renderPortal(), overlay)}
{/* render markers before map load done */}
{mapMarkerPrerender}
</div>
);
[...]
この記事のコードは前回の記事の続きになっています。
コードは以下の環境で確認されました。
まずはマーカーの画像を管理するコンポーネントを作成します。
今回はscssモジュールを利用するのでまずそれを作成します。
.area_base {
width: 3rem;
height: 3rem;
display: "block";
}
.area_in_map {
@extend .area_base;
}
.area_mouse_trace {
@extend .area_base;
z-index: 1;
position: absolute;
pointer-events: none;
}
.area_display_none {
@extend .area_base;
display: none;
}
.img {
width: 100%;
height: 100%;
filter: drop-shadow(2px 2px 3px rgba(0, 0, 0, 0.8));
}
ここではreactのsassモジュールを使っています。
コンポーネント側の実装は以下の様になります。
import React from "react";
import logoSvg from "./logo.svg";
import styles from "./Marker.module.scss";
export interface Props {
traceMouse: boolean;
inMap: boolean;
position?: {
x: number;
y: number;
};
}
const Marker = (props: Props) => {
let classNameArea: string;
if (props.inMap) {
classNameArea = styles.area_in_map;
} else if (props.traceMouse) {
classNameArea = styles.area_mouse_trace;
} else {
classNameArea = styles.area_display_none;
}
return (
<div
className={classNameArea}
style={{
left: props.position?.x,
top: props.position?.y,
}}
>
<img src={logoSvg} alt="map marker logo" className={styles.img} />
</div>
);
};
export default Marker;
ここで注目してほしいのはclassNameAreaです。
このclassNameAreaはコンポーネントのクラス名を格納する変数なのですが、propsのtraceMouse、inMapの値によってクラス名が変わる様に処理を書いています。
今回作成する機能では以下のように3つの状態を表現するためにこのような実装にしました。
ちなみに画像はcreate-react-appのテンプレートに用意されているreactアイコンを使用しています。
次に地図上に表示するマーカーコンポーネントを作成します。マーカーコンポーネントは必ずlat、lngのpropsを受け取る必要があるため上で作成したMarkerコンポーネントをラッピングします。
import React from "react";
import Marker, { Props as MarkerProps } from "./Marker";
export interface Props {
lat: number;
lng: number;
}
/**
* 地図のマーカー
* @param props
* @returns
*/
const MapMarker = (props: Props) => {
return <Marker traceMouse={false} inMap={true} />;
};
export default MapMarker;
最後に素材をまとめるコンポーネントを作成します。
このコンポーネントではmouseTrackingNowというstateでマウスのトラッキングのON/OFFを制御しています。このstateはclickButton()関数でトグルされ、trueのときにはmousePosというstateがページ上のマウス位置をトラッキングします。
更にmouseTrackingNowがtrueのときにマップをクリックすると、clickMap()関数によってマップ上にマーカーを配置できます。
import React, { useState } from "react";
import GoogleMapReact, { ChangeEventValue } from "google-map-react";
import MapMarker, { Props as MarkerProps } from "./MapMarker";
import Marker from "./Marker";
import styles from "./SampleComponent.module.scss";
/**
* マウスのポジション
*/
interface MousePosition {
x: number;
y: number;
}
/**
* Mapに使用するプロパティ
*/
interface MapProps {
center: {
lat: number;
lng: number;
};
zoom: number;
}
/**
* MapのPropsの初期値
*/
const initialMapProps: MapProps = {
center: {
lat: 35.39,
lng: 139.44,
},
zoom: 18,
};
/**
* 地図上のMarkerのプロパティ
*/
const initialMarkerProps: MarkerProps = {
lat: 0,
lng: 0,
};
/**
* APIキー
*/
const API_KEY = "****************"; // TODO: 自分のキーをここに入力
/**
* サンプルとして地図を表示するコンポーネント
*/
const SampleComponent = () => {
// 地図のポジション
const [mapProps, setMapProps] = useState<MapProps>(initialMapProps);
// 地図上のマーカーポジション
const [markerProps, setMarkerProps] = useState<MarkerProps>(
initialMarkerProps
);
// マウスのトラッキングON/OFF
const [mouseTrackingNow, setMouseTrackingNow] = useState<boolean>(false);
// マウスのポジション
const [mousePos, setMousePos] = useState<MousePosition>({ x: 0, y: 0 });
/**
* マウストラッキング時に、マウスポジションを更新する
* @param event
*/
const onMouseMove = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (mouseTrackingNow) {
setMousePos({ x: event.clientX, y: event.clientY });
}
};
/**
* マウストラッキングのON/OFFをトグルする
*/
const clickButton = () => {
setMouseTrackingNow(!mouseTrackingNow);
};
/**
* マップをクリックしたときの処理
* トラッキングがONの場合にはマーカーを置く
* @param value
*/
const clickMap = (value: GoogleMapReact.ClickEventValue) => {
if (mouseTrackingNow) {
setMarkerProps({
lat: value.lat,
lng: value.lng,
});
setMouseTrackingNow(false);
}
};
/**
* 描画
*/
return (
<>
<Marker traceMouse={mouseTrackingNow} position={mousePos} inMap={false} />
<div
className={styles.area}
onMouseMove={mouseTrackingNow ? onMouseMove : undefined}
>
<button onClick={clickButton}>
マーカー{mouseTrackingNow ? "OFF" : "ON"}
</button>
<GoogleMapReact
bootstrapURLKeys={{ key: API_KEY }}
defaultCenter={initialMapProps.center}
center={mapProps.center}
defaultZoom={initialMapProps.zoom}
zoom={mapProps.zoom}
onClick={clickMap}
>
<MapMarker lat={markerProps?.lat} lng={markerProps?.lng} />
</GoogleMapReact>
</div>
</>
);
};
export default SampleComponent;
最後にscssモジュールを作成して完成です。
.area {
width: 100vw;
height: 100vh;
}
動かして見ましょう。
$ npm run start
今回紹介したpropの他にもいくつかの機能が実装してあるので、確認してみると良いでしょう。