Adicionar um mapa do Google a um app do React

1. Antes de começar

Neste codelab, você vai aprender tudo o que precisa para começar a usar a biblioteca vis.gl/react-google-map (link em inglês) da API Google Maps JavaScript, que permite adicionar um mapa do Google a um app do React. Também vamos mostrar como configurar e carregar a API Maps JavaScript, mostrar seu primeiro mapa, trabalhar com marcadores e o clustering de marcadores, fazer desenhos em mapas e lidar com interações dos usuários.

Pré-requisitos

  • Conhecimento básico de JavaScript, HTML e CSS

O que você vai aprender

  • Como começar a usar a biblioteca vis.gl/react-google-map (link em inglês) para a Plataforma Google Maps.
  • Como carregar a API Maps JavaScript de forma declarativa.
  • Como carregar um mapa em um app do React.
  • Como usar marcadores comuns/personalizados e o clustering de marcadores.
  • Como trabalhar com o sistema de eventos da API Maps JavaScript nas interações com os usuários.
  • Como controlar o mapa de forma dinâmica.
  • Como fazer desenhos no mapa.

O que é necessário

  • Uma conta do Google Cloud com o faturamento ativado.
  • Uma chave de API da Plataforma Google Maps com a API Maps JavaScript ativada.
  • Node.js instalado no seu computador.
  • Um editor de texto ou ambiente de desenvolvimento integrado.
  • A biblioteca vis.gl/react-google-map (link em inglês) para a API Google Maps JavaScript.
  • A biblioteca googlemaps/markerclusterer.

Configurar a Plataforma Google Maps

Caso você ainda não tenha uma conta do Google Cloud Platform e um projeto com faturamento ativado, veja como criá-los no guia da Plataforma Google Maps.

  1. No Console do Cloud, clique no menu suspenso do projeto e selecione o projeto que você quer usar neste codelab.

  1. Ative as APIs e os SDKs da Plataforma Google Maps necessários para este codelab no Google Cloud Marketplace. Para fazer isso, siga as etapas descritas neste vídeo ou nesta documentação.
  2. Gere uma chave de API na página Credenciais do Console do Cloud. Siga as etapas indicadas neste vídeo ou nesta documentação. Todas as solicitações feitas à Plataforma Google Maps precisam de uma chave de API.

2. Começar a configuração

Baixar o projeto inicial

Para baixar o modelo do projeto inicial e o código da solução:

  1. Baixe ou crie uma ramificação do repositório do GitHub (link em inglês). O projeto inicial está no diretório /starter e inclui a estrutura básica de arquivos necessária para concluir o codelab. Você vai trabalhar apenas no diretório /starter/src.
git clone https://github.com/googlemaps-samples/codelab-maps-platform-101-react-js.git

Se preferir, clique no botão abaixo para baixar o código-fonte.

  1. Acesse o diretório /starter e instale o npm para ter todas as dependências necessárias que constam no arquivo package.json.
cd starter && npm install
  1. Ainda no diretório /starter:
npm start

O projeto inicial foi configurado para você usar o servidor de desenvolvimento do Vite, que compila e executa o código escrito localmente. Esse servidor também recarrega automaticamente o app no navegador sempre que você altera o código. Se você acessar o link fornecido no fim do processo de build, o resultado será uma página da Web com as palavras: "Hello World!"

  1. Para executar o código completo da solução, vá até o diretório /solution e siga as mesmas etapas de configuração.

3. Carregar a API Maps JavaScript

A base de uso da Plataforma Google Maps na Web é a API Maps JavaScript. Essa API disponibiliza uma interface JavaScript para usar todos os recursos da

Plataforma Google Maps, incluindo mapa, marcadores, ferramentas de desenho e outros serviços da Plataforma Google Maps, como o Places.

