Skip to content

Store Locator using GeoJSON File as Data Source with Custom Controls

This guide shows you how to build a store locator from scratch using a GeoJSON file containing the store locations.

The finished example will look like this:

See the example fullscreen

When to use

  • The entire store location data set is relatively small and can be easily downloaded.

  • The data set is not changing frequently.

What is needed

  • Magic Lane API key token

  • Web server (an example is provided)

  • SVG file of the store logo (or use our sample SVG)

  • GeoJSON file containing the store locations (or use our sample GeoJSON)

Setup

Get your Magic Lane API key token: if you do not have a token, see the Getting Started guide.

This project needs a web server. If you do not have access to a web server, you can easily install a local web server, see the Installing a Local Web Server guide. In this project we use a local web server.

(1) Interactive Map

The first step is to render an interactive map.

How it works

1// Start by setting your token from https://developer.magiclane.com/api/projects
2if (gem.core.App.token === undefined)
3     gem.core.App.token = "";
4
5var defaultAppScreen = gem.core.App.initAppScreen({
6     container: "map-canvas",
7     style: "./Mono-Day.style" // map style downloaded from Studio
8});
The first step in JavaScript is to set your API key token gem.core.App.token
The app is initialized inside the map-canvas container with a custom map style downloaded from Studio: gem.core.App.initAppScreen();
 1<!DOCTYPE html>
 2<html lang="en-us">
 3  <head>
 4    <meta charset="utf-8" />
 5    <title>Store Locator using GeoJSON data with Custom Controls - Step 1</title>
 6    <link rel="stylesheet" type="text/css" href="https://www.magiclane.com/sdk/js/gem.css" />
 7    <link rel="stylesheet" href="/fonts/webfonts.css" type="text/css" media="all" />
 8  </head>
 9
10  <body>
11    <div id="store-locator" style="width: 100%; height: 100%">
12      <div id="map-canvas" style="width: 100%; height: 100%; position: absolute; overflow: hidden"></div>
13    </div>
14
15    <script src="https://www.magiclane.com/sdk/js/gemapi.js"></script>
16    <script type="text/javascript" src="token.js"></script>
17    <script type="text/javascript" src="storeGeojsonCustomControls01.js"></script>
18  </body>
19</html>
Source code at this stage of the project: JavaScript , HTML , Map Style

(2) Render Store Locations using Map Markers and Information Bubbles

Next, we render store locations on the map using a SVG image as marker.

How it works

Create a directory for this project in the root directory of your web server.

In our case, the root directory for our local web server is htdocs, in which we create the project directory named storeGeojsonCustomControls.

So in our case, the project directory is: htdocs/storeGeojsonCustomControls
and the local web server url is: http://localhost/storeGeojsonCustomControls

Copy your SVG store logo file and GeoJSON store locations file into the project directory.

In our case, we are using the SVG file icon.svg and the GeoJSON file paris.geojson, which can be downloaded from here:

icon.svg and paris.geojson
 1// stores data source control
 2let geojsonDataControl = new gem.control.GeoJsonAddedDataControl("paris.geojson", "icon.svg", undefined /*icon filter*/, {
 3      marker: {
 4             cssClass: 'store-marker',
 5             highlightClass: 'highlight-marker-icon'
 6      },
 7      markerBubble: {
 8             markerBubbleClass: 'store-bubble',
 9             markerBubbleFunction: function (elMarker, properties, coords) {
10                     elMarker.innerHTML = '<div class="store-bubble">' +
11                             '<div class="store-bubble--title">Supermarket</div>' +
12                             '<div class="store-bubble--description">' + properties['addr:street'] + ', ' + properties['addr:city'] +
13                             '</div></div>';
14             }
15      },
16      markerGrouping: {
17             maxLevel: 14
18      }
19});
20defaultAppScreen.addControl(geojsonDataControl);
 1<style>
 2   .store-locator {
 3     font: 400 15px/22px "Source Sans Pro", "Helvetica Neue", Sans-serif;
 4   }
 5   .store-marker {
 6     background-image: url(./icon.svg);
 7     width: 20px;
 8     height: 20px;
 9     position: absolute;
10     cursor: pointer;
11   }
12
13   .store-marker.active {
14     width: 25px;
15     height: 25px;
16   }
17
18   .store-marker button {
19     position: absolute;
20     top: -50%;
21     left: 50%;
22     background: white;
23     color: #5390d9;
24     border-color: #5390d9;
25     border-style: solid;
26     padding: 0px;
27     cursor: pointer;
28     font-weight: 900;
29     font-size: 60%;
30     border-radius: 9px;
31     min-width: 18px;
32     min-height: 18px;
33     box-shadow: 0 2px 4px rgb(0 0 0 / 20%), 0 -1px 0 rgb(0 0 0 / 2%);
34   }
35
36   .highlight-marker-icon {
37     filter: hue-rotate(300deg);
38     transform: scale(1.5);
39   }
40
41   .store-bubble {
42     width: 180px;
43     bottom: 180%;
44     left: 50%;
45     margin-left: -90px;
46     position: absolute;
47   }
48
49   .store-bubble::after {
50     content: "";
51     position: absolute;
52     top: 100%;
53     left: 50%;
54     margin-left: -10px;
55     border-width: 10px;
56     border-style: solid;
57     border-color: white transparent transparent transparent;
58   }
59
60   .store-bubble--title {
61     background: #48bfe3;
62     color: #fff;
63     font-weight: 700;
64     font-size: 16px;
65     text-align: center;
66     display: block;
67     padding: 10px;
68     border-radius: 3px 3px 0px 0px;
69   }
70
71   .store-bubble--description {
72     display: block;
73     padding: 10px;
74     background: #fff;
75     border-radius: 0px 0px 3px 3px;
76     color: grey;
77   }
78</style>
Source code at this stage of the project: JavaScript and HTML

