Magic Lane
Skip to content

Store Locator - Switch from Google Maps to Magic Lane Platform

In this guide you will learn how to switch from a simple store locator using Google Maps JavaScript API to using Magic Lane JavaScript API.

Google Maps Simple Store Locator

To create a map which displays store locations from a GeoJSON data source using the Google Places JavaScript API you would write something like the following example code from Google Maps Simple Store Locator :
  1 function initMap() {
  2    // Create the map.
  3     const map = new google.maps.Map(document.getElementById('map'), {
  4         zoom: 7,
  5         center: { lat: 52.632469, lng: -1.689423 },
  6     });
  7
  8     // Load the stores GeoJSON onto the map.
  9     map.data.loadGeoJson('stores.json', {idPropertyName: 'storeid'});
 10
 11     // Define the custom marker icons, using the store's "category".
 12     map.data.setStyle((feature) => {
 13       return {
 14         icon: {
 15           url: `img/icon_${feature.getProperty('category')}.png`,
 16           scaledSize: new google.maps.Size(64, 64),
 17         },
 18       };
 19     });
 20
 21     const apiKey = 'YOUR_API_KEY';
 22     const infoWindow = new google.maps.InfoWindow();
 23
 24     // Show the information for a store when its marker is clicked.
 25     map.data.addListener('click', (event) => {
 26       const category = event.feature.getProperty('category');
 27       const name = event.feature.getProperty('name');
 28       const description = event.feature.getProperty('description');
 29       const hours = event.feature.getProperty('hours');
 30       const phone = event.feature.getProperty('phone');
 31       const position = event.feature.getGeometry().get();
 32       const content = `
 33         <h2>${name}</h2><p>${description}</p>
 34         <p><b>Open:</b> ${hours}<br/><b>Phone:</b> ${phone}</p>
 35       `;
 36
 37       infoWindow.setContent(content);
 38       infoWindow.setPosition(position);
 39       infoWindow.setOptions({pixelOffset: new google.maps.Size(0, -30)});
 40       infoWindow.open(map);
 41     });
 42
 43     // Build and add the search bar
 44     const card = document.createElement('div');
 45     const titleBar = document.createElement('div');
 46     const title = document.createElement('div');
 47     const container = document.createElement('div');
 48     const input = document.createElement('input');
 49     const options = {
 50       types: ['address'],
 51       componentRestrictions: {country: 'gb'},
 52     };
 53
 54     card.setAttribute('id', 'pac-card');
 55     title.setAttribute('id', 'title');
 56     title.textContent = 'Find the nearest store';
 57     titleBar.appendChild(title);
 58     container.setAttribute('id', 'pac-container');
 59     input.setAttribute('id', 'pac-input');
 60     input.setAttribute('type', 'text');
 61     input.setAttribute('placeholder', 'Enter an address');
 62     container.appendChild(input);
 63     card.appendChild(titleBar);
 64     card.appendChild(container);
 65     map.controls[google.maps.ControlPosition.TOP_RIGHT].push(card);
 66
 67     // Make the search bar into a Places Autocomplete search bar and select
 68     // which detail fields should be returned about the place that
 69     // the user selects from the suggestions.
 70     const autocomplete = new google.maps.places.Autocomplete(input, options);
 71
 72     autocomplete.setFields(
 73         ['address_components', 'geometry', 'name']);
 74
 75     // Set the origin point when the user selects an address
 76     const originMarker = new google.maps.Marker({map: map});
 77     originMarker.setVisible(false);
 78     let originLocation = map.getCenter();
 79
 80     autocomplete.addListener('place_changed', async () => {
 81       originMarker.setVisible(false);
 82       originLocation = map.getCenter();
 83       const place = autocomplete.getPlace();
 84
 85       if (!place.geometry) {
 86         // User entered the name of a Place that was not suggested and
 87         // pressed the Enter key, or the Place Details request failed.
 88         window.alert('No address available for input: \'' + place.name + '\'');
 89         return;
 90       }
 91
 92       // Recenter the map to the selected address
 93       originLocation = place.geometry.location;
 94       map.setCenter(originLocation);
 95       map.setZoom(9);
 96       console.log(place);
 97
 98       originMarker.setPosition(originLocation);
 99       originMarker.setVisible(true);
100
101       // Use the selected address as the origin to calculate distances
102       // to each of the store locations
103       const rankedStores = await calculateDistances(map.data, originLocation);
104       showStoresList(map.data, rankedStores);
105
106       return;
107     });
108  }
109
110  async function calculateDistances(data, origin) {
111    const stores = [];
112    const destinations = [];
113
114    // Build parallel arrays for the store IDs and destinations
115    data.forEach((store) => {
116      const storeNum = store.getProperty('storeid');
117      const storeLoc = store.getGeometry().get();
118
119      stores.push(storeNum);
120      destinations.push(storeLoc);
121    });
122
123    // Retrieve the distances of each store from the origin
124    // The returned list will be in the same order as the destinations list
125    const service = new google.maps.DistanceMatrixService();
126    const getDistanceMatrix =
127      (service, parameters) => new Promise((resolve, reject) => {
128        service.getDistanceMatrix(parameters, (response, status) => {
129          if (status != google.maps.DistanceMatrixStatus.OK) {
130            reject(response);
131          } else {
132            const distances = [];
133            const results = response.rows[0].elements;
134            for (let j = 0; j < results.length; j++) {
135              const element = results[j];
136              const distanceText = element.distance.text;
137              const distanceVal = element.distance.value;
138              const distanceObject = {
139                storeid: stores[j],
140                distanceText: distanceText,
141                distanceVal: distanceVal,
142              };
143              distances.push(distanceObject);
144            }
145
146            resolve(distances);
147          }
148        });
149      });
150
151    const distancesList = await getDistanceMatrix(service, {
152      origins: [origin],
153      destinations: destinations,
154      travelMode: 'DRIVING',
155      unitSystem: google.maps.UnitSystem.METRIC,
156    });
157
158    distancesList.sort((first, second) => {
159      return first.distanceVal - second.distanceVal;
160    });
161
162    return distancesList;
163  }
164
165  function showStoresList(data, stores) {
166    if (stores.length == 0) {
167      console.log('empty stores');
168      return;
169    }
170
171    let panel = document.createElement('div');
172    // If the panel already exists, use it. Else, create it and add to the page.
173    if (document.getElementById('panel')) {
174      panel = document.getElementById('panel');
175      // If panel is already open, close it
176      if (panel.classList.contains('open')) {
177        panel.classList.remove('open');
178      }
179    } else {
180      panel.setAttribute('id', 'panel');
181      const body = document.body;
182      body.insertBefore(panel, body.childNodes[0]);
183    }
184
185
186    // Clear the previous details
187    while (panel.lastChild) {
188      panel.removeChild(panel.lastChild);
189    }
190
191    stores.forEach((store) => {
192      // Add store details with text formatting
193      const name = document.createElement('p');
194      name.classList.add('place');
195      const currentStore = data.getFeatureById(store.storeid);
196      name.textContent = currentStore.getProperty('name');
197      panel.appendChild(name);
198      const distanceText = document.createElement('p');
199      distanceText.classList.add('distanceText');
200      distanceText.textContent = store.distanceText;
201      panel.appendChild(distanceText);
202    });
203
204    // Open the panel
205    panel.classList.add('open');
206
207    return;
208  }
  1 <html>
  2
  3 <head>
  4     <title>Google Store Locator</title>
  5     <style>
  6         #map {
  7             height: 100%;
  8         }
  9
 10         html,
 11         body {
 12             height: 100%;
 13             margin: 0;
 14             padding: 0;
 15         }
 16
 17         /* Styling for Autocomplete search bar */
 18         #pac-card {
 19           background-color: #fff;
 20           border-radius: 2px 0 0 2px;
 21           box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
 22           box-sizing: border-box;
 23           font-family: Roboto;
 24           margin: 10px 10px 0 0;
 25           -moz-box-sizing: border-box;
 26           outline: none;
 27         }
 28
 29         #pac-container {
 30           padding-top: 12px;
 31           padding-bottom: 12px;
 32           margin-right: 12px;
 33         }
 34
 35         #pac-input {
 36           background-color: #fff;
 37           font-family: Roboto;
 38           font-size: 15px;
 39           font-weight: 300;
 40           margin-left: 12px;
 41           padding: 0 11px 0 13px;
 42           text-overflow: ellipsis;
 43           width: 400px;
 44         }
 45
 46         #pac-input:focus {
 47           border-color: #4d90fe;
 48         }
 49
 50         #title {
 51           color: #fff;
 52           background-color: #acbcc9;
 53           font-size: 18px;
 54           font-weight: 400;
 55           padding: 6px 12px;
 56         }
 57
 58         .hidden {
 59           display: none;
 60         }
 61
 62         /* Styling for an info pane that slides out from the left.
 63          * Hidden by default. */
 64         #panel {
 65           height: 100%;
 66           width: null;
 67           background-color: white;
 68           position: fixed;
 69           z-index: 1;
 70           overflow-x: hidden;
 71           transition: all .2s ease-out;
 72         }
 73
 74         .open {
 75           width: 250px;
 76         }
 77
 78         .place {
 79           font-family: 'open sans', arial, sans-serif;
 80           font-size: 1.2em;
 81           font-weight: 500;
 82           margin-block-end: 0px;
 83           padding-left: 18px;
 84           padding-right: 18px;
 85         }
 86
 87         .distanceText {
 88           color: silver;
 89           font-family: 'open sans', arial, sans-serif;
 90           font-size: 1em;
 91           font-weight: 400;
 92           margin-block-start: 0.25em;
 93           padding-left: 18px;
 94           padding-right: 18px;
 95         }
 96     </style>
 97 </head>
 98
 99 <body>