Para carregar a API Maps JavaScript com a estrutura do React, você precisa usar o componente APIProvider que faz parte da biblioteca vis.gl/react-google-map. É possível adicionar o componente em qualquer nível do app (geralmente fica próximo à parte de cima), e ele renderiza todos os componentes secundários sem modificações. Além de trabalhar com o carregamento da API Maps JavaScript, ele dá informações contextuais e funções para outros componentes e hooks da biblioteca. O componente APIProvider já está incluído na biblioteca vis.gl/react-google-map, então foi instalado quando você executou o npm install (links em inglês).

Para usar o componente APIProvider:

  1. Abra o arquivo /src/app.tsx. É com ele que você vai trabalhar neste codelab.
  2. Na parte de cima do arquivo, importe a classe APIProvider da biblioteca @vis.gl/react-google-maps (link em inglês):
import {APIProvider} from '@vis.gl/react-google-maps';
  1. Na definição da função do App, defina o parâmetro apiKey do componente APIProvider usando a chave de API criada na etapa anterior, além da propriedade onLoad com uma mensagem de registro do console:
<APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>

O componente APIProvider usa várias propriedades que especificam opções para carregar a API Maps JavaScript, incluindo sua chave de API da Plataforma Google Maps, a versão da API que você quer carregar e qualquer outra biblioteca fornecida pela API Maps JavaScript que você também quer carregar.

A chave de API do Google Maps é a única propriedade necessária para que o APIProvider funcione. Incluímos a propriedade onLoad apenas como exemplo. Para mais informações, confira Componente <APIProvider> (link em inglês):

O arquivo app.tsx vai ficar assim:

import React from 'react';
import {createRoot} from "react-dom/client";
import {APIProvider} from '@vis.gl/react-google-maps';

const App = () => (
 <APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
   <h1>Hello, world!</h1>
 </APIProvider>
);

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

export default App;

Se tudo der certo, você vai ver a instrução console.log no console do navegador. Agora que a API Maps JavaScript está carregada, você pode renderizar o mapa dinâmico na próxima etapa.

4. Mostrar um mapa

É hora de mostrar seu primeiro mapa.

A parte mais usada da API Maps JavaScript é o google.maps.Map, que é a classe que permite criar e manipular instâncias de mapas. A biblioteca vis.gl/react-google-map (link em inglês) une essa classe no componente Map. Importe as classes Map e MapCameraChangedEvent.

import {APIProvider, Map, MapCameraChangedEvent} from '@vis.gl/react-google-maps';

O componente Map aceita várias configurações do mapa. Neste codelab, vamos usar as configurações a seguir:

  • defaultCenter: define a latitude e a longitude para o centro do mapa.
  • defaultZoom: define o nível de zoom inicial do mapa.
  • Para mostrar um mapa, insira o código abaixo entre as tags do componente APIProvider para centralizar em Sidney (Austrália) e insira o nível de zoom 13, que é o ideal para exibir o centro da cidade:
 <Map
      defaultZoom={13}
      defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
      onCameraChanged={ (ev: MapCameraChangedEvent) =>
        console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
      }>
</Map>

O resultado será um mapa de Sydney no navegador:

761c8c51c6631174.png

Nesta seção, você mostrou um mapa com o componente <Map> e definiu o status inicial dele com propriedades, além de ter usado eventos para capturar mudanças na câmera.

O arquivo app.tsx vai ficar assim:

import React from 'react';
import {createRoot} from "react-dom/client";
import {APIProvider, Map, MapCameraChangedEvent} from '@vis.gl/react-google-maps';

const App = () => (
 <APIProvider apiKey={'Your API key here'} onLoad={() => console.log('Maps API has loaded.')}>
   <Map
      defaultZoom={13}
      defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
      onCameraChanged={ (ev: MapCameraChangedEvent) =>
        console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
      }>
   </Map>
 </APIProvider>
);

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

export default App;

5. Adicionar a Estilização de mapas baseada na nuvem

Essa estilização é necessária para usar os Marcadores Avançados, que servem para escolher pontos de interesse no seu mapa de Sidney.

