# Leaflet

[Leaflet ](https://leafletjs.com/)is an open-source JavaScript library for displaying interactive maps on the web or mobile. A simple and lightweight library that will enable you to display and visualise location data and build dynamic applications.&#x20;

## What you'll need

* OS Maps API and OS NGD API – Features added to an API project in the OS Data Hub with an API Key. See [Getting started with an API project](https://app.gitbook.com/s/oEzqrBzRGoJw7nQyLj1W/core-concepts/getting-started-with-an-api-project "mention") for more information.&#x20;
* A text editor like Visual Studio Code or Notepad to edit and save your HTML and JavaScript files.

{% stepper %}
{% step %}

### Set up your HTML file

* Create a new HTML file with a text editor (for example, Notepad, Visual Studio Code).
* Add the basic HTML structure to your file with a placeholder `<div>` for the map.&#x20;

{% code overflow="wrap" %}

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>OS NGD API – Features | Template (EPSG:3857) | Leaflet</title>
    
    <!--Add the Ordnance Survey Styling-->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/OrdnanceSurvey/os-api-branding@0.3.1/os-api-branding.css" />
    <script src="https://cdn.jsdelivr.net/gh/OrdnanceSurvey/os-api-branding@0.3.1/os-api-branding.js"></script>
    
    <!--Add the Leaflet libraries-->
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    
   
    <style>
        /* Set the map container size and style */
        body { margin: 0; padding: 0; }
        #map { position: absolute; top: 0; bottom: 0; width: 100%; }
    </style>
</head>
<body>
    
    <!--Create a div element to hold the map-->
    <div id="map"></div>
    
    <!--Add your Javascript code below--> 
    <script>
        // Your Javascript code will go here

    </script>

</body>
</html>
```

{% endcode %}

{% endstep %}

{% step %}

### Insert your API Key and OS NGD collection

* To enable access to OS APIs an API Key is required. Inside the `<script>` tag, add a variable called `apiKey`, replacing `'INSERT_API_KEY_HERE'` with the API Key from your project.
* Add a variable called `collectionID`, replacing `'INSERT_COLLECTIONID_HERE'` with the collection ID for the desired OS NGD feature type and version (for example, bld-fts-buildingpart-1).

```javascript
// Set API Key 
 const apiKey = 'INSERT_API_KEY_HERE';
 
 const collectionId= 'INSERT_COLLECTIONID_HERE';
```

{% endstep %}

{% step %}

### Add a basemap

* Define the configuration options for the map, defining `minZoom`, `maxZoom`, `center`, `zoom`, `maxBounds`, `attributionControl`.&#x20;
  * `minZoom` and `maxZoom`: Sets the minimum and maximum zoom level for the map. Users will not be able to go beyond these levels.&#x20;
  * `center`: Sets the initial centre point of the map.&#x20;
  * `zoom`: Sets the initial zoom level of the map.
  * `maxBounds`: Defines the maximum bounds and restricts panning the map.
  * `style`: Defines the style of the map, configured via a URL pointing at the style specified.
  * `attributionControl`: When set to 'false', it hides the attribution control which displays map credits.
* Initialize the map with the `id` of the `<div>` element and the configuration option defined in `mapOptions`.&#x20;
* Using the '`L.tileLayer`' method, specify the basemap layer for OS Maps API, which includes your API Key to load the tiles to your map. &#x20;

{% code overflow="wrap" %}

```javascript
// Initialize the map.
    const mapOptions = {
        minZoom: 7,
        maxZoom: 20,
        center: [ 50.727589, -3.541809 ],
        zoom: 18,
        maxBounds: [
            [ 49.528423, -10.76418 ],
            [ 61.331151, 1.9134116 ]
        ],
        attributionControl: false
    };

    const map = L.map('map', mapOptions);

    // Load and display ZXY tile layer on the map.
    const basemap = L.tileLayer(`https://api.os.uk/maps/raster/v1/zxy/Light_3857/{z}/{x}/{y}.png?key=${apiKey}`, {
        maxZoom: 20
    }).addTo(map);
```

{% endcode %}
{% endstep %}

{% step %}

### Add an OS NGD API – Features layer

* Create a function called `fetchFeatures` that fetches the API based on the current map extent (bounding box) by generating a bbox string.&#x20;
* Construct the API request URL to fetch OS NGD data from OS NGD API – Features. The URL includes the `collectionId`, `bbox` and `apiKey`.
* Once the features have been returned in JSON, update the source data of the map's layers to display the features.

{% code overflow="wrap" %}

```javascript
// Add layer group to make it easier to add or remove layers from the map.
    const lyrGroup = new L.layerGroup().addTo(map);

// Define an asynchronous function to fetch and display the NGD Features API features.
    async function fetchFeatures(bounds) {
        // Generate a BBOX string for the map extent.
        const bbox = bounds.toBBoxString();

        // Construct the NGD Features API request URL.
        const url = `https://api.os.uk/features/ngd/ofa/v1/collections/${collectionId}/items?key=${apiKey}&bbox=${bbox}`;

        // Fetch features from the API endpoint.
        const features = await fetch(url).then(response => response.json());

        // Parse the GeoJSON data and display it on the map.
        lyrGroup.clearLayers().addLayer(L.geoJSON(features));
    }

// Get the visible map bounds (BBOX).
    let bounds = map.getBounds();

// Initial fetch and display of features.
    fetchFeatures(bounds);
```

{% endcode %}
{% endstep %}

{% step %}

### Load and update features on the map dynamically

The `map.on('moveend',...)` event handler fetches and updates the features based on the map's current extent.

{% code overflow="wrap" %}

```javascript
// Add event which will be triggered when the map has finshed moving (pan + zoom).
// Implements a simple strategy to only request data when the map viewport invalidates
// certain bounds.
    map.on('moveend', function() {
        let bounds1 = new L.latLngBounds(bounds.getSouthWest(), bounds.getNorthEast()),
            bounds2 = map.getBounds();

        if( JSON.stringify(bounds) !== JSON.stringify(bounds1.extend(bounds2)) ) {
            bounds = bounds2;
            fetchFeatures(bounds);
        }
    });
```

{% endcode %}

{% hint style="warning" %}
Features within the viewport extent will load initially (first 100 features) and will continue to load as you pan and zoom across the map.
{% endhint %}
{% endstep %}
{% endstepper %}

## What's next?

Congratulations! You've successfully created a map using Leaflet and added an OS NGD layer using OS NGD API – Features in a few steps.

Now you can continue to explore Ordnance Survey's [code examples](https://labs.os.uk/public/osngd/os-ngd-api-features/) to learn more about advanced features and functionality, such as adding markers, pop-ups, and additional layers.
