View on GitHub

Cviceni-React-mapy

Úkol na procvičení práce s mapou Mapbox v Reactu.

Cvičení: React mapy

Dokumentace react-map-gl: visgl.github.io/react-map-gl/docs

demo

Zadání

  1. Založ nový projekt pomocí create-czechitas-app.

  2. Vytvoř v src složku components a v ní přichystej komponentu Mapa.

    import React from 'react'
    
    export const Mapa = () => {
    	return <>Mapa</>
    }
    
  3. Přidej mapu na stránku.

  4. Koukni do prohlížeče a zkontroluj, že se ti na stránce vypisuje text Mapa.

  5. Nainstaluj závislost pro mapy přes příkaz npm install react-map-gl@5.

  6. Mapbox vyžaduje import vlastních css. Přidej do komponenty import 'mapbox-gl/dist/mapbox-gl.css'.

  7. Zobraz základní mapu.

    1. Přidej do komponenty stav, který bude reprezentovat střed mapy a přiblížení. Nezapomeň useState importovat.

      const [viewport, setViewport] = useState({
      	latitude: 50.087543262674856,
      	longitude: 14.421045443793917,
      	zoom: 15,
      })
      
    2. Naimportuj ReactMapGL pomocí import ReactMapGL from 'react-map-gl'.

    3. Komponentu ReactMapGL vykresli.

      return (
      	<ReactMapGL
      		{...viewport}
      		width="100%"
      		height={400}
      		onViewportChange={(nextViewport) => setViewport(nextViewport)}
      	></ReactMapGL>
      )
      
    4. Komponentě ReactMapGL přidej prop mapStyle s následujícím objektem nastavujícím mapové podklady ze Seznam map.

      {
         version: 8,
         sources: {
            'raster-tiles': {
               type: 'raster',
               tiles: ['https://mapserver.mapy.cz/base-m/{z}-{x}-{y}'],
               tileSize: 256,
               attribution:
                  'Mapové podklady od <a target="_top" rel="noopener" href="https://mapy.cz/">Seznam.cz</a> a <a target="_top" rel="noopener" href="http://openstreetmap.org">OpenStreetMap</a>.',
            },
         },
         layers: [
            {
               id: 'simple-tiles',
               type: 'raster',
               source: 'raster-tiles',
               minzoom: 0,
               maxzoom: 18,
            },
         ],
      }
      
    5. Vyzkoušej, že se ti mapa zobrazí a ukazuje Staroměstské náměstí v Praze.

  8. Najdi gps souřadnice nejbližší Czechitas pobočky a nastav je jako výchozí střed mapy. Adresu zjistíš třeba ze stránek czechitas.cz. Z webu gps-coordinates.net pak vytáhni konkrétní souřadnice latitude a longitude (zeměpisnou šířku a délku).

  9. Přidej na mapu Marker, který bude značit polohu této pobočky.

    1. Z webu iconmonstr.com stáhni ikonku špendlíku a ulož ji do složky src/img pod názvem spendlik.svg.

    2. Přidej do komponenty Mapa import Markeru.

      import ReactMapGL, { Marker } from 'react-map-gl'
      
    3. Marker přidej jako potomka <ReactMapGL>. Nastav mu vlastnosti latitude a longitude. Použij hodnoty z gps-coordinates.net.

      <Marker latitude={…} longitude={…}>
      	Czechitas
      </Marker>
      
    4. Místo textu Czechitas vlož do Markeru obrázek špendlíku.

      import spendlikUrl from '../img/spendlik.svg'
      
      <img src={spendlikUrl} width={50} height={50} alt="Czechitas" />
      
    5. Všimni si, že špendlík je zapíchnutý o kousek vedle a jeho poloha se při přibližování a oddalování mapy (třeba kolečkem myši) lehce mění. Je to tím, že Mapbox neví, kde má ikonka hrot. Nastav Markeru vlastnosti offsetLeft a offsetTop. offsetLeft={-25} říká, že hrot je po ose x posunutý o 25 pixelů od levého horního rohu. offsetTop={-50} pak o 50 pixelů po ose y. Zkontroluj, že se špendlík už drží na správném místě.

  10. Označ Marker Popupem.

    1. Doplň do importů { Popup } z react-map-gl.

    2. Přidej Popup bublinu vedle Markeru.

      <Marker></Marker>
      <Popup
      	latitude={50.0833715}
      	longitude={14.4252452}
      >
      	Czechitas
      </Popup>
      
    3. Gps souřadnice dej stejné jako u Markeru.

    4. Popup má také vlastnost offsetTop. Nastav ji tak, aby bublina nepřekrývala špendlík, byla nad ním.

  11. Zobraz Popup až po kliknutí na Marker.

    1. Přidej stavovou proměnnou.

      const [popupOtevren, setPopupOtevren] = useState(false)
      
    2. Vykresli Popup jen v případě, že popupOtevren je true.

      {popupOtevren && <Popup  />}
      
    3. Obal obrázek v Markeru tlačítkem, které na klik upraví stav.

      <button onClick={() => setPopupOtevren(true)}><img … /></button>
      
    4. Vyzkoušej, že kliknutí opravdu otevírá Popup.

    5. Po kliknutí na křížek v pravém horním rohu Popup zavři. Přidej Popupu posluchač onClose.

      <Popup
      …
      onClose={() => setPopupOtevren(false)}
      >
      
    6. Vyzkoušej.

    7. Dostyluj button v Markeru, aby byl nenápadný.

      1. Dej mu className="marker-button".

      2. Vytvoř v components soubor marker-button.css.

      3. Přidej do něj styly a soubor v komponentě naimportuj.

        .marker-button {
        	padding: 0;
        	border: none;
        	background-color: transparent;
        	display: flex;
        	outline: none;
        	cursor: pointer;
        }
        
  12. Zobraz na mapě více Markerů.

    1. Přidej do složky src/img obrázek bagru jako bagr.svg a informační ikonky jako info.svg.

    2. Importuj je v komponentě Mapa.

      import bagrUrl from '../img/bagr.svg'
      import infoUrl from '../img/info.svg'
      
    3. Přichystej si na začátku komponenty pole objektů.

      const mista = [
      	{
      		id: 1,
      		ikonaUrl: bagrUrl,
      		latitude: 50.08415631476569,
      		longitude: 14.423472469019359,
      	},
      	{
      		id: 2,
      		ikonaUrl: infoUrl,
      		latitude: 50.08140252219053,
      		longitude: 14.425123690476866,
      	},
      	{
      		id: 3,
      		ikonaUrl: infoUrl,
      		latitude: 50.08315119880879,
      		longitude: 14.42713937555392,
      	},
      	{
      		id: 4,
      		ikonaUrl: bagrUrl,
      		latitude: 50.08147136893371,
      		longitude: 14.427310912961879,
      	},
      ]
      
    4. Představme si, že třeba ikonky bargu budou symbolizovat neprůchodná místa, kde se zrovna staví a ikonky informací budou místa s info stánky. Uprav gps souřadnice tak, aby odpovídali místům v okolí pobočky.

    5. Pomocí mista.map vykresli všechny nové špendlíky na mapě.

      {mista.map((misto) => (
      	<Marker
      		key={misto.id}
      		latitude={misto.latitude}
      		longitude={misto.longitude}
      		offsetLeft={-15}
      		offsetTop={-15}
      	>
      		<img src={misto.ikonaUrl} width={30} height={30} alt="" />
      	</Marker>
      ))}
      
  13. Přidej filtrování pro zvýraznění informačních stánků, skrytí bagrů.

    1. Přidej stavovou proměnnou.

      const [bargrySkryty, setBargrySkryty] = useState(false)
      
    2. Pod </ReactMapGL> přidej tlačítko, které bude měnit (togglovat) stav bargrySkryty z false na true a obráceně. Return může vracet jen jeden prvek. Budeš muset <ReactMapGL> a <button> obalit React fragmentem return <>…</>.

      <button onClick={() => setBargrySkryty(!bargrySkryty)}>
      	{bargrySkryty ? 'zobrazit' : 'skrýt'} bagry
      </button>
      
    3. K výpisu mista přidej filtr, který bude skrývat bagry.

      mista
      	.filter((misto) => bargrySkryty === false || misto.ikonaUrl !== bagrUrl)
      	.map()
      
  14. Přidej na stránku tlačítka pro ovládání přiblížení.

    1. Importuj { NavigationControl } z react-map-gl.

    2. Mezi potomky <ReactMapGL> přidej div a v něm NavigationControl.

      <div className="ovladani">
      	<NavigationControl />
      </div>
      
    3. Všimni si třídy ovladani. Vytvoř soubor ovladani.css, importuj ho a napiš do něj styly pro ovládací panel.

      .ovladani {
      	position: absolute;
      	top: 10px;
      	right: 10px;
      }
      
      .ovladani > * {
      	position: static !important;
      }
      
  15. Přidej tlačítko pro zaměření aktuální polohy uživatele.

    1. Importuj { GeolocateControl } z react-map-gl.

    2. Vedle vykreslení <NavigationControl /> přidej <GeolocateControl />.

    3. Vyzkoušej, co tlačítko po kliknutí dělá. Poprvé by se tě mělo zeptat na přístup k polozek a po chvíli pak zaměřit mapu na místo, kde zrovna jsi. Na počítači může být poloha značně nepřesná.

