Get it on Google Play
26-06-2018
 

Aplicaciones con Geolocalización con ElasticSearch

En este artículo vamos a ver como lo hacemos para crear un sistema de geolocalización con ElasticSearch. Os recomiendo que os miréis el vídeo porque creo que aporta cosas que el artículo no hace.

Creación del índice

Tal como se comenta en el vídeo lo primero que tenemos que hacer(Después de descargar, descomprimir y arrancar ElasticSearch desde su web https://www.elastic.co/downloads/elasticsearch) es crear el índice para indexar el contenido. En este caso nos creamos un índice llamado ‘ciudades’ con un typo llamado ‘ciudades’ con varios campos:

curl -X PUT http://127.0.0.1:9200/ciudades -H 'Content-Type: application/json' -d '{ 
    "mappings": { 
        "ciudades": { 
            "properties": { 
                "country": { "type": "text", "fielddata": true  }, 
                "city": { "type": "text"  }, 
                "accentCity": { "type": "text"  }, 
                "region": { "type": "text"  }, 
                "population": { "type": "long", "ignore_malformed": true, "null_value": 0  }, 
                "location": { "type": "geo_point"  }
                } 
            } 
        } 
}'

Una vez ya con el índice creado ya podemos empezar a indexar ciudades:

Indexación de contenido en ElasticSearch

Tal como comento en el vídeo, Elastic Search ofrece dos alternativas para indexar contenido. La primera es mediante su propia API y la segunda es usando la API _bulk, esta última nos permite volcar gran cantidad de operaciones haciendo una sola llamada a su API.

Las dos alternativas son:

# Crear un documento
curl -XPOST http://127.0.0.1:9200/ciudades/ciudades/ -d '{ "country": "es", "city": "Llagostera", "accentCity": "Llagostera", "region": "gi", "population": 102569, location: "12.25698,11.235985" }' -H 'Content-Type: application/json'

# Crear varios documentos via API bulk
curl -XPOST 'localhost:9200/erp/facturas/_bulk?pretty' -d'
{ "index" : {} }
{ "country": "es", "city": "Llagostera", "accentCity": "Llagostera", "region": "gi", "population": 102569, location: "12.25698,11.235985" }
{ "index" : {} }
{ "country": "es", "city": "Girona", "accentCity": "Girona", "region": "gi", "population": 1202569, location: "51.25698,12.235985" }
' -H 'Content-Type: application/json'

Una vez con los datos indexados ya podemos explotarlos en nuestro Dashboard:

Explotación del Dashboard

Tenéis el código completo del dashboard en Github https://github.com/acoronadoc/elasticsearch-geolocation-web-dashboard.

El código empieza con la maquetación HTML:

<html>
	<head>
		<meta charset="UTF-8" />

		<!-- OpenStreet maps -->
		<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css"
		   integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ=="
		   crossorigin=""/>

		<script src="https://unpkg.com/[email protected]/dist/leaflet.js"
		   integrity="sha512-/Nsx9X4HebavoBvEBuyp3I7od5tA0UzAxs+j83KgC8PU0kgB4XiK4Lfe4y4cgBtaRJQEIFCW+oC506aPT2L1zw=="
		   crossorigin=""></script>
	</head>
	<body>
		<!-- Dashboard -->
		<div class='wrapper'>
			<div id="mapid" class='panel'></div>
			<div class='panel'><h2>Total ciudades</h2><div id='total'></div></div>
			<div id="panel-cities" class='panel'><h2>Ciudades (60km)</h2><div id='cities'></div></div>
			<div class='panel'><h2>Países (60km)</h2><div id='countries'></div></div>
		</div>


		<!-- Injección del JS i de los estilos -->
		<script src="map.js"></script>
		<link rel="stylesheet" href="style.css"/>
	</body>
</html>

Además tendremos también que injectar el código CSS en el fichero ‘style.css'(Es bastante sencillito):

body {
	text-align: center;
	background-color: #ececec;
	font-family: 'Helvetica', sans-serif;
	}