100     <!-- The div to hold the map -->
101     <div id="map"></div>
102
103     <script src="app.js"></script>
104     <script async defer src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initMap">
105     </script>
106 </body>
107
108 </html>

Magic Lane Store Locator Example

The first step is to set your API key token gem.core.App.token , which you can get at the Magic Lane website, see the Getting Started tutorial.
You only need to type your email address and create a new password.

To create a store locator using a GeoJSON data source you would write something similar to the following example code:

 1 // Start by setting your token from https://developer.magiclane.com/api/projects
 2 if (gem.core.App.token === undefined)
 3     gem.core.App.token = "";
 4
 5 var defaultAppScreen = gem.core.App.initAppScreen({
 6     container: "map-canvas"
 7 });
 8 let geojsonDataControl = new gem.control.GeoJsonAddedDataControl("Oslo_Shops_from_OpenTripMap.geojson", "" /*icon default*/, "" /*no icon filter*/,
 9     {
10             markerBubble: {
11                     title: ['name'],
12                     image: ['preview']
13             }
14     });
15 defaultAppScreen.addControl(geojsonDataControl);
16
17 let listUIControl = new gem.control.ListControl({
18     sourceControl: geojsonDataControl,
19     container: 'menu-list-container',
20     menuName: 'Store Locator Example',
21     titleProperties: ['name'],     // GeoJSON property keys to use for store title
22     detailsProperties: ['kinds'],  // GeoJSON property keys to use for store details
23     imageProperty: ['preview']         // GeoJSON property key to use for store image
24 });
25 defaultAppScreen.addControl(listUIControl);
26
27 let searchControl = new gem.control.SearchControl({
28     highlightOptions: {
29             contourColor: { r: 0, g: 255, b: 0, a: 0 }
30     },
31     searchPreferences: {
32             exactMatch: true,
33             maximumMatches: 3,
34             addressSearch: true,
35             mapPoisSearch: true,
36             setCursorReferencePoint: true
37     }
38 });
39 defaultAppScreen.addControl(searchControl);
 1 <!doctype html>
 2 <html lang="en-us">
 3
 4   <head>
 5     <meta charset="utf-8">
 6     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, minimum-scale=1, user-scalable=no, shrink-to-fit=no" />
 7     <title>Store Locator using a GeoJSON data source - Maps SDK for JavaScript</title>
 8     <link rel="stylesheet" type="text/css" href="https://www.magiclane.com/sdk/js/gem.css">
 9   </head>
10
11   <body>
12     <div id="store-locator" style="width: 100%; height: 100%">
13       <div id="menu-list-container" class="menu-list-container"
14         style="width: 30%; height: 100%; left: 70%; position: absolute;"></div>
15       <div id="map-canvas" style="width: 70%; right: 30%; height: 100%; position:absolute; overflow:hidden"></div>
16     </div>
17
18     <script src="https://www.magiclane.com/sdk/js/gemapi.js"></script>
19     <script type="text/javascript" src="token.js"></script>
20     <script type="text/javascript" src="jsStoreGeojsonFile.js"></script>
21
22   </body>
23 </html>
See the example fullscreen

JavaScript Examples

Maps SDK for JavaScript Examples can be downloaded or cloned with Git