How to Populate OpenStreetMap with Foursquare Venues/Amenities

on in AJAX and Fetching Data
Last modified on

First of all, we need a Foursquare app and a client ID and secret. Head over to developer.foursquare.com and get your free account.

Follow the steps to verify your account and create an app.

Create the HTML structure which will feed the map and the markers. Don’t forget to feed in your Foursquare details, your coordinates (latitude and longitude) and property title (note the variables between {curly brackets}).

<div id="amenities" data-fscid="{foursquare_client_id}" data-fscs="{foursquare_client_secret}" data-latitude="53.3032329" data-longitude="-6.1792373" data-property-title="{title}"></div>
<div id="osm-map-amenities"></div>

Next, here’s the JavaScript function that does the heavy lifting:

/**
 * Generate amenity map based on Foursquare
 *
 * Generates an amenity map based on Foursquare and populates it with closest venues
 *
 * @docs https://developer.foursquare.com/docs/api/venues/search
 * @docs https://foursquare.com/developers/apps/5M2CNASPDYYJ304R1BCDPLAP4ENIJCMRYF5CIMCGC1VNY2L3/settings
 * @docs https://foursquare.com/developers/apps/5M2CNASPDYYJ304R1BCDPLAP4ENIJCMRYF5CIMCGC1VNY2L3/places/usage
 *
 * @type function
 */
function get_amenity_map() {
    var amenitiesElement = document.getElementById('amenities');

    var fsClientId = amenitiesElement.dataset.fscid,
        fsClientSecret = amenitiesElement.dataset.fscs,
        latitude = amenitiesElement.dataset.latitude,
        longitude = amenitiesElement.dataset.longitude,
        currentPropertyTitle = amenitiesElement.dataset.propertyTitle;

    var data = null,
        xhr = new XMLHttpRequest();

    xhr.withCredentials = false;

    xhr.addEventListener("readystatechange", function () {
        if (this.readyState === 4) {
            var latLngArray = [],
                venues = JSON.parse(this.response),
                venueCategory;

            venues.response.venues.forEach(function (venue) {
                venueCategory = '';

                if (venue.categories[0]) {
                    venueCategory = venue.categories[0].name;
                }
                if (!isNaN(venue.location.lat) && !isNaN(venue.location.lng)) {
                    latLngArray.push([parseFloat(venue.location.lat), parseFloat(venue.location.lng), '<h3>' + venue.name + '</h3>' + venueCategory + '<br><small>' + venue.location.address + '</small>']);
                }
            });

            var osmMapAmenities = L.map("osm-map-amenities").setView([0, 0], 16);

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

            var markers = L.markerClusterGroup({
                maxClusterRadius: 0,
                spiderfyOnMaxZoom: false,
                showCoverageOnHover: false,
                zoomToBoundsOnClick: false
            });

            // Add current property marker
            var propertyIcon = L.icon({
                iconUrl: "map-marker-red.png"
            });

            var title = currentPropertyTitle;
            var marker = L.marker(new L.LatLng(latitude, longitude), {
                title: title,
                icon: propertyIcon
            });
            marker.bindPopup(title);
            markers.addLayer(marker);

            var myIcon;

            for (var i = 0; i < latLngArray.length; i++) {
                var a = latLngArray[i];
                var title = a[2];

                myIcon = L.icon({
                    iconUrl: "map-marker-green.png"
                });

                var marker = L.marker(new L.LatLng(a[0], a[1]), {
                    title: title,
                    icon: myIcon
                });
                marker.bindPopup(title);
                markers.addLayer(marker);
            }

            osmMapAmenities.addLayer(markers);
            osmMapAmenities.setView(new L.LatLng(latitude, longitude), 17);
        }
    });

    xhr.open("GET", "https://api.foursquare.com/v2/venues/search?ll=" + latitude + "," + longitude + "&client_id=" + fsClientId + "&client_secret=" + fsClientSecret + "&v=20190801&radius=500&limit=20&intent=browse");
    xhr.send(data);
}

document.addEventListener('DOMContentLoaded', function () {
    if (document.getElementById('amenities') && document.getElementById('amenities').dataset.fscid !== '' && document.getElementById('amenities').dataset.fscs !== '') {
        get_amenity_map();
    }
});

