Draw GeoJSON points as a layer with MapLibre GL (Mapbox GL)

Geoapify returns results in GeoJSON format which is natively supported by most of the client-side map libraries. Our Geocoding API and Places API return a GeoJSON.FeatureCollection with Features with geometry type Point.

In this code sample, we would like to show how GeoJSON points can be added to a map and handled as a layer. The full version of the code sample you can find on JSFiddle.

1. Create a map

You require a Geoapify API key to make API calls and display a map. Register and get an API key on MyProjects Geoapify.com.

We use MapLibre GL as a client-side library which is an open-source fork of Mapbox GL library before their switch to a non-OSS license.

You can include the library directly to your HTML-page or install it with npm:

<link rel="stylesheet" type="text/css" href="https://cdn.skypack.dev/maplibre-gl/dist/maplibre-gl.css">

Add a map container to your HTML-template and initilize the map:

<div id="my-map"></div>

body {
    margin: 0;
    padding: 0;
}

#my-map {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 100%;
}
import { Map, NavigationControl, Popup } from 'https://cdn.skypack.dev/maplibre-gl';

var bounds = {
  // Paris
  lat1: 48.88002146841028,
  lon1: 2.3410839716279455,
  lat2: 48.86395628860821,
  lon2: 2.368348737185606
}

var map = new Map({
  center: [(bounds.lon1 + bounds.lon2) / 2, (bounds.lat1 + bounds.lat2) / 2],
  zoom: 15,
  container: 'my-map',
  style: `https://maps.geoapify.com/v1/styles/klokantech-basic/style.json?apiKey=${YOUR_API_KEY}`,
});
map.addControl(new NavigationControl());

2. Query places for the given area

We use Geoapify Places API to get places of type 'cafe'. You can query the data with the following code:

var type = "cafe"

// getting cafes for the given boundary (number of results limited by 100)
var placesUrl = `https://api.geoapify.com/v1/places?lat1=${bounds.lat1}&lon1=${bounds.lon1}&lat2=${bounds.lat2}&lon2=${bounds.lon2}&type=${type}&limit=100&apiKey=${YOUR_API_KEY}`;

fetch(placesUrl).then(response => response.json()).then(places => {
  console.log(places);
});

3. Add results as a layer to the map

MapLibre GL lets to load an image and set it as an icon for the layer. We use a Geoapify Marker Icon API to generate an icon:

// getting an icon from Geoapify Icons API
map.loadImage(`https://api.geoapify.com/v1/icon/?icon=coffee&color=%23ff9999&size=large&type=awesome&apiKey=${YOUR_API_KEY}`, function(error, image) {
  if (error) throw error;
  map.addImage('rosa-pin', image); //38x55px, shadow adds 5px
});

Add the places to a map as a layer of type 'symbol':

fetch(placesUrl).then(response => response.json()).then(places => {
  showGeoJSONPoints(places, type);
});

function showGeoJSONPoints(geojson, id) {
  var layerId = `${id}-layer`;

  if (map.getSource(id)) {
    // romove first the old one
    map.removeLayer(layerId);
    map.removeSource(id);
  }

  map.addSource(id, {
    'type': 'geojson',
    'data': geojson
  });

  map.addLayer({
    'id': layerId,
    'type': 'symbol',
    'source': id,
    'layout': {
      'icon-image': 'rosa-pin',
      'icon-anchor': 'bottom',
      'icon-offset': [0, 5],
      'icon-allow-overlap': true
    }
  });
}

Note, the icons contain shadow, so we need to specify an icon offset to correct the icon position. Use Marker Icon API Playground to get the correct bottom offset.

Get a notification and show a popup when a user clicks on a place

The big advantage of the layer is that you add only 1 click event handler for the whole layer. However, you need to take care of some details:

  • Normalize coordinates;
  • Setup cursor value:
function showGeoJSONPoints(geojson, id) {
  ...

  map.on('click', layerId, function(e) {
    var coordinates = e.features[0].geometry.coordinates.slice();
    var name = e.features[0].properties.name;

    // Ensure that if the map is zoomed out such that multiple
    // copies of the feature are visible, the popup appears
    // over the copy being pointed to.
    while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) {
      coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360;
    }

    new Popup({
        anchor: 'bottom',
        offset: [0, -50]
      })
      .setLngLat(coordinates)
      .setText(name)
      .addTo(map);
  });

  // Change the cursor to a pointer when the mouse is over the places layer.
  map.on('mouseenter', layerId, function() {
    map.getCanvas().style.cursor = 'pointer';
  });

  // Change it back to a pointer when it leaves.
  map.on('mouseleave', layerId, function() {
    map.getCanvas().style.cursor = '';
  });

}

Note, the position of the popup also needs to be adjusted according to icon size.