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
<?xml version="1.0" encoding="UTF-8"?>
<svg width="200px" height="117px" viewBox="0 0 200 117" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Black-Best-Buy</title>
<g id="Black-Best-Buy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Best_Buy_logo_2018" fill-rule="nonzero">
<path d="M0,1.4482648 L0,51.9124394 L26.42228,51.9124394 C36.821918,51.9124394 46.432398,48.2624334 46.432398,37.5408614 C46.432398,30.2927294 41.210864,26.9840414 35.467746,25.2521214 C38.966846,23.8485794 42.936206,20.9000094 42.936206,14.7189146 C42.936206,6.8076886 35.08656,1.4482648 24.517972,1.4482648 L0,1.4482648 L0,1.4482648 Z M16.186622,13.2014188 L22.137586,13.2014188 C24.560128,13.2014188 26.407402,15.099005 26.407402,17.0249134 C26.407402,18.8142674 24.487912,20.7293874 22.137586,20.7293874 L16.186622,20.7293874 L16.186622,13.2014188 Z M16.186622,31.2030854 L24.041894,31.2030854 C26.784156,31.2030854 29.085336,33.2858174 29.085336,35.6663074 C29.085336,38.1941494 26.933106,40.2931834 23.565818,40.2931834 L16.186622,40.2931834 L16.186622,31.2030854 L16.186622,31.2030854 Z" id="path2998" fill="#1C252C"></path>
<path d="M13.09212,57.4169294 L13.09212,107.881105 L39.514402,107.881105 C49.91404,107.881105 59.524518,104.231097 59.524518,93.5095254 C59.524518,86.2613954 54.302986,82.9527074 48.559866,81.2207854 C52.058966,79.8172434 56.028326,76.8686734 56.028326,70.6875794 C56.028326,62.7763534 48.178682,57.4169294 37.610092,57.4169294 L13.09212,57.4169294 L13.09212,57.4169294 Z M29.278744,69.1700834 L35.229708,69.1700834 C37.65225,69.1700834 39.499524,71.0676694 39.499524,72.9935774 C39.499524,74.7829334 37.580034,76.6980534 35.229708,76.6980534 L29.278744,76.6980534 L29.278744,69.1700834 Z M29.278744,87.1717494 L37.134016,87.1717494 C39.876278,87.1717494 42.177458,89.2544814 42.177458,91.6349734 C42.177458,94.1628134 40.025228,96.2618474 36.657938,96.2618474 L29.278744,96.2618474 L29.278744,87.1717494 L29.278744,87.1717494 Z" id="path3005" fill="#1C252C"></path>
<polygon id="path3007" fill="#1C252C" points="47.702928 51.9063374 47.702928 1.4421614 88.407522 1.4421614 88.407522 13.6297358 63.841942 13.6297358 63.841942 20.2472074 83.789574 20.2472074 83.789574 31.6730594 63.841942 31.6730594 63.841942 39.7663694 88.407522 39.7663694 88.407522 51.9063374"></polygon>
<path d="M110.457624,53.3508074 C121.915158,53.3508074 131.079766,46.8153194 131.079766,36.2244694 C131.079766,19.1321834 108.84426,21.7623954 108.84426,16.4406374 C108.84426,14.3849798 111.006128,13.2941062 113.223184,13.2941062 C117.04406,13.2941062 119.804684,15.8113314 119.804684,15.8113314 L129.370892,6.7026804 C125.54466,3.0613712 119.501772,0 111.361486,0 C99.135968,0 91.137768,7.2560486 91.137768,16.0089734 C91.137768,33.3186294 113.05275,30.8063614 113.05275,36.2375814 C113.05275,38.1414514 111.22108,40.0568194 107.898948,40.0568194 C104.127104,40.0568194 101.13919,37.7798614 98.813792,35.8479834 L89.191536,45.0216574 C93.066142,48.7957014 99.28657,53.3508074 110.457624,53.3508074 L110.457624,53.3508074 Z" id="path3009" fill="#1C252C"></path>
<polygon id="path3011" fill="#1C252C" points="143.537254 51.9063374 143.537254 13.582128 130.207094 13.582128 130.207094 1.4421614 173.05404 1.4421614 173.05404 13.582128 159.723876 13.582128 159.723876 51.9063374"></polygon>
<path d="M60.080934,57.3812234 L76.219948,57.3812234 L76.219948,87.6121214 C76.219948,90.6819714 79.275164,93.5108934 82.460518,93.5108934 C85.465356,93.5108934 88.502738,90.9562674 88.502738,87.4506974 L88.502738,57.3812234 L104.594144,57.3812234 L104.594144,87.2450274 C104.594144,99.0951294 94.755592,108.547745 82.034072,108.547745 C69.240134,108.547745 60.080934,98.2604434 60.080934,86.5993294 L60.080934,57.3812234 Z" id="path3013" fill="#1C252C"></path>
<polygon id="path3015" fill="#1C252C" points="124.494168 107.845399 124.494168 89.8130874 105.525246 57.3812234 121.854206 57.3812234 132.58748 74.7580394 143.357834 57.3812234 159.723876 57.3812234 140.68079 89.9984994 140.68079 107.845399"></polygon>
<polygon id="path3017" fill="#FFED31" points="159.723876 84.9997994 150.202332 94.5213434 150.202332 107.375425 159.723876 116.896967 200 116.896967 200 84.9997994"></polygon>
<path d="M158.160036,100.94228 C158.160036,102.125466 157.200876,103.084627 156.017689,103.084627 C154.834503,103.084627 153.875342,102.125466 153.875342,100.94228 C153.875342,99.7590936 154.834503,98.7999327 156.017689,98.7999327 C157.200876,98.7999327 158.160036,99.7590936 158.160036,100.94228 Z" id="path3024" fill="#1C252C"></path>
</g>
</g>
</svg>
<!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