LoginSignup
2
4

More than 1 year has passed since last update.

Leaflet 地図上の緯度経度情報を React Redux で状態管理する

Last updated at Posted at 2022-02-19

先日は React で OpenStreetMap を表示することが出来る Leaflet をご紹介しました。外部データを地図上に表示したいというユースケースがあると思います。React の状態管理で広く使われている Redux と Leaflet を連携させる方法を解説します。

Leaflet から Redux へデータを渡す

Leaflet

MapContainer はマップを表示・操作するコンポーネントです。このコンポーネント配下では Leaflet の地図を操作することが出来ます。
MapController は地図操作処理を記述したコンポーネントです。
TileLayer は表示する地図を定義しています。
LocationMarker は後述します。

OpenStreetMap.tsx
import { MapContainer, MapContainerProps, TileLayer } from 'react-leaflet';

import MapController from './MapController';

import 'leaflet/dist/leaflet.css';
import 'assets/css/OpenStreetMap.css';

export const OpenStreetMap = (props: MapContainerProps) => {
  const initialPosition = {
    lat: 35.6809591,
    lng: 139.7673068,
  };
  return (
    <MapContainer
      center={initialPosition}
      zoom={17}
      scrollWheelZoom={true}
      whenReady={() => {
        console.log('This function will fire once the map is created');
      }}
      whenCreated={(map) => {
        console.log('The underlying leaflet map instance:', map);
        // デバイスの現在地の緯度経度を取得する
        map.locate();
      }}
    >
      <TileLayer
        attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <MapController />
      <LocationMarker />
    </MapContainer>
  );
};

export default OpenStreetMap;

useMapEvents は Leaflet の関数です。
locationfound(e) はデバイスの現在地の緯度経度情報を見つけた時に処理されます。e には現在地の緯度経度情報が入っています。
moveend(e) は地図を移動し終えた時に処理されます。e には地図の中心の緯度経度情報が入っています。

setCurrentLocation と setMapCenterPosition で各々の緯度経度情報を redux に渡しています。

MapController.tsx
import { useAppDispatch } from 'hooks';

import {
  setCurrentLocation,
  setMapCenterPosition,
} from 'stores/openstreetmap-slice';
import { useMapEvents } from 'react-leaflet';

export const MapController = (props: any) => {
  const dispatch = useAppDispatch();

  const map = useMapEvents({
    locationfound(e) {
      // デバイスの現在地の緯度経度情報を redux に渡す
      dispatch(
        setCurrentLocation({ 
          lat: e.latlng.lat, 
          lng: e.latlng.lng
        })
      );
      // デバイスの現在地の緯度経度に地図の中心を移動する
      map.flyTo(e.latlng, map.getZoom()); 
    },
    moveend(e) {
      // 地図の中心の緯度経度情報を redux に渡す
      dispatch(
        setMapCenterPosition({
          lat: e.target._lastCenter.lat,
          lng: e.target._lastCenter.lng,
        })
      );
    },
  });

  return <></>;
};

export default MapController;

Redux Toolkit で状態管理

npx create-react-app my-app --template redux-typescript

redux 周りは上記で生成したソースコードをベースに作っています。
Leaflet で取得できる現在地(currentLocation)と地図上の位置情報(mapCenterPosition)を redux で管理しています。

openstreetmap-slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState } from 'store';
import { LatLngLiteral } from 'leaflet';

const STORE_NAME = 'openstreetmap';

export interface OpenStreetMapState {
  status: 'idle' | 'loading' | 'failed';
  currentLocation: LatLngLiteral;
  mapCenterPosition: LatLngLiteral;
}

const initialPosition: LatLngLiteral = {
  lat: 35.6809591,
  lng: 139.7673068,
};

const initialState: OpenStreetMapState = {
  status: 'idle',
  currentLocation: initialPosition,
  mapCenterPosition: initialPosition,
};

export const openstreetmapSlice = createSlice({
  name: STORE_NAME,
  initialState,
  reducers: {
    setCurrentLocation: (state, action: PayloadAction<LatLngLiteral>) => {
      state.currentLocation = action.payload;
    },
    setMapCenterPosition: (state, action: PayloadAction<LatLngLiteral>) => {
      state.mapCenterPosition = action.payload;
    },
  },
  extraReducers: (builder) => {},
});

export const selectCurrentLocation = (state: RootState) => state.openstreetmap.currentLocation;
export const selectMapCenterPosition = (state: RootState) => state.openstreetmap.mapCenterPosition;
export const { setCurrentLocation, setMapCenterPosition } = openstreetmapSlice.actions;

export default openstreetmapSlice.reducer;

Redux から Leaflet へデータを渡す

オープンデータを Leaflet に表示する際には以下の手順になります。
1.API 通信して redux でオープンデータを管理(※ stores/opendata-slice 部分)
2.Leaflet 地図上に redux のデータをセットした React コンポーネントを描画

LocationMarker.tsx
import { useAppSelector } from 'hooks';
import { selectCurrentLocation } from 'stores/openstreetmap-slice';
import { selectOpenData} from 'stores/opendata-slice';
import { LatLng } from 'leaflet';
import { Circle, FeatureGroup, Popup } from 'react-leaflet';
import { Avatar, Button, Card, CardActions, CardHeader } from '@mui/material';
import DirectionsIcon from '@mui/icons-material/Directions';

export const LocationMarker = () => {
  const currentLocation = useAppSelector(selectCurrentLocation);
  const openDataList = useAppSelector(selectOpenData);

  const OpenDataCircle = openDataList.map(
    (openData, index) => {
      return (
        <Circle
          key={index.toString()}
          pathOptions={{ color: 'orange' }}
          center={new LatLng(openData.lat, openData.lng)}
          radius={50}
          eventHandlers={{
            click: () => {
              // console.log('Circle clicked');
            },
          }}
        >
          <Popup closeButton={false}>
            <Card sx={{ maxWidth: 'auto' }}>
              <CardHeader
                avatar={<Avatar src={<DirectionsIcon />} variant="square" />}
                title={openData.name}
                }
              />
              <CardActions>
                <Button variant="outlined" href={openData.id} >詳細を見る</Button>
              </CardActions>
            </Card>
          </Popup>
        </Circle>
      );
    }
  );

  return currentLocation === null ? null : (
    <FeatureGroup>
      {OpenDataCircle}
    </FeatureGroup>
  );
};

export default LocationMarker;

以上です。どなたかのお役に立てれば嬉しいです。

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4