openlayersでReactを試す2


前回、[openlayersをReactで試す](https://qiita.com/kenji123/items/1271b68f123e14719b52)記事を掲載したが、コンポーネント分け、イベントを使っていなかった。せめてそれは盛り込んだものを残したいのでもう一度テコ入れした。
下記サイトを使用し、ほぼ写経になったが、コンポーネント分け、イベントの記載が実現できたので掲載する。

https://medium.com/swlh/how-to-incorporate-openlayers-maps-into-react-65b411985744


## 記載したコード
・Create React Appを利用
npx create-react-app my-app

(バージョン)
node:v14.16.0

下記インストール
"ol": "^6.5.0",


変更箇所はApp.js、作成箇所はMap、Layers、Controlsのフォルダとファイル

・src配下
── App.css
├── App.js
├── App.test.js
├── Controls
│   ├── Controls.js
│   ├── FullScreenControl.js
│   └── index.js
├── Layers
│   ├── Layers.js
│   ├── TileLayer.js
│   ├── VectorLayer.js
│   └── index.js
├── Map
│   ├── Map.css
│   ├── Map.js
│   ├── MapContext.js
│   └── index.js
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
└── setupTests.js

・作成したコード

App.js

```App.js

import React, { useState }  from "react"

// import Map from "./Map/Map";
import Map from "./Map";

import { fromLonLat, get } from 'ol/proj';

import { Layers,TileLayer,VectorLayer } from "./Layers";
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';

import * as olSource from "ol/source";
import { Vector as VectorSource } from 'ol/source';
import GeoJSON from 'ol/format/GeoJSON';
import { Controls, FullScreenControl } from "./Controls";


// import Map from "./Map";

let styles = {
  'Point': new Style({
    image: new CircleStyle({
      radius: 10,
      fill: null,
      stroke: new Stroke({
        color: 'magenta',
      }),
    }),
  }),
  'Polygon': new Style({
    stroke: new Stroke({
      color: 'blue',
      lineDash: [4],
      width: 3,
    }),
    fill: new Fill({
      color: 'rgba(0, 0, 255, 0.1)',
    }),
  }),
  'MultiPolygon': new Style({
    stroke: new Stroke({
      color: 'blue',
      width: 1,
    }),
    fill: new Fill({
      color: 'rgba(0, 0, 255, 0.1)',
    }),
  }),
};

const geojsonObject = {
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "kind": "county",
        "name": "Wyandotte",
        "state": "KS"
      },
      "geometry": {
        "type": "MultiPolygon",
        "coordinates": [
          [
            [
              [
                -94.8627,
                39.202
              ],
              [
                -94.901,
                39.202
              ],
              [
                -94.9065,
                38.9884
              ],
              [
                -94.8682,
                39.0596
              ],
              [
                -94.6053,
                39.0432
              ],
              [
                -94.6053,
                39.1144
              ],
              [
                -94.5998,
                39.1582
              ],
              [
                -94.7422,
                39.1691
              ],
              [
                -94.7751,
                39.202
              ],
              [
                -94.8627,
                39.202
              ]
            ]
          ]
        ]
      }
    }
  ]
};


 const App = () =>{
  const [center, setCenter] = useState([-94.9065, 38.9884]);
  const [zoom, setZoom] = useState(9);
  const [showLayer1, setShowLayer1] = useState(true);
  const [showLayer2, setShowLayer2] = useState(true);
  
  return (
  <div>
    <Map center={fromLonLat(center)} zoom={zoom} >
      <Layers>
        <TileLayer
            source={new olSource.OSM()}
            zIndex={0}
          />
        {showLayer1 && (
        <VectorLayer
            source={new VectorSource({ features: new GeoJSON().readFeatures(geojsonObject, { featureProjection: get('EPSG:3857') }) })}
            style={styles.MultiPolygon}
          />    
         )}
      </Layers>
      <Controls>
        <FullScreenControl />
      </Controls>
    </Map>
    <div>
      <input
        type="checkbox"
        checked={showLayer1}
        onChange={event => setShowLayer1(event.target.checked)}
      /> Johnson County
    </div>
 </div>
 );
}

export default App;

```

***

Mapフォルダ(Mapコンポーネント群)

```Map.js
import React, { useEffect, useRef, useState } from 'react';
// OpenLayers読み込み
import * as ol from "ol";
import MapContext from "./MapContext";
import 'ol/ol.css';
import './Map.css';

const Map = ({children,zoom,center}) => {
  const mapRef = useRef();
console.log(zoom);
console.log(center);
console.log('childrenとは');
console.log(children);
  const [map,setMap] = useState(null);

  // on component mount
  useEffect(() => {
    let options = {
      view: new ol.View({ zoom, center }),
      layers: [],
      controls: [],
      overlays: []
    };
    let mapObject = new ol.Map(options);
console.log('aaa');
console.log(mapObject);
    mapObject.setTarget(mapRef.current);
    setMap(mapObject);
console.log(map);
    return () => mapObject.setTarget(undefined);
  }, []);
  
  // zoom change handler
  useEffect(() => {
    if (!map) return;
    map.getView().setZoom(zoom);
  }, [zoom]);

  // center change handler
  useEffect(() => {
    if (!map) return;

    map.getView().setCenter(center)
  }, [center])
  
    return (
      <MapContext.Provider value={{ map }}>
      <div ref={mapRef} className="ol-map">
        {children}
      </div>
       </MapContext.Provider>   
     )
}

export default Map;

```


```MapContext.js
import React from "react";
const MapContext = new React.createContext();
export default MapContext;
```

```Map.css
.ol-map {
  min-width: 600px;
  min-height: 500px;
  margin: 50px;
  height: 500px;
  width: "100%";
}

.ol-control {
  position: absolute;
  background-color: rgba(255,255,255,0.4);
  border-radius: 4px;
  padding: 2px;
}

.ol-full-screen {
  top: .5em;
  right: .5em;
}
```

```index.js
export { default } from "./Map"
```
***

レイヤー群


```Layers.js
import React from "react";

const Layers = ({ children }) => {
  console.log('Layersの中');
  console.log(children);
	return <div>{children}</div>;
};

export default Layers;
```

```TileLayer.js
import { useContext, useEffect } from "react";
import MapContext from "../Map/MapContext";
import OLTileLayer from "ol/layer/Tile";

const TileLayer = ({ source, zIndex = 0 }) => {
  const { map } = useContext(MapContext);
console.log('TileLayersに入っている！！！！');
console.log(map);
console.log(zIndex);
console.log(source);
  useEffect(() => {
    if (!map) return;

    let tileLayer = new OLTileLayer({
      source,
      zIndex,
    });

    map.addLayer(tileLayer);
    // tileLayer.setZIndex(zIndex);

    return () => {
      if (map) {
        map.removeLayer(tileLayer);
      }
    };
  }, [map]);

  return null;
};

export default TileLayer;

```

```VectorLayer.js
import { useContext, useEffect } from "react";
import MapContext from "../Map/MapContext";
import OLVectorLayer from "ol/layer/Vector";

const VectorLayer = ({ source, style, zIndex = 0 }) => {
  const { map } = useContext(MapContext);

  useEffect(() => {
    if (!map) return;

    let vectorLayer = new OLVectorLayer({
      source,
      style
    });

    map.addLayer(vectorLayer);
    vectorLayer.setZIndex(zIndex);

    return () => {
      if (map) {
        map.removeLayer(vectorLayer);
      }
    };
  }, [map]);

  return null;
};

export default VectorLayer;

```

```index.js
import Layers from "./Layers";
import VectorLayer from "./VectorLayer";
import TileLayer from "./TileLayer";

export {
	Layers,
	VectorLayer,
	TileLayer
}
```

***

Control群

```Control.js
import React from "react";

const Controls = ({ children }) => {
	return <div>{children}</div>;
};

export default Controls;

```

```FullScreenControl.js
import React, { useContext, useEffect, useState } from "react";
import { FullScreen } from "ol/control";
import MapContext from "../Map/MapContext";

const FullScreenControl = () => {
  const { map } = useContext(MapContext);

  useEffect(() => {
    if (!map) return;

    let fullScreenControl = new FullScreen({});

    map.controls.push(fullScreenControl);

    return () => map.controls.remove(fullScreenControl);
  }, [map]);

  return null;
};

export default FullScreenControl;
```

```index.js
import Controls from "./Controls";
import FullScreenControl from "./FullScreenControl";

export {
  Controls,
  FullScreenControl
}
```

![スクリーンショット 2021-05-27 20.42.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/176603/a239fff6-ad17-4960-e4f1-8b8988e978d3.png)



## ポイント

### 使用レイヤーでコンポーネント分け
要はレイヤーを重ねて地図を表示しているんだが、コンポーネント化して書くと、どうやって地図を表示しているかのわかりやすい。
以前、[もしGISのオープンソース を使用することになったら押さえておくこと(OpenLayersを例にする)](https://qiita.com/kenji123/items/b0a15474f264a36459b4)
で、地図表示からレイヤー表示、データの反映イメージ等を整理した内容を記載したが、そのイメージがわかりやすいものになっている。
openlayersの地図アプリを使用する時、SPAのライブラリーを使わずに、javascriptでごりごり書くよりも最初の理解のステップが早いかもしれない。

### グローバル変数とその利用
createContext()を使用し、どこからでもmapを利用できるしている。
グローバルで使用、useContextでいきなり受け取れる。
Map.jsでVueのslotのようにchildrenを使用している。

### clickイベントの盛り込み
イベントより地図を操作。チェックを外すとVectorLayerが消える。

### useState, useEffect, useRefを利用
前回と同様

### 各フォルダのindex.js
冗長さを感じたがimportしやすい。