.wrapper {
	text-align: left;
	max-width: 800px;
	margin: auto;
	display: grid;
	grid-template-columns: 1fr 1fr 1fr;
	grid-column-gap: 15px;
	grid-row-gap: 15px;
	}

.wrapper .panel {
    text-align: left;
    background-color: #fff;
    border: 1px solid #d3d3d3;
    box-shadow: 0 0 4px 1px rgba(143,143,143,.2);
    padding: 15px;
    }

.wrapper .panel h2 {
	margin-top: 0px;
	}

#mapid {
	height: 400px;
	grid-column: 1 / 4;
	}

#panel-cities {
	max-height: 200px;
	overflow-y: scroll;
	}

Y finalmente el código que hace la magia, el Javascript:

var mapcenter=[42.5424371, 1.4572426];
var mapzoom=8;
var maptoken='pk.eyJ1IjoiYWNvcm9uYWRvYyIsImEiOiJjamlwMG0zOGUwdDV2M3Bud2d6dXN6Nzh4In0.FuCykMm8Fl7qVla2DEfyiQ';

/* Mapa */
document.addEventListener("DOMContentLoaded", function(event) { 
	var mymap = L.map('mapid').setView(mapcenter, mapzoom);

	L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token='+maptoken, {
	    maxZoom: 18,
	    id: 'mapbox.streets',
	    accessToken: 'your.mapbox.access.token'
	}).addTo(mymap);

	mymap.on('click', onMapClick);
	});

function onMapClick(e) {
	getInfo( e.latlng.lat, e.latlng.lng);
	}


/* Datos */
function getInfo(lat,lng) {
	var data=getData(lat,lng);

	fetch("http://127.0.0.1:9200/ciudades/ciudades/_search?pretty", {
	    body: data,
	    headers: {
		'Content-Type': 'application/json'
		},
	    mode: 'cors',
	    method: 'POST', 
	  }).then(function( response ) { 
		response.json().then(function(json) {
			results(json);
			});
		});
	}

function results(json) {
	var s="";
	var countries={};

	for ( var i=0; i<json.hits.hits.length; i++ ) {
		s+="<div>";
		s+=json.hits.hits[i]._source.accentCity+" ("+json.hits.hits[i]._source.country+")";
		s+="</div>";

		if ( countries[ json.hits.hits[i]._source.country ] == undefined )
			countries[ json.hits.hits[i]._source.country ]=1;
			else
			countries[ json.hits.hits[i]._source.country ]++;
		}

	document.getElementById("total").innerHTML=json.hits.total;
	document.getElementById("cities").innerHTML=s;
	document.getElementById("countries").innerHTML=JSON.stringify(countries);		
	}

function getData(lat,lng) {
	return '{'+
		'    "size" : 100,'+
		'    "query": {'+
		'        "bool" : {'+
		'            "must" : {'+
		'                "match_all" : {}'+
		'            },'+
		'            "filter" : {'+
		'                "geo_distance" : {'+
		'                    "distance" : "60km",'+
		'                    "location" : {'+
		'                        "lat" : '+lat+','+
		'                        "lon" : '+lng+
		'                    }'+
		'                }'+
		'            }'+
		'        }'+
		'    }'+
		'}';
	}

La parte mas importante es la función ‘getData’ que es la encargada de generar el JSON con el que ‘getInfo’ hace la llamada ‘POST’ al ElasticSearch para que le devuelva los datos.

A tener en cuenta

Importante securizar el ejemplo, este ejemplo es muy fácil de adaptar a un montón de aplicaciones practicas pero deberemos securizarlo porque no podemos dar acceso a todo el mundo a ElasticSearch(Podrían hacer sus propias llamadas, eliminar datos, etc.).

Por otro loado, Elastic Search siempre devolverá los datos en tiempo real. Seguramente tendréis que configurarlo bien, peró ElasticSearch siempre devuelve todo en tiempo real, esto lo hace imprescindible para cierto tipo de aplicaciones: Data Science, redes sociales, etc.

En fin, espero que os resulte útil. A disfrutar y como siempre, compartir y comentar siempre será bienvenido 😀

ElasticSearch

ElasticSearch

Si te ha servido, por favor comparte
 

Leave a Reply