LoginSignup
2
1

Reactベースの地図アプリを作成してみる

Last updated at Posted at 2023-09-19

はじめに

HEREでエンジニアをしておりますkekishidaと申します。
以前、拙記事においてHERE Maps API for JavascriptをReactコンポーネント化するサンプルを紹介しました。

以前に紹介した地図アプリがあまりに個人的なもので、入門編のサンプルとしては相応しくなかったため、今回改めて日本地図向けに特化したReactベースのアプリのサンプルを作成してみました。
image.png
image.png
本アプリケーション自体はReactの特性を活かして、サーチボックスと地図部分をそれぞれコンポーネント化し、左上のサーチボックスに住所、地名などを入力することで、指定した地点を中心とした地図が表示されます。今回使用するプロダクトおよびサービスは以下の通りです。

  • HERE Maps API for Javascript
  • HERE Geocoding & Search

それでは始めましょう。

必要なもの

ご紹介するサンプルコードは、HEREアカウントの取得が必要になります。

HEREアカウントの取得

作成手順 (step by step)

以下よりstep by stepでコードを紹介いたします。APIKEY以外は基本的にコピーペーストのみで実装可能なように配慮してあります。

React projectの作成

shell command
npx create-react-app <プロジェクトフォルダ名>

index.htmlの編集

index.htmlを以下のように書き換えてください。

index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>HERE Maps API for Javascript Demo</title>
    <meta name="viewport" content="initial-scale=1.0, width=device-width" />
    <!-- <link rel="stylesheet" type="text/css" href="https://js.api.here.com/v3/3.1/mapsjs-ui.css" /> -->
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-core.js"
    ></script>
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-service.js"
    ></script>
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-ui.js"
    ></script>
    <script
      type="text/javascript"
      src="https://js.api.here.com/v3/3.1/mapsjs-mapevents.js"
    ></script> 
    <script 
      type="text/javascript" 
      src="https://js.api.here.com/v3/3.1/mapsjs-clustering.js"
    ></script> 
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

index.jsの編集

index.jsを以下のように書き換えてください。

index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <App/>
);


App.jsの編集

App.jsを以下のように書き換えてください。また、以下のAPIKEYは取得されたAPIKEYに置き換えてください。

App.js
import Map from './Map';
import Search from './Search';
import { useState } from 'react';
let apikey = <APIKEY>;

function App() {
    const [ gps, setGps ] = useState({lat: "35.6814568602531", lng: "139.76799772026422"});
    return (
    <div>
      <Search apikey={apikey} onGeocodeResult={query=> setGps(query)}/>
      <Map apikey={apikey} gps={gps}/>
    </div>
  );
};

export default App;

Map.jsの作成

以下のファイルを追加してください。

Map.js
import * as React from 'react';