Com a Estilização de mapas baseada na nuvem, você pode personalizar o estilo do mapa.

Criar um ID do mapa

Se você ainda não criou um ID do mapa com um estilo associado a ele, consulte o guia de IDs do mapa para concluir as seguintes etapas:

  1. Crie um ID do mapa.
  2. Associe um ID do mapa a um estilo.

Se quiser usar o ID do mapa que você criou, defina a propriedade mapId do componente <Map>:

<Map
    defaultZoom={13}
    defaultCenter={ { lat: -33.860664, lng: 151.208138 } }
    mapId='YOUR_MAP_ID'
    onCameraChanged={ (ev: MapCameraChangedEvent) =>
        console.log('camera changed:', ev.detail.center, 'zoom:', ev.detail.zoom)
    }>
</Map>

O estilo que você selecionou no mapa vai aparecer.

6. Adicionar marcadores ao mapa

A API Maps JavaScript oferece inúmeras possibilidades para os desenvolvedores, e a ação mais comum é colocar marcadores no mapa. Eles mostram pontos específicos e são elementos comuns da interface para lidar com as interações dos usuários. Quem já usou o Google Maps conhece o marcador padrão, que tem esta aparência:

d9a6513b82a2f1e1.png

Para usar o componente AdvancedMarker e colocar marcadores no mapa:

  1. Crie uma lista de objetos que representem pontos de interesse na região de Sidney e coloque logo abaixo das importações, fora da definição do App:
type Poi ={ key: string, location: google.maps.LatLngLiteral }
const locations: Poi[] = [
  {key: 'operaHouse', location: { lat: -33.8567844, lng: 151.213108  }},
  {key: 'tarongaZoo', location: { lat: -33.8472767, lng: 151.2188164 }},
  {key: 'manlyBeach', location: { lat: -33.8209738, lng: 151.2563253 }},
  {key: 'hyderPark', location: { lat: -33.8690081, lng: 151.2052393 }},
  {key: 'theRocks', location: { lat: -33.8587568, lng: 151.2058246 }},
  {key: 'circularQuay', location: { lat: -33.858761, lng: 151.2055688 }},
  {key: 'harbourBridge', location: { lat: -33.852228, lng: 151.2038374 }},
  {key: 'kingsCross', location: { lat: -33.8737375, lng: 151.222569 }},
  {key: 'botanicGardens', location: { lat: -33.864167, lng: 151.216387 }},
  {key: 'museumOfSydney', location: { lat: -33.8636005, lng: 151.2092542 }},
  {key: 'maritimeMuseum', location: { lat: -33.869395, lng: 151.198648 }},
  {key: 'kingStreetWharf', location: { lat: -33.8665445, lng: 151.1989808 }},
  {key: 'aquarium', location: { lat: -33.869627, lng: 151.202146 }},
  {key: 'darlingHarbour', location: { lat: -33.87488, lng: 151.1987113 }},
  {key: 'barangaroo', location: { lat: - 33.8605523, lng: 151.1972205 }},
];

const App = () => (
  ...
);
  1. Personalize os alfinetes com o elemento <Pin>:
<Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
  1. Crie um componente personalizado para renderizar sua lista com Marcadores Avançados e insira abaixo da definição do App:
const App = () => (
  ...
);

const PoiMarkers = (props: {pois: Poi[]}) => {
  return (
    <>
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          key={poi.key}
          position={poi.location}>
        <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
        </AdvancedMarker>
      ))}
    </>
  );
};
  1. Adicione o componente PoiMarkers como um elemento filho do componente Map:
<Map
  ... map properties ...
>
  <PoiMarkers pois={locations} />
</Map>
  1. Adicione Pin e AdvancedMarker às importações.
import {
  APIProvider,
  Map,
  AdvancedMarker,
  MapCameraChangedEvent,
  Pin
} from '@vis.gl/react-google-maps';

Os Marcadores Avançados personalizados vão aparecer no mapa:

98d12a994e12a2c1.png

