Geoespacial search es ahora totalmente compatible con Couchbase Server 5.5. Echa un vistazo a la Anuncio de Couchbase Server 5.5y descargar la versión para desarrolladores gratis ahora mismo.
En este post, voy a demostrar las capacidades geoespaciales de Couchbase Full Text Search mediante la creación de una interfaz de usuario basada en web que realiza búsquedas. Siempre que pienso en búsquedas geoespaciales, pienso en Yelp, que es genial para ayudarme a encontrar restaurantes en un área específica.
Así que voy a divertirme un poco y crear una versión muy básica de Yelp, pero sólo para hoteles. Lea también ¿Qué son los datos geoespaciales? si es nuevo para usted.
Si quieres seguirnos, la El código fuente completo está disponible en Github.
Puesta en marcha
Estos son los pasos que seguí para crear un nuevo proyecto antes de empezar a escribir código.
- En la línea de comandos:
dotnet nueva aurelia
. Esto supone que usted tiene .NET Core instalado. Tenga en cuenta que la búsqueda geoespacial no es una función exclusiva de .NET: puede utilizarla con la aplicación otros SDK de Couchbase como Node.js, Java, etc. También asume que has instalado un Plantilla SPA para Aurelia. También puede ir con Angular o React si lo desea, pero me gusta mucho Aureliay creo que deberías darle una oportunidad. - El comando anterior creará un shell de un proyecto ASP.NET Core. En esta entrada de blog, no voy a usar Razor. Sólo voy a utilizar ASP.NET como backend para los puntos finales de la API REST.
npm install aurelia-google-maps
. No tiene por qué utilizarlo, pero el plugin aurelia-google-maps me facilitará la interacción con Google Maps en mi aplicación.- Abrí este proyecto en Visual Studio 2017. He añadido Couchbase.Extensions.DependencyInjection con NuGet. No es necesario utilizar esta extensión, pero facilita las cosas.
- He instalado Couchbase Server 5.5, incluida la Servicio de búsqueda de texto completo. He configurado el cubo para muestras de viaje. I creado un usuario "mate" con acceso total a ese cubo.
Crear un índice geoespacial
Antes de construir el backend ASP.NET, necesitamos crear un índice geoespacial en Couchbase Server. Una vez que inicies sesión, haz clic en "Buscar" en el menú (está debajo de "Workbench"). Haz clic en "Añadir índice" para empezar.
He llamado a mi índice "mygeoindex". Seleccioné muestra-viaje como el bucket a indexar.
En "Type Mappings", desmarco la opción por defecto. Añado una nueva correspondencia de tipos con el nombre "hotel". Cada documento de hotel en "muestra-viaje" tiene un tipo con el valor "hotel". Selecciono la casilla "indexar sólo los campos especificados".
Voy a añadir dos campos hijo. Uno es "geo", que contiene las coordenadas geoespaciales dentro de un documento de hotel. Asegúrate de seleccionar "geopunto" como tipo. El otro es "name", que será el nombre del hotel. He optado por "almacenar" cada uno de estos datos: el índice será más grande, pero puedo evitar una búsqueda secundaria si almaceno la información en el índice.
Nota importante: Existe un error (NCBC-1651) en la versión actual del SDK .NET que provocará un error si se intenta leer desde un campo geopunto. En los ejemplos de código, he creado una solución: En realidad, no obtengo los campos geo y name del índice de búsqueda. En su lugar, utilizo la clave del documento devuelta por la búsqueda para realizar una llamada "get" secundaria y obtener el documento completo. Ten en cuenta que ésta es una técnica que puedes considerar si quieres mantener bajo el tamaño de tu índice. Este error ya se ha corregido y se incluirá en una próxima versión. Este es el peligro de estar a la última.
Eso es todo. Haz clic en "Crear índice". Observa el "progreso de indexación" en la siguiente pantalla hasta que llegue a 100% (no debería tardar mucho, suponiendo que te hayas acordado de desmarcar "por defecto").
ASP.NET Core REST Endpoints
A continuación, vamos a pasar a ASP.NET. Crearé dos endpoints. Un endpoint demostrará el cuadro delimitador método de búsqueda, y el otro demostrará el distancia método de búsqueda.
Necesitaré un objeto bucket de Couchbase para ejecutar las consultas. Siga las instrucciones ejemplos en la entrada de mi blog sobre inyección de dependencia o consulta el código fuente en Github si nunca has hecho esto antes.
Cuadro delimitador
Una búsqueda en un "cuadro delimitador" significa que usted define un cuadro en un mapa y desea buscar puntos de interés que estén dentro de ese cuadro. Sólo necesita dos puntos para definir una caja: las coordenadas de la esquina superior derecha y las coordenadas de la esquina inferior izquierda. (Las coordenadas son latitud y longitud).
1 2 3 4 5 6 7 |
público clase BoxSearch { público doble LatitudeTopLeft { consiga; configure; } público doble LongitudTopLeft { consiga; configure; } público doble LatitudeBottomRight { consiga; configure; } público doble LongitudInferiorDerecha { consiga; configure; } } |
Para crear una consulta geoespacial de cuadro delimitador, utilice la función GeoBoundingBoxQuery
disponible en el SDK de .NET. Voy a hacer esto dentro de un método POST con lo anterior BoxSearch
como parámetro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
[Ruta("api/Box")] [HttpPost] público IActionResult Caja([FromBody] BoxSearch caja) { var consulta = nuevo GeoBoundingBoxQuery(); consulta.TopLeft(caja.LongitudTopLeft, caja.LatitudeTopLeft); consulta.BottomRight(caja.LongitudInferiorDerecha, caja.LatitudeBottomRight); var searchParams = nuevo Parámetros de búsqueda() // .Fields("geo", "name") // omitir por error NCBC-1651 .Límite(10) .Tiempo de espera(TimeSpan.DesdeMilisegundos(10000)); var searchQuery = nuevo BúsquedaQuery { Consulta = consulta, Índice = "mygeoindex", Parámetros de búsqueda = searchParams }; var resultados = _bucket.Consulta(searchQuery); // ... snip ... |
Todo lo que necesito devolver desde este endpoint es una lista de los resultados: las coordenadas de cada hotel y el nombre y ubicación del hotel. He creado un GeoSearchResult
clase para esto.
1 2 3 4 5 6 7 8 9 10 11 |
público clase GeoSearchResult { público doble Latitud { consiga; configure; } público doble Longitud { consiga; configure; } público InfoVentana InfoVentana { consiga; configure; } } público clase InfoVentana { público cadena Contenido { consiga; configure; } } |
He construido esta clase para que coincida con el plugin de Google Maps que utilizaré más adelante.
Finalmente, usaré esta clase para devolver algunos resultados desde el endpoint.
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 |
// ... snip ... var lista = nuevo Lista<GeoSearchResult>(); foreach (var hit en resultados.Hits) { // *** esta parte no debería ser necesaria // la geografía y el nombre deberían aparecer en los resultados de la búsqueda // pero hay un error en el SDK NCBC-1651 var doc = _bucket.Visite<dinámico>(hit.Id).Valor; // **************** lista.Añadir(nuevo GeoSearchResult { Latitud = doc.geo.lat, Longitud = doc.geo.lon, InfoVentana = nuevo InfoVentana { Contenido = doc.nombre + "<br \/>" + doc.ciudad + ", " + doc.estado + " " + doc.país } }); } devolver Ok(lista); } |
Búsqueda a distancia
La búsqueda por "distancia" es otra forma de realizar consultas geoespaciales. Esta vez, en lugar de una caja, será más como un círculo. Usted proporciona una coordenada y una distancia. La distancia será el radio desde ese punto.
1 2 3 4 5 6 7 8 |
público clase PointSearch { público doble Latitud { consiga; configure; } público doble Longitud { consiga; configure; } público int Distancia { consiga; configure; } // millas se asume como la unidad público cadena DistanciaConUnidades => Distancia + "mi"; } |
Lo pongo por defecto en millas, pero ciertamente puedes usar kilómetros en su lugar, o presentar la opción en la interfaz de usuario.
El punto final será muy similar al punto final del cuadro delimitador, salvo que utiliza GeoDistanceQuery
.
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 |
[Ruta("api/Punto")] [HttpPost] público IActionResult Punto([FromBody] PointSearch punto) { var consulta = nuevo GeoDistanceQuery(); consulta.Latitud(punto.Latitud); consulta.Longitud(punto.Longitud); consulta.Distancia(punto.DistanciaConUnidades); var searchParams = nuevo Parámetros de búsqueda() // .Fields("geo", "name") // omitir por error NCBC-1651 .Límite(10) .Tiempo de espera(TimeSpan.DesdeMilisegundos(10000)); var searchQuery = nuevo BúsquedaQuery { Consulta = consulta, Índice = "mygeoindex", Parámetros de búsqueda = searchParams }; var resultados = _bucket.Consulta(searchQuery); var lista = nuevo Lista<GeoSearchResult>(); foreach (var hit en resultados.Hits) { // *** esta parte no debería ser necesaria // la geografía y el nombre deberían aparecer en los resultados de la búsqueda // pero hay un error en el SDK NCBC-1651 var doc = _bucket.Visite<dinámico>(hit.Id).Valor; // **************** lista.Añadir(nuevo GeoSearchResult { Latitud = doc.geo.lat, Longitud = doc.geo.lon, InfoVentana = nuevo InfoVentana { Contenido = doc.nombre + "<br \/>" + doc.ciudad + ", " + doc.estado + " " + doc.país } }); } devolver Ok(lista); } |
En este punto, puedes empezar a probar estos endpoint con Postman o Fiddler si quieres. Pero será muy agradable ver esto en un mapa.
Auerlia y Google Maps
En Aurelia, he creado dos componentes: geosearchbox y geosearchpoint.
Cada uno de ellos tendrá un componente de Google Maps con el que el usuario podrá interactuar. Estos mapas se centrarán en San Francisco, porque es donde se encuentran muchos de los hoteles de "muestra-viaje".
Componente de búsqueda Bounding Box
En google-map`
tiene un map-click.delegate que se activará cuando el usuario haga clic en el mapa. En geosearchbox.html:
1 2 3 4 5 6 7 8 |
<google-mapa si.vincular="marcadores" mapa-haga clic en.delegado="clickMap($event)" latitud="37.780986253433895" longitud="-122.45291600632277" zoom="12" marcadores.vincular="marcadores"> </google-mapa> |
marcadores
es simplemente una matriz que contiene las coordenadas de los resultados de búsqueda que deberían aparecer en el mapa. Inicialmente estará vacío.
Cuando el usuario haga clic por primera vez en el mapa, esto establecerá la primera coordenada (arriba a la izquierda) en el formulario. En geosearchbox.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
público clickMap(evento : cualquier) { var latLng = evento.detalle.latLng, lat = latLng.lat(), lng = latLng.lng(); // sólo actualizar la parte superior izquierda si aún no se ha establecido // o si la parte inferior derecha ya está configurada si (!este.longitudTopLeft || este.longitudeBottomRight) { este.longitudTopLeft = lng; este.latitudeTopLeft = lat; este.longitudeBottomRight = null; este.latitudeBottomRight = null; } si no { este.longitudeBottomRight = lng; este.latitudeBottomRight = lat; } } |
A continuación, haga clic en otro punto del mapa. Esto fijará la segunda coordenada (abajo a la derecha).
Mi implementación es muy básica. Nada de gráficos extravagantes ni validación de que la segunda coordenada está abajo a la derecha de la primera. Los campos de un formulario simplemente se rellenarán con la latitud y la longitud. En geosearchbox.html:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<p> Encuadernación caja busque en: <br /> Latitud (top izquierda): <entrada tipo="texto" valor="${ latitudeTopLeft }" /> Longitud (top izquierda): <entrada tipo="texto" valor="${ longitudeTopLeft }" /> <br /> Latitud (fondo derecha): <entrada tipo="texto" valor="${ latitudeBottomRight }" /> Longitud (fondo derecha): <entrada tipo="texto" valor="${ longitudeBottomRight }" /> <br /> <entrada si.vincular="latitudeTopLeft && latitudeBottomRight" haga clic en.desencadenar="searchClick()" tipo="botón" nombre="buscar" valor="Buscar" /> </p> |
Una vez que haya seleccionado dos coordenadas, aparecerá un botón de búsqueda. Haga clic en él para enviar estas coordenadas al punto final creado anteriormente, y se activará la función searchClick()
como se ve en geosearchbox.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
público searchClick() { deje boxBuscar = { latitudeTopLeft: este.latitudeTopLeft, longitudTopLeft: este.longitudTopLeft, latitudeBottomRight: este.latitudeBottomRight, longitudeBottomRight: este.longitudeBottomRight }; consola.registro("POSTing to api/Box: " + JSON.stringify(boxBuscar)); este.http.buscar(api/Box, { método: "POST", cuerpo: json(boxBuscar) }) .entonces(resultado => resultado.json() como Promesa<cualquier[]>) .entonces(datos => { este.marcadores = datos; }); } |
Cuando Aurelia, Google Maps, ASP.NET Core y Couchbase trabajan juntos, tiene este aspecto:
Búsqueda a distancia
La aplicación de la consulta geoespacial de "distancia" será similar a la de la interfaz de usuario de los recuadros delimitadores. Esta vez, sólo tendrá que hacer clic en un único punto del mapa. Pero tendrá que introducir una distancia (en millas).
En google-map
tendrá un aspecto idéntico. El sitio clickMap
es diferente:
1 2 3 4 5 6 7 8 |
público clickMap(evento: cualquier) { var latLng = evento.detalle.latLng, lat = latLng.lat(), lng = latLng.lng(); este.longitud = lng; este.latitud = lat; } |
Especifique una distancia (en millas) y, a continuación, haga clic en "buscar" para realizar una solicitud POST al punto final que escribimos anteriormente.
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 |
geosearchbox.html: <p> Distancia busque en: <br /> Latitud: <entrada tipo="texto" valor="${ latitud }" /> Longitud: <entrada tipo="texto" valor="${ longitud }" /> <br /> Distancia (millas): <entrada tipo="texto" valor="${ distancia }" /> <br /> <entrada si.vincular="latitud" haga clic en.desencadenar="searchClick()" tipo="botón" nombre="buscar" valor="Buscar" /> </p> geosearchbox.ts: público searchClick() { deje puntoBuscar = { latitud: este.latitud, longitud: este.longitud, distancia: este.distancia }; consola.registro("POSTing to api/Point:" + JSON.stringify(puntoBuscar)); este.http.buscar(api/Point, { método: "POST", cuerpo: json(puntoBuscar) }) .entonces(resultado => resultado.json() como Promesa<cualquier[]>) .entonces(datos => { este.marcadores = datos; }); } } |
A continuación se muestra un clip de la búsqueda en movimiento. Observe cómo cambian los resultados a medida que muevo la coordenada.
Resumen
Con la función indexación geoespacial toda la matemática y la búsqueda se delega a la Plataforma de Datos Couchbase. Así que puedes centrarte en crear una interfaz de usuario increíble (mejor que la mía) y una lógica de negocio sólida como una roca.
Asegúrese de consulte la documentación para obtener una visión completa de las capacidades geoespaciales de Couchbase. Lea esta entrada de blog para obtener más información sobre bases de datos espaciales.
Si necesita ayuda o tiene alguna pregunta, consulte la página Foros de Couchbase Servery si tiene alguna pregunta sobre el SDK .NET de Couchbase, consulte la sección Foros del SDK .NET.
Si quieres ponerte en contacto conmigo, deja un comentario o búscame en Twitter @mgroves.