const Map = (props) => {
  const apikey = props.apikey;
  const gps = props.gps;
  // Create a reference to the HTML element we want to put the map on
  const mapRef = React.useRef(null);
  /**
   * Create the map instance
   * While `useEffect` could also be used here, `useLayoutEffect` will render
   * the map sooner
   */
    // React.useEffect(() => {
   React.useLayoutEffect(() => {
    // `mapRef.current` will be `undefined` when this hook first runs; edge case that
    if (!mapRef.current) return;
    const H = window.H;
    const platform = new H.service.Platform({
        apikey: apikey
    });
    const defaultLayers = platform.createDefaultLayers();
    // configure an OMV service to use the `core` endpoint
    var omvService = platform.getOMVService({ path: "v2/vectortiles/core/mc" });
    var baseUrl = "https://js.api.here.com/v3/3.1/styles/omv/oslo/japan/";

    // create a Japan specific style
    var style = new H.map.Style(`${baseUrl}normal.day.yaml`, baseUrl);

    // instantiate provider and layer for the base map
    var omvProvider = new H.service.omv.Provider(omvService, style);
    var omvlayer = new H.map.layer.TileLayer(omvProvider, { max: 22 ,dark:true});

    // instantiate (and display) a map:
    var map = new H.Map(mapRef.current, omvlayer, {
        zoom: 16,
        center: { lat: gps.lat, lng: gps.lng },
    });

    // add a resize listener to make sure that the map occupies the whole container
    window.addEventListener("resize", () => map.getViewPort().resize());

    // MapEvents enables the event system
    // Behavior implements default interactions for pan/zoom (also on mobile touch environments)
    var behavior = new H.mapevents.Behavior(new H.mapevents.MapEvents(map));
    // Create the default UI components
    var ui = H.ui.UI.createDefault(map, defaultLayers); 
    // Marker code goes here
    var LocationOfMarker = { lat: gps.lat, lng: gps.lng };
    var pngIcon = new H.map.Icon('<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 2c2.131 0 4 1.73 4 3.702 0 2.05-1.714 4.941-4 8.561-2.286-3.62-4-6.511-4-8.561 0-1.972 1.869-3.702 4-3.702zm0-2c-3.148 0-6 2.553-6 5.702 0 3.148 2.602 6.907 6 12.298 3.398-5.391 6-9.15 6-12.298 0-3.149-2.851-5.702-6-5.702zm0 8c-1.105 0-2-.895-2-2s.895-2 2-2 2 .895 2 2-.895 2-2 2zm10.881-2.501c0-1.492-.739-2.83-1.902-3.748l.741-.752c1.395 1.101 2.28 2.706 2.28 4.5s-.885 3.4-2.28 4.501l-.741-.753c1.163-.917 1.902-2.256 1.902-3.748zm-3.381 2.249l.74.751c.931-.733 1.521-1.804 1.521-3 0-1.195-.59-2.267-1.521-3l-.74.751c.697.551 1.141 1.354 1.141 2.249s-.444 1.699-1.141 2.249zm-16.479 1.499l-.741.753c-1.395-1.101-2.28-2.707-2.28-4.501s.885-3.399 2.28-4.5l.741.752c-1.163.918-1.902 2.256-1.902 3.748s.739 2.831 1.902 3.748zm.338-3.748c0-.896.443-1.698 1.141-2.249l-.74-.751c-.931.733-1.521 1.805-1.521 3 0 1.196.59 2.267 1.521 3l.74-.751c-.697-.55-1.141-1.353-1.141-2.249zm16.641 14.501c0 2.209-3.581 4-8 4s-8-1.791-8-4c0-1.602 1.888-2.98 4.608-3.619l1.154 1.824c-.401.068-.806.135-1.178.242-3.312.949-3.453 2.109-.021 3.102 2.088.603 4.777.605 6.874-.001 3.619-1.047 3.164-2.275-.268-3.167-.296-.077-.621-.118-.936-.171l1.156-1.828c2.723.638 4.611 2.016 4.611 3.618z"/></svg>', { size: { w: 56, h: 56 } });

    // Create a marker using the previously instantiated icon:
    var marker = new H.map.Marker(LocationOfMarker, { icon: pngIcon });

    // Add the marker to the map:
    map.addObject(marker);
   
    // Optionally, 
    //Show the marker in the center of the map
    map.setCenter(LocationOfMarker)

    // This will act as a cleanup to run once this hook runs again.
    // This includes when the component un-mounts
    return () => {
      map.dispose();
    };
  }, [props.gps]); // This will run this hook every time this ref is updated
  return <div style={ { width: "100%", height: "500px" } }  ref={mapRef}  />;
};
export default Map;

Search.jsの作成

以下のファイルを追加してください。

Search.js
import * as React from 'react';
import { useState } from 'react';

const Search = (props) => {
  const [text,setText] = useState("");
  const [addText,setAddText] = useState("");

  const apikey = props.apikey;
  const H = window.H;
  const platform = new H.service.Platform({
      apikey: apikey
  });
  const service = platform.getSearchService();
  const onClickSearch = () => {
      service.geocode({
        q: text,
        limit: 1
      }, (result) => {
        result.items.forEach((item) => {
          console.log(item.position);
          props.onGeocodeResult(item.position);
        });
      }, alert);
    setText("");
  }


  return (
    <div>
        <input type="text" name="query" id="query" value={text}
          onChange={(event)=>setText(event.target.value)}
          placeholder="Search location" />
        <button onClick={onClickSearch}>Search</button>
    </div>
  );
};
export default Search;

create-react-appで作成された不要なファイルを削除すると、プロジェクトファイルの構成は以下のようになります。
image.png

プロジェクトの実行

shell command
npm start

(以下は、東京=>大阪=>福岡=>札幌と確認しています。)
完成イメージ.gif

おわりに

いかがでしたでしょうか?今回はHERE Maps API for JavascriptをReactコンポーネント化するサンプルを紹介しました。以前の記事で紹介しましたが、今回紹介しましたコードをベースに、HERE Geocoding & Search API以外にもクラスタリング機能やHERE Routing APIなども容易に実装することが可能となります。ぜひ参考にしていただければ幸いです。ここまで読んでいただいてありがとうございました。

2
1
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
1