7. Ativar o clustering de marcadores

Ao usar muitos marcadores próximos entre em si ou em grandes quantidades, eles podem ficar sobrepostos ou muito perto uns dos outros, o que causa uma experiência negativa para o usuário. Por exemplo, depois de criar os marcadores na última etapa, isto pode ter acontecido:

98d12a994e12a2c1.png

A solução neste caso é o clustering de marcadores, outro recurso comum que agrupa marcadores próximos em um único ícone e muda dependendo do nível de zoom. Veja como:

3da24a6b737fe499.png

O algoritmo para clustering de marcadores divide a área visível do mapa em uma grade e agrupa os ícones que estão na mesma célula. Felizmente, você não precisa se preocupar com isso porque a equipe da Plataforma Google Maps criou uma biblioteca muito conveniente de utilitários de código aberto, a MarkerClustererPlus, que faz tudo de forma automática. Você pode consultar a origem da biblioteca MarkerClustererPlus no GitHub (link em inglês).

Para ativar o clustering de marcadores:

  1. Na parte de cima do arquivo app.tsx, vamos atualizar e incluir na biblioteca os tipos compatíveis e as importações.
import React, {useEffect, useState, useRef, useCallback} from 'react';
import {createRoot} from "react-dom/client";
import {
    APIProvider,
    Map,
    AdvancedMarker,
    MapCameraChangedEvent,
    useMap,
    Pin
  } from '@vis.gl/react-google-maps';
  import {MarkerClusterer} from '@googlemaps/markerclusterer';
  import type {Marker} from '@googlemaps/markerclusterer';

Para o projeto modelo deste codelab, a biblioteca de utilitários do MarkerClustererPlus já está incluída nas dependências declaradas no arquivo package.json, ou seja, você já a instalou quando executou npm install no início do codelab.

  1. Crie variáveis para o MarkerClusterer e os elementos compatíveis no componente PoiMarkers.

Você precisa que uma instância do mapa inicialize o MarkerClusterer. Faça isso no hook do useMap():

const map = useMap();
  1. Crie uma lista de marcadores armazenados em uma variável de estado:
const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
  1. Armazene o clusterer como uma referência:
const clusterer = useRef<MarkerClusterer | null>(null);
  1. No componente PoiMarkers, crie uma instância do MarkerClusterer e transfira a instância do componente Map onde você quer mostrar o clustering de marcadores:
 useEffect(() => {
    if (!map) return;
    if (!clusterer.current) {
      clusterer.current = new MarkerClusterer({map});
    }
  }, [map]);
  1. Crie um efeito que atualize o clusterer assim que a lista de marcadores mudar:
useEffect(() => {
    clusterer.current?.clearMarkers();
    clusterer.current?.addMarkers(Object.values(markers));
  }, [markers]);
  1. Crie uma função para cunhar as referências dos marcadores novos:
const setMarkerRef = (marker: Marker | null, key: string) => {
    if (marker && markers[key]) return;
    if (!marker && !markers[key]) return;

    setMarkers(prev => {
      if (marker) {
        return {...prev, [key]: marker};
      } else {
        const newMarkers = {...prev};
        delete newMarkers[key];
        return newMarkers;
      }
    });
  };
  1. Use este método no elemento AdvancedMarker para criar a referência de cada marcador.
<AdvancedMarker
  key={poi.key}
  position={poi.location}
  ref={marker => setMarkerRef(marker, poi.key)}
  >
    <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
</AdvancedMarker>

Os clusters de marcadores vão aparecer no seu mapa:

3da24a6b737fe499.png

Se você aumentar e diminuir o zoom, o MarkerClustererPlus vai renumerar e redimensionar automaticamente os clusters. Também é possível clicar no ícone de qualquer cluster para aumentar o zoom e ver todos os marcadores incluídos nele.

d5e75480e9abd3c7.png