Bonus

  1. Zkus z mapy.cz použít jiný mapový podklad.

    1. Může to být trochu detektivní práce. Na Seznam mapách si přepni zobrazení třeba na zeměpisné zobrazení. Koukni do vývojářských nástrojů, co se stahuje za obrázky.

    2. Pravděpodobně najdeš nějakou adresu v podobě https://mapserver.mapy.cz/zemepis-m/11-1106-696, kde poslední tři čísla jsou parametry konkrétní dlaždice. Nahraď je za {z}-{x}-{y} a použij v prop mapStyle.

  2. Umožni uživateli přesouvat Marker kliknutím na mapu.

    1. Stáhni si obrázek kotvy a ulož ho do složky src/img s názvem kotva.svg.

    2. Přidej do komponenty Mapa stav, který bude reprezentovat polohu kotvy.

      const [polohaKotvy, setPolohaKotvy] = useState({
      	latitude: 50.082027979423236,
      	longitude: 14.426295971100695,
      })
      
    3. Gps souřadnice opět uprav na něco v okolí.

    4. Vykresli do mapy nový Marker s ikonou kotvy a polohou podle stavu polohaKotvy.

      <Marker {...polohaKotvy} offsetLeft={-25} offsetTop={-50}>
      	<img src={kotvaUrl} width={50} height={50} alt="kotva" />
      </Marker>
      
    5. Do <ReactMapGL> přidej poslouchač události onClick, který bude přenastavovat stav polohaKotvy. Všimni si, že událost (event) má vlastnost event.lngLat, pole o dvou prvcích v pořadí longitude, latitude.

      onClick={(event) =>
      	setPolohaKotvy({
      		latitude: event.lngLat[1],
      		longitude: event.lngLat[0],
      	})
      }
      
  3. Umožni uživateli přesouvat kotvu tahem myši.

    1. Protože obrázky mají na tah myši speciální chování, budeš ho muset potlačit stylem pointer-events: none. Ideálně by takový styl mohl být ve vlastním souboru css, ale pro teď si práci můžeš zjednodušit inline stylováním přímo v jsx.

      <img
      	src={kotvaUrl}
      	width={50}
      	height={50}
      	alt="kotva"
      	style=
      />
      
    2. Nastav Markeru s kotvou vlastnou draggable.

      <Marker … draggable>
      
    3. Vyzkoušej si kotvu tahem myši na mapě přemístit. Všimni si, že po puštění tlačítka Marker vždy skočí do původní polohy.

    4. Na událost onDragEnd ulož do polohaKotvy nové umístění.

      <Marker
      	…
      	draggable
      	onDragEnd={(event) =>
      		setPolohaKotvy({
      			latitude: event.lngLat[1],
      			longitude: event.lngLat[0],
      		})
      	}
      >
      
    5. Nyní by kotva po přesunutí tahem myši měla zůstat na novém místě. Vyzkoušej si to. Hodnotu polohaKotvy můžeš na nějakém projektu případně využít pro zadávání polohy uživatelem při ukládání formuláře s mapou.