JavaScript in 60 Seconds: Working with a Map (Geolocation API, Leaflet.js, Nominatim)





Good day, friends!



In this small tutorial, we will complete three simple tasks together with you:



  • Using the Geolocation API and Leaflet.js, determine the user's current location and display it on the map
  • We implement animated transition between cities
  • We implement switching between addresses with preliminary obtaining the name of the object and its coordinates


The project code is here .



You can play with the code here:







Determine the current location of the user



The Geolocation API allows a user to provide a web application with their location data. The application uses the Geolocation.getCurrentPosition () method to request this data . This method takes one required and two optional parameters: success is a callback function that receives a Position object when permission is granted, error is a callback function that receives a PositionError object when access is denied, and options is a settings object. This is how it looks in code:



navigator.geolocation.getCurrentPosition(success, error, {
  //  
  enableHighAccuracy: true
})

function success({ coords }) {
  //    
  const { latitude, longitude } = coords
  const position = [latitude, longitude]
  console.log(position) // [, ]
}

function error({ message }) {
  console.log(message) //      PositionError: User denied Geolocation
}

      
      





Displaying the user's location on the map



We will use Leaflet.js as the map . This service is an alternative to Google Maps and OpenStreetMap, inferior to them in functionality, but captivating with the simplicity of the interface. We create a markup in which we connect the styles and the map script:



<head>
  <!--   -->
  <link
      rel="stylesheet"
      href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
      integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
      crossorigin=""
    />
    <!--   -->
    <script
      src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
      integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
      crossorigin=""
    ></script>
    <!--   -->
    <link rel="stylesheet" href="style.css" />
</head>
<body>
  <!--    -->
  <div id="map"></div>
  <!--     -->
  <button id="my_position">My Position</button>
  <!--  - -->
  <script src="script.js" type="module"></script>
</body>

      
      





Add minimal styles (style.css):



* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  min-height: 100vh;
  display: grid;
  place-content: center;
  place-items: center;
  background-color: rgb(241, 241, 241);
}

#map {
  width: 480px;
  height: 320px;
  border-radius: 4px;
  box-shadow: 0 0 1px #222;
}

button {
  padding: 0.25em 0.75em;
  margin: 1em 0.5em;
  cursor: pointer;
  user-select: none;
}

      
      





Create a module map.js with the following content:



//       
//      
let map = null
let marker = null

//    -     
//  ,    (tooltip)
export function getMap(position, tooltip) {
  //     
  if (map === null) {
    //  ,   setView -   (zoom)
    map = L.map('map').setView(position, 15)
  } else return

  // -  
  //      
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution:
      'ยฉ <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map)

  //    
  L.marker(position).addTo(map).bindPopup(tooltip).openPopup()
}

      
      





Finally, we create script.js:



//  
import { getMap } from './map.js'

//       
document.getElementById('my_position').onclick = () => {
  navigator.geolocation.getCurrentPosition(success, error, {
    enableHighAccuracy: true
  })
}

function success({ coords }) {
  const { latitude, longitude } = coords
  const currentPosition = [latitude, longitude]
  //  ,      
  getMap(currentPosition, 'You are here')
}

function error({ message }) {
  console.log(message)
}

      
      





Open index.html in a browser, click on the button, grant permission to receive location data, see our position on the map.







Fine. Moving on.



Animated transition between cities



Suppose we have an object with three cities (Moscow, St. Petersburg, Yekaterinburg) and their coordinates (db / cities.json):



{
  "Moscow": {
    "lat": "55.7522200",
    "lon": "37.6155600"
  },
  "Saint-Petersburg": {
    "lat": "59.9386300",
    "lon": "30.3141300"
  },
  "Ekaterinburg": {
    "lat": "56.8519000",
    "lon": "60.6122000"
  }
}

      
      





We need to implement smooth switching between these cities on the map.



Add a container for cities to the markup:



<div id="cities"></div>

      
      





Rewriting script.js:



import { getMap } from './map.js'

//    
const $cities = document.getElementById('cities')

;(async () => {
  //    
  const response = await fetch('./db/cities.json')
  const cities = await response.json()
  //  
  for (const city in cities) {
    //  
    const $button = document.createElement('button')

    //    -  
    $button.textContent = city

    //    
    const { lat, lon } = cities[city]

    //   ,   
    //   data-
    $button.dataset.city = city
    $button.dataset.lat = lat
    $button.dataset.lon = lon

    //    
    $cities.append($button)
  }
})()

//   
$cities.addEventListener('click', ({ target }) => {
  //     
  if (target.tagName !== 'BUTTON') return

  //   ,     data-
  const { city, lat, lon } = target.dataset
  const position = [lat, lon]
  //  ,      
  getMap(position, city)
})

      
      





Let's also change map.js a bit:



let map = null
let marker = null

export function getMap(position, tooltip) {
  if (map === null) {
    map = L.map('map').setView(position, 15)
  } else {
    //    
    map.flyTo(position)
  }

  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution:
      'ยฉ <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map)

  //   
  if (marker) {
    map.removeLayer(marker)
  }
  marker = new L.Marker(position).addTo(map).bindPopup(tooltip).openPopup()
}

      
      





Open up index.html. When you press the first button, we immediately get the position and name of the city. When you press the second and subsequent buttons, we smoothly move between cities.







Smooth switching between addresses



Suppose we have three objects with names and addresses (db / addresses.json):



{
  " ": " , 2",
  "   ": " , 46",
  "-": " , 97"
}

      
      





We need to implement switching between these objects on the map. But how can we do this without coordinates? No way. Therefore, we somehow need to get these coordinates. To do this, we will use the Nominatim service from OpenStreetMap. For information on how to correctly form a query string, see here . I will demonstrate just one of the possible options.



So, we create a container for addresses in markup:



<div id="addresses"></div>

      
      





Rewriting script.js:



//    
const $addresses = document.getElementById('addresses')

;(async () => {
  //    
  const response = await fetch('./db/addresses.json')
  const addresses = await response.json()

  //   
  for (const place in addresses) {
    //  
    const $button = document.createElement('button')

    $button.textContent = place

    //  
    const address = addresses[place]

    //   
    const query = address.replace(
      /([--]+)\s([--]+),\s([0-9--]+)/,
      '$3+$1+$2,+'
    )
    // , , 2++,+

    //     data-
    $button.dataset.address = address
    $button.dataset.query = query

    $addresses.append($button)
  }
})()

//   
$addresses.addEventListener('click', async ({ target }) => {
  if (target.tagName !== 'BUTTON') return

  //    data-
  const { address, query } = target.dataset

  //    
  const response = await fetch(
    `https://nominatim.openstreetmap.org/search?q=${query}&format=json&limit=1`
  )
  // format -  , limit -    

  //  ,   
  const { display_name, lat, lon } = (await response.json())[0]

  //   
  const name = display_name.match(/[--\s(ยซ\-ยป)]+/)[0]

  const position = [lat, lon]

  //  
  const tooltip = `${name}<br>${address}`

  //  
  getMap(position, tooltip)
})

      
      





Open up index.html. When you press the first button, we immediately get the position and name of the theater. By pressing the second and subsequent buttons, we smoothly move between theaters.







Cool. Everything works as expected.



On this, let me take my leave. I hope you found something interesting for yourself. Thank you for your attention and have a nice day.



All Articles