Nesta seção, você importou e usou a biblioteca de utilitários de código aberto do MarkerClustererPlus para criar uma instância de MarkerClusterer. Com a ajuda do estado e das referências do React, essa instância agrupou de forma automática os marcadores criados na etapa anterior.

O componente PoiMarkers vai ficar assim:

const PoiMarkers = (props: { pois: Poi[] }) => {
  const map = useMap();
  const [markers, setMarkers] = useState<{[key: string]: Marker}>({});
  const clusterer = useRef<MarkerClusterer | null>(null);

  // Initialize MarkerClusterer, if the map has changed
  useEffect(() => {
    if (!map) return;
    if (!clusterer.current) {
      clusterer.current = new MarkerClusterer({map});
    }
  }, [map]);

  // Update markers, if the markers array has changed
  useEffect(() => {
    clusterer.current?.clearMarkers();
    clusterer.current?.addMarkers(Object.values(markers));
  }, [markers]);

  const setMarkerRef = (marker: Marker | null, key: string) => {
    if (marker && markers[key]) return;
    if (!marker && !markers[key]) return;

    setMarkers(prev => {
      if (marker) {
        return {...prev, [key]: marker};
      } else {
        const newMarkers = {...prev};
        delete newMarkers[key];
        return newMarkers;
      }
    });
  };

  return (
    <>
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          key={poi.key}
          position={poi.location}
          ref={marker => setMarkerRef(marker, poi.key)}
          >
            <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
        </AdvancedMarker>
      ))}
    </>
  );
};

Agora você vai aprender como lidar com interações dos usuários.

8. Adicionar interação do usuário

Agora seu mapa mostra alguns dos destinos turísticos mais famosos de Sydney. Nesta seção, você vai incluir algumas respostas a interações das pessoas usando o sistema de eventos da API Maps JavaScript para melhorar ainda mais a experiência no seu mapa.

A API Maps JavaScript oferece um sistema abrangente que usa manipuladores de eventos JavaScript para lidar com várias interações no código. Por exemplo, você pode criar listeners de eventos para acionar a execução do código quando houver interações dos usuários, como clicar no mapa e nos marcadores, mover o mapa, aumentar e diminuir o zoom e muito mais.

Siga as etapas abaixo para adicionar um listener de click aos marcadores e mover o mapa de forma programática para centralizar o marcador que foi clicado:

  1. Crie uma callback de manipulador de click.

No componente PoiMarkers, defina um manipulador de click com o useCallback() do React.

O evento click é acionado sempre que um usuário clica/toca em um marcador e retorna um evento como um objeto JSON com informações sobre o elemento da interface que recebeu o clique. Para melhorar a experiência do usuário no mapa, você pode processar o evento click e usar o respectivo objeto LatLng para ver a latitude e a longitude do marcador clicado.

Quando você souber a latitude e a longitude, passe essas informações à função panTo() integrada da instância do Map. Com isso, o mapa vai se mover um pouco e recentralizar no marcador que recebeu o clique. Basta adicionar este código à função de callback do manipulador de eventos:

const PoiMarkers = (props: { pois: Poi[] }) => {
...
const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
    if(!map) return;
    if(!ev.latLng) return;
    console.log('marker clicked:', ev.latLng.toString());
    map.panTo(ev.latLng);
  });
...
};
  1. Atribua os manipuladores de click aos marcadores.

Os elementos AdvancedMarker da biblioteca vis.gl/react-google-map (link em inglês) mostram duas propriedades que ajudam a processar os cliques:

  • clickable: se verdadeiro, será possível clicar no AdvancedMarker, o que vai acionar o evento gmp-click, com interações pensando na acessibilidade, por exemplo, a navegação pelo teclado usando as teclas de seta.
  • onClick: a função de callback acionada quando um evento click ocorre.
  1. Atualize a renderização do componente PoiMarkers para atribuir um manipulador de click a cada marcador:
