En este tutorial te mostraré cómo Crear un localizador de tiendas utilizando Mapbox GL JS, PHP y MySQL. Podrás explorar todas las ubicaciones desde una barra lateral y seleccionar una tienda específica para ver más información de dicha tienda. Al seleccionar los datos podrás ver un marcador en el mapa y se resaltará la tienda seleccionada en la barra lateral.
Para este ejemplo he tomado la ubicación de las tiendas de Almacenes Vidrí El Salvador. Tienen un número considerable de sucursales, es por ello que usaremos la data de ellos.
Este tutorial te muestra cómo usar Mapbox GL JS para construir un mapa web interactivo. Si eres nuevo en Mapbox GL JS, es posible que primero necesites leer la documentación sobre las aplicaciones web de Mapbox.
A continuación se muestra como quedará nuestra aplicación ya finalizada:
Empezando
Para este proyecto, te recomiendo que crees una carpeta en tu servidor local llamada “localizador” para alojar los archivos de tu proyecto.
Hay algunos recursos que necesitará antes de comenzar:
- Un token de acceso a tu cuenta. Utilizarás un token de acceso para asociar un mapa con tu cuenta. tu token de acceso puedes conseguirlo en tu cuenta de mapbox.
- Mapbox GL JS. La biblioteca JavaScript de Mapbox que usa WebGL para representar mapas interactivos usando los estilos de Mapbox GL.
- Un editor de texto. Después de todo, escribirás código PHP, HTML, CSS y JavaScript.
- Un servidor web local, recomiendo XAMPP, pero puedes usar el que tu consideres necesario.
- Un gestor de base datos MySQL. Durante el tutorial se ha recolectado algunas de las ubicaciones de Almacenes Vidrí y recuperamos los datos usando JSON.
- Marcador de mapa personalizado. Utilizarás una imagen para tu marcador de mapa. Guarda la imagen siguiente en tu carpeta del proyecto.
Descargar marcador personalizado
Agregar estructura
En tu carpeta de proyecto, crea un archivo index.html. Configura el documento agregando Mapbox GL JS y CSS a su cabeza, el código de la estructura html del archivo se muestra a continuación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Localizador de tiendas</title> <meta name="robots" content="noindex, nofollow" /> <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" /> <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700" rel="stylesheet" /> <script src="https://api.tiles.mapbox.com/mapbox-gl-js/v1.10.0/mapbox-gl.js"></script> <link href="https://api.tiles.mapbox.com/mapbox-gl-js/v1.10.0/mapbox-gl.css" rel="stylesheet" /> <link rel="stylesheet" type="text/css" href="css/style.css"> </head> <body> <div id="map" class="map"></div> <div class="sidebar"> <div class="heading"> <h1>Nuestras sucursales</h1> </div> <div id="listings" class="listings"></div> </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script type="text/javascript" src="js/script.js"></script> </body> </html> |
Creando el archivo CSS
Ahora vamos a crear una carpeta llamada css y dentro de dicha carpeta un archivo, el cual nombraremos style.css
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
body { color: #404040; font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', Sans-serif; margin: 0; padding: 0; -webkit-font-smoothing: antialiased; } * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } .sidebar { position: absolute; width: 30%; height: 100%; top: 0; left: 0; overflow: hidden; border-right: 1px solid rgba(0, 0, 0, 0.25); } .pad2 { padding: 20px; } .map { position: absolute; left: 30%; width: 70%; top: 0; bottom: 0; } h1 { font-size: 22px; margin: 0; font-weight: 400; line-height: 20px; padding: 20px 2px; } a { color: #404040; text-decoration: none; } a:hover { color: #101010; } .heading { background: #fff; border-bottom: 1px solid #eee; min-height: 60px; line-height: 60px; padding: 0 10px; background-color: #6c5ce7; color: #fff; } .listings { height: 100%; overflow: auto; padding-bottom: 60px; } .listings .item { display: block; border-bottom: 1px solid #eee; padding: 10px; text-decoration: none; } .listings .item:last-child { border-bottom: none; } .listings .item .title { display: block; color: #6c5ce7; font-weight: 700; } .listings .item .title small { font-weight: 400; } .listings .item.active .title, .listings .item .title:hover { color: #8cc63f; } .listings .item.active { background-color: #f8f8f8; } ::-webkit-scrollbar { width: 3px; height: 3px; border-left: 0; background: rgba(0, 0, 0, 0.1); } ::-webkit-scrollbar-track { background: none; } ::-webkit-scrollbar-thumb { background: #6c5ce7; border-radius: 0; } .marker { border: none; cursor: pointer; height: 56px; width: 56px; background-image: url(../img/marker.png); background-color: rgba(0, 0, 0, 0); } .clearfix { display: block; } .clearfix:after { content: '.'; display: block; height: 0; clear: both; visibility: hidden; } /* Marker tweaks */ .mapboxgl-popup { padding-bottom: 50px; } .mapboxgl-popup-close-button { display: none; } .mapboxgl-popup-content { font: 400 15px/22px 'Source Sans Pro', 'Helvetica Neue', Sans-serif; padding: 0; width: 275px; } .mapboxgl-popup-content-wrapper { padding: 1%; } .mapboxgl-popup-content h3 { background: #a29bfe; color: #fff; margin: 0; display: block; padding: 10px; border-radius: 3px 3px 0 0; font-weight: 700; margin-top: -15px; } .mapboxgl-popup-content h4 { margin: 0; display: block; padding: 10px 10px 10px 10px; font-weight: 400; } .mapboxgl-popup-content div { padding: 10px; } .mapboxgl-container .leaflet-marker-icon { cursor: pointer; } .mapboxgl-popup-anchor-top > .mapboxgl-popup-content { margin-top: 15px; } .mapboxgl-popup-anchor-top > .mapboxgl-popup-tip { border-bottom-color: #a29bfe; } .text-center{ text-align:center } |
El archivo anterior se encagará de darle un aspecto profesional al listado de tiendas.
Añadiendo código JavaScript
Luego vamos a crear otra carpeta siempre en el directorio root del proyecto, a dicha carpeta la vamos a nombrar js y dentro de ella creamos un archivo llamado script.js. El código es el siguiente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
/* This will let you use the .remove() function later on */ if (!('remove' in Element.prototype)) { Element.prototype.remove = function() { if (this.parentNode) { this.parentNode.removeChild(this); } }; } var apiToken = "TU API KEY AQUI"; var defaultMap = [-89.2436391, 13.6894234]; mapboxgl.accessToken =apiToken; /** * Add the map to the page */ var map = new mapboxgl.Map({ container: 'map', style: 'mapbox://styles/mapbox/light-v10', center: defaultMap, zoom: 12, scrollZoom: true }); var nav = new mapboxgl.NavigationControl(); map.addControl(nav, 'top-right'); function getStores(){ var jsonData= $.ajax({ url: 'stores.php', dataType: 'json', async: false }).responseText; return jQuery.parseJSON(jsonData); } var stores = getStores(); /** * Assign a unique id to each store. You'll use this `id` * later to associate each point on the map with a listing * in the sidebar. */ stores.features.forEach(function(store, i) { store.properties.id = i; }); /** * Wait until the map loads to make changes to the map. */ map.on('load', function(e) { /** * This is where your '.addLayer()' used to be, instead * add only the source without styling a layer */ map.addSource('places', { 'type': 'geojson', 'data': stores }); /** * Add all the things to the page: * - The location listings on the side of the page * - The markers onto the map */ buildLocationList(stores); addMarkers(); }); /** * Add a marker to the map for every store listing. **/ function addMarkers() { /* For each feature in the GeoJSON object above: */ stores.features.forEach(function(marker) { /* Create a div element for the marker. */ var el = document.createElement('div'); /* Assign a unique `id` to the marker. */ el.id = 'marker-' + marker.properties.id; /* Assign the `marker` class to each marker for styling. */ el.className = 'marker'; /** * Create a marker using the div element * defined above and add it to the map. **/ new mapboxgl.Marker(el, { offset: [0, -23] }) .setLngLat(marker.geometry.coordinates) .addTo(map); /** * Listen to the element and when it is clicked, do three things: * 1. Fly to the point * 2. Close all other popups and display popup for clicked store * 3. Highlight listing in sidebar (and remove highlight for all other listings) **/ el.addEventListener('click', function(e) { /* Fly to the point */ flyToStore(marker); /* Close all other popups and display popup for clicked store */ createPopUp(marker); /* Highlight listing in sidebar */ var activeItem = document.getElementsByClassName('active'); e.stopPropagation(); if (activeItem[0]) { activeItem[0].classList.remove('active'); } var listing = document.getElementById( 'listing-' + marker.properties.id ); listing.classList.add('active'); }); }); } /** * Add a listing for each store to the sidebar. **/ function buildLocationList(data) { data.features.forEach(function(store, i) { /** * Create a shortcut for `store.properties`, * which will be used several times below. **/ var prop = store.properties; /* Add a new listing section to the sidebar. */ var listings = document.getElementById('listings'); var listing = listings.appendChild(document.createElement('div')); /* Assign a unique `id` to the listing. */ listing.id = 'listing-' + prop.id; /* Assign the `item` class to each listing for styling. */ listing.className = 'item'; /* Add the link to the individual listing created above. */ var link = listing.appendChild(document.createElement('a')); link.href = '#'; link.className = 'title'; link.id = 'link-' + prop.id; link.innerHTML = prop.storeName; /* Add details to the individual listing. */ var details = listing.appendChild(document.createElement('div')); details.innerHTML = prop.address+' '+prop.city; details.innerHTML += '<br> Teléfono: ' + prop.phoneFormatted; /** * Listen to the element and when it is clicked, do four things: * 1. Update the `currentFeature` to the store associated with the clicked link * 2. Fly to the point * 3. Close all other popups and display popup for clicked store * 4. Highlight listing in sidebar (and remove highlight for all other listings) **/ link.addEventListener('click', function(e) { for (var i = 0; i < data.features.length; i++) { if (this.id === 'link-' + data.features[i].properties.id) { var clickedListing = data.features[i]; flyToStore(clickedListing); createPopUp(clickedListing); } } var activeItem = document.getElementsByClassName('active'); if (activeItem[0]) { activeItem[0].classList.remove('active'); } this.parentNode.classList.add('active'); }); }); } /** * Use Mapbox GL JS's `flyTo` to move the camera smoothly * a given center point. **/ function flyToStore(currentFeature) { map.flyTo({ center: currentFeature.geometry.coordinates, zoom: 15 }); } /** * Create a Mapbox GL JS `Popup`. **/ function createPopUp(currentFeature) { var popUps = document.getElementsByClassName('mapboxgl-popup'); if (popUps[0]) popUps[0].remove(); var popup = new mapboxgl.Popup({ closeOnClick: false }) .setLngLat(currentFeature.geometry.coordinates) .setHTML( '<h3 class="text-center">'+currentFeature.properties.storeName+'</h3>' + '<h4>' + currentFeature.properties.address +'<br><b>Teléfono: </b>'+currentFeature.properties.phoneFormatted+ '</h4>' ) .addTo(map); } |
El archivo que hicimos en el paso anterior, contiene una seria de funciones avanzadas que se encarga de pintar los datos en el mapa, mostrar el lista de tiendas encontradas. El listado de tiendas se obtiene haciendo una llamada via jquery ajax a un archivo que nombraremos stores.php.
Agregar marcadores mediante PHP y MySQL
Crear un archivo en la carpeta root del proyecto y lo llamaremos stores.php.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?php //http://geojson.io/#map=11/13.6150/-88.2339 //https://wtools.io/convert-json-to-php-array # conectare la base de datos $db_host="localhost"; $db_user="root"; $db_pass=""; $db_name="store_locator"; $con=@mysqli_connect($db_host, $db_user, $db_pass, $db_name); $sql="select * from stores"; $query=mysqli_query($con,$sql); $features = []; $i=0; while($row=mysqli_fetch_array($query)){ $lat=$row['latitude']; $long=$row['longitude']; $propiedades1=array ('phoneFormatted'=> $row['phoneFormatted'],'address'=> $row['address'],'city'=> $row['city'],'country'=> $row['country'] ,'postalCode'=> $row['postalCode'],'storeName'=>$row['storeName']); $arreglo_datos=array ('type' => 'Feature','properties' => $propiedades1,'geometry' => array ('type' => 'Point','coordinates' => array (0 => $long,1 => $lat))); $features += ["$i" =>$arreglo_datos ]; $i++; } $array_multi=$features; $data= array ('type' => 'FeatureCollection','features' => $features); echo json_encode($data); |
Este archivo recién creado, se encarga de conectarse a la base de datos y hacer una consulta a la base de datos para devolver mediante json un listado de las sucursales o tiendas encontradas en la base de datos, para que de ese modo se pueda crear el listado de tiendas en el mapa con su respectiva ubicación.
Creando nuestra base de datos
Ahora vamos a abrir nuestro gestor de base de datos mysql y vamos a crear una base de datos en blanco, para este ejemplo la llamaremos store_locator, una vez se ha creado la base de datos debemos seleccionarla y ejecutar el código sql siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
CREATE TABLE `stores` ( `id` int(11) NOT NULL, `storeName` varchar(100) DEFAULT NULL, `phoneFormatted` varchar(100) DEFAULT NULL, `address` varchar(100) DEFAULT NULL, `city` varchar(100) DEFAULT NULL, `country` varchar(100) DEFAULT NULL, `postalCode` varchar(100) DEFAULT NULL, `latitude` varchar(100) NOT NULL, `longitude` varchar(100) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- -- Volcado de datos para la tabla `stores` -- INSERT INTO `stores` (`id`, `storeName`, `phoneFormatted`, `address`, `city`, `country`, `postalCode`, `latitude`, `longitude`) VALUES (1, 'Vidrí San Miguel', '(503) 2210-0000', 'Calle el delirio. Urbanización jardines del río.', 'San Miguel', 'El Salvador', '20005', '13.4542469', '-88.1610564'), (2, 'Almacenes Vidrí Venezuela', '(503) 2278-3033', '21 Avenida sur entre 12 y 14 calle poniente', 'San Salvador', 'El Salvador', '20037', '13.6940612', '-89.2051787'), (3, 'Vidrí Merliot', '(503) 2278-3033', 'Bulevard Merliot y carretera al puerto de la libertad', 'La Libertad', 'El Salvador', '20037', '13.6759777', '-89.2665569'), (4, 'Vidrí Santa Ana', '(503) 2448-1122', '4 Ave. sur #5 entre 1a y 3a calle poniente', 'Santa Ana', 'El Salvador', '20037', '13.9938217', '-89.5608017'), (5, 'Vidrí Ejercito', '(503) 2277-7333', 'Km. 6 Bulevard del Ejercito', 'San Salvador', 'El Salvador', '20037', '13.6973159', '-89.1462172'), (6, 'Vidrí San Miguelito', '(503) 2277-7333', '29 calle poniente y 1a. Ave. norte No. 207', 'San Salvador', 'El Salvador', '20037', '13.713037', '-89.1935422'), (7, 'Vidrí Soyapango', '(503) 2277-7333', 'Calle a Tonacatepeque y ciudadela Don Bosco', 'Soyapango', 'El Salvador', '20037', '13.7168524', '-89.1439835'), (8, 'Vidrí Centro', '(503) 2271-3033', '1a calle poniente y avenida España', 'San Salvador', 'El Salvador', '20037', '13.6999626', '-89.1936537'), (9, 'Vidrí San Benito', '(503) 2264-3033', 'Bulevard El Hipodromo y calle Circunvalacion No. 428 Colonia San Benito', 'San Salvador', 'El Salvador', '20037', '13.6894234', '-89.2436391'); -- -- Índices para tablas volcadas -- -- -- Indices de la tabla `stores` -- ALTER TABLE `stores` ADD PRIMARY KEY (`id`); -- -- AUTO_INCREMENT de las tablas volcadas -- -- -- AUTO_INCREMENT de la tabla `stores` -- ALTER TABLE `stores` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=10; COMMIT; |
La consulta SQL se encarga de crear una tabla llamada stores y también le inserta los datos de prueba.
Hemos creado un mapa utilizando marcadores interactivos con datos y estilos personalizados utilizando la librería Mapbox GL JS. No olvides que puedes descargar los archivos fuentes y hacer tus pruebas.