And finally, here’s your dependencies. Note that I have included the CSS inline for better performance, as I save two requests for relatively small code.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.css">

<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.5.1/leaflet.js</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/leaflet.markercluster.js"></script>

<script src="https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js"></script>

<style>
/**
 * Leaflet.js Fullscreen Module
 *
 * @source  https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/leaflet.fullscreen.css
 * @version 1.0.1
 */
.leaflet-control-fullscreen a {
    background:#fff url(../images/fullscreen.png) no-repeat 0 0;
    background-size: 26px 52px;
}
.leaflet-touch .leaflet-control-fullscreen a {
    background-position: 2px 2px;
}
.leaflet-fullscreen-on .leaflet-control-fullscreen a {
    background-position:0 -26px;
}
.leaflet-touch.leaflet-fullscreen-on .leaflet-control-fullscreen a {
    background-position: 2px -24px;
}

/* Do not combine these two rules; IE will break. */
.leaflet-container:-webkit-full-screen {
    width: 100% !important;
    height: 100% !important;
}
.leaflet-container.leaflet-fullscreen-on {
    width: 100% !important;
    height: 100% !important;
}

.leaflet-pseudo-fullscreen {
    position: fixed !important;
    width: 100% !important;
    height: 100% !important;
    top: 0 !important;
    left: 0 !important;
    z-index: 99999;
}

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
    .leaflet-control-fullscreen a {
        background-image:url(../images/fullscreen@2x.png);
    }
}



/**
 * Leaflet.js Marker Cluster Module
 *
 * @source  https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.css
 * @version 1.4.1
 */
.leaflet-cluster-anim .leaflet-marker-icon, .leaflet-cluster-anim .leaflet-marker-shadow {
    -webkit-transition: -webkit-transform 0.3s ease-out, opacity 0.3s ease-in;
    -moz-transition: -moz-transform 0.3s ease-out, opacity 0.3s ease-in;
    -o-transition: -o-transform 0.3s ease-out, opacity 0.3s ease-in;
    transition: transform 0.3s ease-out, opacity 0.3s ease-in;
}
.leaflet-cluster-spider-leg {
    /* stroke-dashoffset (duration and function) should match with leaflet-marker-icon transform in order to track it exactly */
    -webkit-transition: -webkit-stroke-dashoffset 0.3s ease-out, -webkit-stroke-opacity 0.3s ease-in;
    -moz-transition: -moz-stroke-dashoffset 0.3s ease-out, -moz-stroke-opacity 0.3s ease-in;
    -o-transition: -o-stroke-dashoffset 0.3s ease-out, -o-stroke-opacity 0.3s ease-in;
    transition: stroke-dashoffset 0.3s ease-out, stroke-opacity 0.3s ease-in;
}

.leaflet-popup-content {
    font-family: var(--body_font);
}



/**
 * Leaflet.js Marker Cluster Default Theme
 *
 * @source  https://cdnjs.cloudflare.com/ajax/libs/leaflet.markercluster/1.4.1/MarkerCluster.Default.css
 * @version 1.4.1
 */
.marker-cluster-small {
    background-color: rgba(181, 226, 140, 0.6);
}
.marker-cluster-small div {
    background-color: rgba(110, 204, 57, 0.6);
}
.marker-cluster-medium {
    background-color: rgba(241, 211, 87, 0.6);
}
.marker-cluster-medium div {
    background-color: rgba(240, 194, 12, 0.6);
}
.marker-cluster-large {
    background-color: rgba(253, 156, 115, 0.6);
}
.marker-cluster-large div {
    background-color: rgba(241, 128, 23, 0.6);
}

.marker-cluster {
    background-clip: padding-box;
    border-radius: 20px;
}
.marker-cluster div {
    width: 30px;
    height: 30px;
    margin-left: 5px;
    margin-top: 5px;

    text-align: center;
    border-radius: 15px;
    font: 12px -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
}
.marker-cluster span {
    line-height: 30px;
}
</style>

Let me know how it goes for you!

Related posts