return (
    <>
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          ... other properties ...
          clickable={true}
          onClick={handleClick}
          >
           ...
        </AdvancedMarker>
      ))}
    </>
  );
  1. Abra o navegador e clique nos marcadores. O mapa vai retornar automaticamente ao centro quando houver um clique no marcador.

Nesta seção, você usou o sistema de eventos do React para atribuir um manipulador de click a todos os marcadores no mapa, recuperar a latitude e longitude do marcador do evento de click disparado e usar isso para recentralizar o mapa sempre que um marcador receber um clique.

Falta apenas uma etapa. Agora, você vai melhorar ainda mais a experiência no mapa usando os recursos de desenho da API Maps JavaScript.

9. Desenhar no mapa

Até agora, você criou um mapa de Sydney que mostra marcadores dos destinos turísticos mais famosos e lida com interações dos usuários. Na última etapa deste codelab, você usa os recursos de desenho da API Maps JavaScript para adicionar outros recursos úteis à experiência no mapa.

Imagine que esse mapa será acessado pelos usuários que gostariam de conhecer Sydney. Um recurso útil seria ver um raio em torno de um marcador após clicar nele. Com isso, é possível quais outros destinos estão a uma curta distância do marcador selecionado.

A API Maps JavaScript inclui um conjunto de funções para desenhar formas no mapa, como quadrados, polígonos, linhas e círculos. A biblioteca vis.gl/react-google-map (link em inglês) disponibiliza esses recursos no React.

Agora você vai renderizar um círculo para mostrar um raio de 800 metros ao redor de um marcador após ele receber um clique.

O repositório inicial tem um componente personalizado para um elemento de circle, que você encontra no arquivo src/components/circle.tsx.

Para permitir desenhos no mapa:

  1. Atualize as importações para que incluam o componente de círculo fornecido.
import {Circle} from './components/circle'
  1. Crie uma variável de estado para o centro do círculo.

Capture o estado do centro do círculo no componente PoiMarkers. Defina o estado inicial como nulo, considerando que um círculo será renderizado apesar se tiver um local central válido (e um raio).

const PoiMarkers = (props: { pois: Poi[] }) => {
...
  const [circleCenter, setCircleCenter] = useState(null)
...
};
  1. Atualize o centro do círculo quando um evento de click for manipulado.

Chame o setCircleCenter com o local encontrado no objeto do evento:

const handleClick = useCallback((ev: google.maps.MapMouseEvent) => {
    ...
    setCircleCenter(ev.latLng);
  });

Essas funções da API Maps JavaScript oferecem diversas opções para um objeto desenhado aparecer no mapa. Para renderizar um raio circular, defina as propriedades do elemento de círculo, como cor e peso do traço, onde ele vai ficar centralizado, além do raio.

  1. Adicione um círculo à renderização e vincule o centro dele à variável de estado. A renderização vai ficar assim:
return (
    <>
      <Circle
          radius={800}
          center={circleCenter}
          strokeColor={'#0c4cb3'}
          strokeOpacity={1}
          strokeWeight={3}
          fillColor={'#3b82f6'}
          fillOpacity={0.3}
        />
      {props.pois.map( (poi: Poi) => (
        <AdvancedMarker
          key={poi.key}
          position={poi.location}
          ref={marker => setMarkerRef(marker, poi.key)}
          clickable={true}
          onClick={handleClick}
          >
            <Pin background={'#FBBC04'} glyphColor={'#000'} borderColor={'#000'} />
        </AdvancedMarker>
      ))}
    </>
  );
};

Pronto! Abra o navegador e clique em um dos marcadores. Você vai ver um raio circular renderizado ao redor dele:

d243587f4a9ec4a6.png

10. Parabéns!

Você criou seu primeiro app da Web com a biblioteca vis.gl/react-google-map (link em inglês) para a Plataforma Google Maps e aprendeu a carregar a API Maps JavaScript e um mapa, trabalhar com marcadores, controlar e desenhar no mapa, além de adicionar interações dos usuários.

Para ver o código pronto, confira o diretório /solutions.

Saiba mais