Skip to content

Instantly share code, notes, and snippets.

@ivaner
Last active September 17, 2020 12:41
Show Gist options
  • Save ivaner/cf7a7e084235905eefba9f4fbf7d374e to your computer and use it in GitHub Desktop.
Save ivaner/cf7a7e084235905eefba9f4fbf7d374e to your computer and use it in GitHub Desktop.
Store Locator Concept • 002

Store Locator Concept with Mapbox tools • BestBuy

This is a store locator demo that uses Mapbox technology to demonstrate a map-centric store locator concept. Instead of forcing a user to fill in forms, they can begin their search by moving the map.

It shows how a user can zoom into the map and reduce the number of locations returned based on what's showing in the map itself. The interactivity between the sidebar and the map provides you with tight integration as hovering on a sidebar entry shows a card by the location. Clicking the cards allow you to open another page with more detailed information.

Mapbox maps are fully customizable and we see that in this map that uses the company branding to highlight a location, as opposed to using the upside-down teardrop pin, we see on most generic maps and store locators. We also see the search tool to fly to a specific location by typing in its name.

This concept enables a beautifully integrated, powerful and elegant store locator into your overall branded web site.

Major Features

  • Customized Map with Logos and Names -- Every layer of the map is customizable -- Store locations are indicated with store logos and name, not the ubiquitous generic pin
  • Automatically updated sidebar on map movement -- When you pan and zoom on the map, you get a list of the locations in the sidebar
  • Search tool to find locations in the map -- Immediately zooms you to the selected location
  • Hover in sidebar highlights the location on map
  • Card displays satellite image of location -- The card shows a birds eye view of the location
  • Clicking on card opens corresponding webpage -- A new window opens, displaying a webpage with more detailed information

Technologies used

  • Studio
  • Tileset
  • Search
  • queryRenderedFeatures
  • popup

