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>