(3) Add the Store Locations to a Sidebar List

Add a title and information for each store in the left sidebar, as a list.

How it works

 1let listUIControl = new gem.control.ListControl({
 2     sourceControl: geojsonDataControl,
 3     container: 'menu-list-container',
 4     displayCount: false,
 5     flyToItemAltitude: 800,
 6     menuName: 'Store locations',
 7     cssClasses: {
 8             divMenu: {
 9                     className: 'list-menu',
10                     type: 'div'
11             },
12             listHeader: {
13                     className: 'list-title',
14                     type: 'div'
15             },
16             divList: {
17                     className: 'list-group',
18                     type: 'div'
19             },
20             divItem: {
21                     className: 'list-item',
22                     type: 'div'
23             }
24     },
25     populateItemFunction: function (divItemObj, properties) {
26             divItemObj.innerHTML = '<a href="#" class="list-item--title">' + properties['brand'] + ', ' + properties['addr:street'] + '</a>' +
27                     '<div class="list-item--details">' + properties['addr:city'] +
28                     (properties['phone'] ? ' &middot; ' + 'Phone: ' + properties['phone'] : '') +
29                     '<div class="list-item--open-hours">' + 'Open: ' + properties['opening_hours'] + '</div>' +
30                     '</div>';
31             divItemObj.addEventListener('click', function (e) {
32                     e.preventDefault();
33             });
34     }
35});
36defaultAppScreen.addControl(listUIControl);
 1      .list-menu {
 2        border-right: 1px solid rgba(0, 0, 0, 0.25);
 3        background-color: #f8f8f8;
 4        height: 100%;
 5      }
 6
 7      .list-title {
 8        background: #fff;
 9        padding: 0 10px;
10        background-color: #48bfe3;
11        color: #fff;
12        overflow-y: auto;
13      }
14
15      .list-group {
16        max-height: 100%;
17        overflow-y: auto;
18        word-break: break-word;
19      }
20
21      .list-item {
22        display: block;
23        padding: 10px;
24        background-color: #fff;
25        border-bottom: 1px solid #eee;
26      }
27
28      .list-item.active {
29        background-color: #dfeff5;
30      }
31
32      .list-item:hover {
33        background-color: #f8f8f8;
34        cursor: pointer;
35      }
36
37      .list-item--title {
38        display: block;
39        color: #5390d9;
40        font-weight: 700;
41        text-decoration: none;
42        padding-bottom: 5px;
43      }
44
45      .list-item--details {
46        color: grey;
47      }
48
49      .list-item--open-hours {
50        padding-top: 5px;
51      }
52
53      ::-webkit-scrollbar {
54        width: 3px;
55        height: 3px;
56        border-left: 0;
57        background: rgba(0, 0, 0, 0.1);
58      }
59
60      ::-webkit-scrollbar-thumb {
61        background: #5390d9;
62        border-radius: 0;
63      }
64
65      ::-webkit-scrollbar-thumb:hover {
66        background: #48bfe3;
67      }
68
69      ::-webkit-scrollbar-track {
70        background: none;
71      }
72    </style>
73  </head>
74
75  <body>
76    <div id="store-locator" style="width: 100%; height: 100%">
77      <div id="menu-list-container" class="menu-list-container" style="width: 30%; height: 100%; position: absolute"></div>
78      <div id="map-canvas" style="width: 70%; left: 30%; height: 100%; position: absolute; overflow: hidden"></div>
79    </div>
Source code at this stage of the project: JavaScript and HTML

(4) Adding Search Control

How it works

 1let searchControl = new gem.control.SearchControl({
 2     highlightOptions: {
 3             showContour: false
 4     },
 5     searchPreferences: {
 6             exactMatch: true,
 7             maximumMatches: 3,
 8             addressSearch: true,
 9             mapPoisSearch: true,
10             setCursorReferencePoint: true,
11             placeholderText: 'Search...'
12     },
13     searchResults: {
14             showLandmarkIcons: false
15     }
16});
17defaultAppScreen.addControl(searchControl);
Add the above code at the bottom of the JavaScript file. This enables the free-form text search functionality to search for anything on the map.
Source code at this stage of the project: JavaScript and HTML

Files

The finished example consists of the project directory containing these files:

  • JavaScript code (.js file extension)

  • HTML code (.html file extension)

  • GeoJSON store location file (.geojson file extension)

  • SVG store logo (.svg file extension)

  • Map style (.style file extension)

To run the example, the HTML file is loaded in a browser.

Source code for this example:

JavaScript
HTML
GeoJSON store locations
SVG store logo
Map style

right-click on the links and select Save As.

JavaScript Examples

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