Other useful tech (not in this demo)

  • Directions
  • Isochrones
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Mapbox Store Locator • Best Buy</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
<script src="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.js"></script>
<link href="https://api.mapbox.com/mapbox-gl-js/v1.12.0/mapbox-gl.css" rel="stylesheet" />
<script
src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.5.1/mapbox-gl-geocoder.min.js'></script>
<link rel='stylesheet'
href='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.5.1/mapbox-gl-geocoder.css'
type='text/css' />
<link href='https://api.mapbox.com/mapbox-assembly/v0.24.0/assembly.min.css' rel='stylesheet'>
<script async defer src='https://api.mapbox.com/mapbox-assembly/v0.24.0/assembly.js'></script>
</head>
<body>
<script
src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-directions/v4.1.0/mapbox-gl-directions.js"></script>
<link rel="stylesheet"
href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-directions/v4.1.0/mapbox-gl-directions.css"
type="text/css" />
<style>
.mapboxgl-popup {
max-width: 500px;
font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
}
.geocoder {
position: absolute;
z-index: 1;
width: 50%;
left: 50%;
margin-left: -25%;
top: 10px;
}
.mapboxgl-ctrl-geocoder {
min-width: 100%;
}
.mapboxgl-ctrl-geocoder input[type='text'] {
font-size: 12px;
width: 100%;
border: 0;
background-color: transparent;
height: 40px;
margin: 0;
color: rgba(0,0,0,.5);
padding: 10px 40px 10px 30px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
</style>
<div class='flex-parent viewport-full relative scroll-hidden'>
<div class='flex-child w-full w240-ml absolute static-ml left bottom'>
<div class='flex-parent flex-parent--column viewport-third h-full hmax-full bg-white'>
<div class='px12 py40'>
<div class="col col 1">
<img src="64px-Best_Buy_logo_2018.svg.png" style="padding-bottom:20px;padding-top:10px">
</div>
</div>
<div class='flex-child flex-child--grow px12 py12 scroll-auto'>
<div id='geocoder' class='geocoder'></div>
<div id='feature-listing' class='listing'></div>
</div>
<footer class='px12 py12 bg-gray-faint txt-s'>
<div class="col col 1">
<img width="100px" src="Black-Best-Buy.svg">
</div>
</footer>
</div>
</div>
<div id="map" class='flex-child flex-child--grow bg-darken10 viewport-twothirds viewport-full-ml'></div>
</div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiaXZhbnJhbWlzY2FsIiwiYSI6ImNqdWU4dDdibjAwYW4zeXA4aHU1MzdmbmkifQ.DwHnMu8ee4P9ZnSwzFq2Xg';
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/ivanramiscal/ckf689w102xww19mt6gfluopb',
center: [-98, 38.88],
maxZoom: 15,
minZoom: 4,
zoom: 4.46
});
var geocoder = new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
mapboxgl: mapboxgl
});
document.getElementById('geocoder').appendChild(geocoder.onAdd(map));
// Holds visible visibleStores features for filtering
var visibleStores = [];
// Create a popup, but don't add it to the map yet.
var popup = new mapboxgl.Popup({
closeButton: false,
//className: 'col col--2 prose'
});
//var filterEl = document.getElementById('feature-filter');
var listingEl = document.getElementById('feature-listing');
function renderListings(features) {
// Clear any existing listings
listingEl.innerHTML = '';
if (features.length) {
features.forEach(function (feature) {
var prop = feature.properties;
var card = document.createElement('div');
var topline = document.createElement('h3');
topline.className += 'txt-h4 txt-bold pt18 mb12';
topline.textContent = prop.name
card.appendChild(topline);
card.className += 'col col--12 bg-darken10 px12 py12 mb12';
var item = document.createElement('a')
item.href = feature.properties.Website;
item.target = '_blank';
item.textContent = prop.Address;
item.addEventListener('mouseover', function () {
// Highlight corresponding feature on the map
popup.setLngLat(feature.geometry.coordinates)
//.setHTML(
// '<a target="_blank" href="' + feature.properties.Website + '"><img width="50px" src="Black-Best-Buy.svg"><br><b>' +
// feature.properties.name + '</b><img src="https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/static/' + feature.geometry.coordinates + ',16.93,0,60/300x200?access_token=' + mapboxgl.accessToken
// + '"></a>'
// )
.setHTML(
'<img width="50px" src="Black-Best-Buy.svg"><br><b>' +
feature.properties.name + '</b><img src="https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/static/' + feature.geometry.coordinates + ',16.93,0,60/300x200?access_token=' + mapboxgl.accessToken
+ '">'
)
.addTo(map);
});
card.appendChild(item);
listingEl.appendChild(card);
});
}
}
function normalize(string) {
return string.trim().toLowerCase();
}
function getUniqueFeatures(array, comparatorProperty) {
var existingFeatureKeys = {};
// Because features come from tiled vector data, feature geometries may be split
// or duplicated across tile boundaries and, as a result, features may appear
// multiple times in query results.
var uniqueFeatures = array.filter(function (el) {
if (existingFeatureKeys[el.properties[comparatorProperty]]) {
return false;
} else {
existingFeatureKeys[el.properties[comparatorProperty]] = true;
return true;
}
});
return uniqueFeatures;
}
map.addControl(
new MapboxDirections({
accessToken: mapboxgl.accessToken
}),
'top-right'
);
map.on('load', function () {
map.addSource('stores', { //the store source, use in stores-viz
'type': 'vector',
'url': 'mapbox://ivanramiscal.alt70gam'//bestbuy
});
map.addLayer({
'id': 'stores-viz', //use this in layers
'type': 'circle',
'source': 'stores',
'source-layer': 'bestbuy-debh8z', //pull this from Tileset name
'paint': {
'circle-stroke-color': '#000',
'circle-stroke-width': 0,
'circle-radius': 5,
'circle-color': 'rgba(255, 255, 255, 0)'
}
})
map.on('moveend', function () {
var features = map.queryRenderedFeatures({
layers: ['stores-viz'] //
});
console.log("Features " + features)
if (features) {
var uniqueFeatures = getUniqueFeatures(features, "name");
// Populate features for the listing overlay.
renderListings(uniqueFeatures);
// Clear the input container
//filterEl.value = '';
// Store the current features in the `visibleStores` variable to
// later use for filtering on `keyup`.
visibleStores = uniqueFeatures;
}
});
map.on('mouseleave', 'stores-viz', function () {
map.getCanvas().style.cursor = '';
popup.remove();
});
// Call this function on initialization
// passing an empty array to render an empty state
renderListings([]);
});
</script>
</body>
</html>
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment