MapKit es una biblioteca de software que le permite utilizar datos de mapas y tecnologías Yandex en aplicaciones móviles. Ella tiene documentación oficial que ya contiene una descripción detallada de los métodos API, por lo que hoy hablaremos de otra cosa.
En esta publicación, les contaré a los lectores de Habr sobre las características de la búsqueda en MapKit y compartiré recomendaciones y trucos que pueden serle útiles.
TL; DR Si no desea leer el artículo completo, estos son dos de los puntos más útiles como compensación por leer el prólogo:
- No olvide guardar sesiones, de lo contrario la búsqueda no funcionará.
- Toda la información más interesante se almacena en los metadatos del objeto. Si desea averiguar la dirección completa, el horario de apertura o cuánto cuesta una taza de capuchino en un café en particular, necesita metadatos.
Los enlaces a la documentación en el texto serán para Android, las clases y los métodos para iOS se llaman de la misma manera.
Lo que puede buscar
En primer lugar, hablemos sobre lo que puede hacer la búsqueda en MapKit. La búsqueda puede hacer lo que espera de una aplicación de mapas cuando desea encontrar algo allí.
Cuando escribe en la barra de búsqueda “cafe”, “Lev Tolstoy street, 16” o “tram 3”, la búsqueda de texto funciona. Este es el tipo de búsqueda más sofisticado. Apiladas en el sentido de que admite el conjunto máximo de parámetros para la personalización. Puede intentar buscar inmediatamente a lo largo de una ruta o una calle que le interese, aclarar el número deseado de resultados, establecer la posición del usuario, etc. Si después de la primera búsqueda desea mover el mapa o aplicar filtros a la solicitud ("farmacias con un grupo"), estas son nuevas solicitudes .
La búsqueda inversa es familiar para la mayoría de los usuarios en la pregunta "¿Qué hay aquí?". Le permite hacer clic en el mapa para determinar qué calle o casa está "debajo del cursor" o qué organizaciones están cerca de este punto. La búsqueda por URI es necesaria cuando desea encontrar un objeto específico. Se puede usar, por ejemplo, para crear marcadores en una aplicación. Encontramos nuestra cafetería favorita, la marcamos con un asterisco; la próxima vez será posible encontrar exactamente esta organización por URI, donde sea que se encuentre la ventana del mapa. Ni la búsqueda inversa ni las búsquedas de URI admiten nuevas solicitudes, porque no hay nada que especificar para ellas.
Otra posibilidad que vive en la búsqueda son las sugerencias de búsqueda , que le permiten complementar automáticamente la consulta mientras la escribe. Pero pospondremos una historia detallada sobre ellos para otro momento.
Cómo se organiza la solicitud
La búsqueda, como muchas partes de MapKit, funciona de forma asíncrona. El objeto principal para trabajar con esta asincronía es la sesión de búsqueda . Veamos un pequeño ejemplo.
Un poco sobre ejemplosLos ejemplos en el artículo estarán en Kotlin para facilitar el trabajo con valores opcionales y menos código repetitivo. MapKit tiene una aplicación de demostración . Se puede usar para probar ejemplos, pero para esto, SearchActivity
debe convertir de Java a Kotlin. showMessage
, que de vez en cuando aparece en el código, es una forma conveniente de mostrar una línea de texto en la pantalla o en el registro.
Inmediatamente después de llamar a submit
control volverá a su código, y cuando MapKit reciba una respuesta del servidor, se llamará a SearchListener.
La sesión de búsqueda le permite:
- Cancelar solicitud Por ejemplo, si el usuario cierra la pantalla de búsqueda.
- Repita la solicitud en caso de error. Por ejemplo, si hubo algún problema con la red.
- Continuar la interacción con la búsqueda después de la respuesta recibida. Las nuevas solicitudes se realizan a través de la sesión.
Una sesión tras la cancelación se cancela automáticamente. Esto significa que si no se guarda en el lado del código del cliente, la búsqueda no funcionará.
¡No olvides guardar la sesión, la sesión es tu amiga!
Opciones de busqueda
Una forma general de configurar consultas de búsqueda es a través de la clase SearchOptions
, que le permite modificar los parámetros de consulta.
- El principal de estos parámetros es
SearchType
. Le permite especificar si desea ver topónimos, organizaciones o transporte en la respuesta (lo más probable es que no necesite otros tipos). - Otro parámetro de consulta importante son los fragmentos . Hablaremos de ellos con más detalle en la sección sobre el dispositivo de respuesta.
- Si desea obtener la geometría del topónimo (por ejemplo, calles o áreas), debe ordenarlo a través de
setGeometry(true)
. Tenga en cuenta que la geometría es bastante "pesada" en términos de datos transmitidos. - De forma predeterminada, la búsqueda no devuelve organizaciones cerradas (temporal o permanentemente), pero si las necesita, debe establecer
setSearchClosed(true)
.
Además de los parámetros enumerados, hay algunos más que pueden serle útiles, se pueden encontrar en la documentación de la clase. Tenga en cuenta que no todas las consultas admiten todas las combinaciones de parámetros. La documentación para cada método SearchManager
o Session
indica qué parámetros de SearchOptions
entiende.
¿Cómo se arregla la respuesta?
A juzgar por las preguntas de apoyo, la mayoría de los usuarios están confundidos por el formato de la respuesta de búsqueda. Si nos fijamos en la clase de respuesta, parece bastante simple (al menos, la parte interesante para nosotros):
public class Response { public synchronized SearchMetadata getMetadata(); public synchronized GeoObjectCollection getCollection();
Aquí getCollection()
devuelve los objetos en la respuesta, y getMetadata()
son algunos datos adicionales que contienen, por ejemplo, información sobre la ventana de respuesta , el tipo de clasificación y el número de resultados encontrados . Si mira dentro de GeoObjectCollection
puede ver que contiene algunos Item
que pueden ser otros GeoObjectCollection
o GeoObject
.
No hay colecciones dentro de las colecciones en la búsqueda (al menos todavía no), así que echemos un vistazo a GeoObject
. Dentro del objeto hay un nombre ( getName()
), una descripción ( getDescriptionText()
), un marco ( getBoundingBox()
), un conjunto de geometrías ( getGeometry()
) y algunos otros métodos no muy claros. ¿Dónde están los números de teléfono de la organización? ¿Cómo entender a qué ciudad se refiere el topónimo?
Según los métodos del objeto, esto no está muy claro.
Geoobject
Es hora de hablar más sobre GeoObject
.
GeoObject
es un objeto de "tarjeta" tan básico. En su interior puede haber un evento de carretera, un objeto separado del resultado de la búsqueda, una maniobra en la ruta o un objeto en el mapa (PDI), como un monumento o alguna organización notable.
Todo lo más interesante sobre el objeto se almacena en metadatos. Se puede acceder a ellos utilizando el método getMetadataContainer()
. La clave en este contenedor es el tipo de metadatos. Si ve algo en la documentación que termina con la palabra Metadata
, lo más probable es que esté aquí. En la búsqueda de diferentes piezas de "metadatos" 15.

Los metadatos se pueden dividir en varios tipos. El primer tipo son los metadatos que determinan a qué tipo pertenece el objeto: topónimos ( ToponymObjectMetadata
), organizaciones ( BusinessObjectMetadata
) o transporte ( TransitObjectMetadata
). En los metadatos para el topónimo, puede encontrar una dirección estructurada y una geometría detallada. Los metadatos para la organización son las horas de operación o el sitio de la empresa. Estos metadatos están determinados por el tipo de búsqueda en la solicitud: si buscó solo topónimos, cada objeto en la respuesta debería tener los metadatos correspondientes. Si buscaba nombres de lugares u organizaciones, cada objeto tendrá al menos uno de los dos "metadatos".
Aquí le mostramos cómo encontrar los números de teléfono de la compañía:
val phones = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(BusinessObjectMetadata::class.java) ?.phones
Y aquí está cómo encontrar la ciudad en una dirección estructurada:
val city = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(ToponymObjectMetadata::class.java) ?.address ?.components ?.firstOrNull { it.kinds.contains(Address.Component.Kind.LOCALITY) } ?.name
El segundo tipo son los metadatos que vienen con el objeto, aunque no lo haya preguntado. El tipo principal que necesita saber es URIObjectMetadata
. Dentro de URIObjectMetadata
el identificador único del objeto, que URI debe pasar en la búsqueda .
Y el tercer tipo son los metadatos, que aparecerán en la respuesta solo si solicita específicamente una búsqueda al respecto. De manera diferente, estos metadatos se denominan fragmentos . Los fragmentos son pequeños datos adicionales que cambian con más frecuencia que los datos de "referencia" básicos o que no todos necesitan. Puede ser una calificación, un enlace a fotos o panorámicas, el tipo de cambio o el precio del combustible en una estación de servicio. La lista de fragmentos debe establecerse utilizando las opciones de búsqueda. Si el servidor tiene un fragmento ordenado, lo agregará al objeto correspondiente.
val point = Geometry.fromPoint(Point(59.95, 30.32)) val options = SearchOptions() options.snippets = Snippet.FUEL.value searchSession = searchManager!!.submit("", point, options, this) ... override fun onSearchResponse(response: Response) {
Todos los metadatos enumerados anteriormente se agregan a objetos individuales en la respuesta. También hay metadatos que se agregan a toda la respuesta. Pero se muestran en los métodos SearchMetadata
y no es necesario extraerlos de ninguna colección especial.
Ejemplos de uso
Ahora veamos los principales métodos de búsqueda de clases, veamos ejemplos de uso y algunos puntos no obvios asociados con ellos.
Búsqueda de texto
El método principal para la búsqueda de texto (y para toda la búsqueda, probablemente) es submit
:
Session submit( String text, Geometry geometry, SearchOptions searchOptions, SearchListener searchListener );
- Se espera que el parámetro de
text
contenga el texto que desea buscar. - El parámetro de
geometry
es un poco más complicado. Dependiendo de qué tipo de geometría se transfiere, la búsqueda se comportará de manera diferente:
- Si pasa un punto, la búsqueda se realizará en una pequeña ventana al lado de este punto.
- Si pasa una ventana rectangular (
BoundingBox
) o un polígono de cuatro puntos, se usará como un cuadro de búsqueda. Un ejemplo simple de dicha ventana es el área visible del mapa. - Finalmente, si pasa la polilínea , la ventana que la describe se usará como una ventana de búsqueda, y la clasificación se llevará a cabo teniendo en cuenta esta polilínea.
- Ya
SearchOptions
sobre SearchOptions
y SearchListener
arriba.
El servidor puede considerar que la respuesta correcta no está en la ventana en la que se realizó la búsqueda inicial ("café en Vladivostok" cuando la ventana de búsqueda está en Moscú). En este caso, deberá tomar la ventana de respuesta y mover la tarjeta allí para que los resultados sean visibles en la pantalla (las nuevas solicitudes no se permiten y no solicitan mover la tarjeta).
El método de submit
tiene un gemelo de submit
:
Session submit( String text, Polyline polyline, Geometry geometry, SearchOptions searchOptions, SearchListener searchListener );
con un parámetro adicional Este parámetro se puede usar para transferir una polilínea grande (por ejemplo, una ruta a otra ciudad) y una pequeña ventana de búsqueda. Luego, la búsqueda misma cortará la parte necesaria de la polilínea transferida y la usará solo para la consulta.
Re-solicitudes
Las nuevas solicitudes, a diferencia de otros tipos de solicitudes, se realizan con la ayuda de una sesión de búsqueda, que devuelve el mismo submit
y su hermano gemelo. Parte de los métodos de la sesión es simple y directo:
Para realizar una búsqueda refinada, debe utilizar el método de resubmit
. Acepta el mismo SearchListener
que una búsqueda normal. Antes de llamarlo, puede cambiar varios parámetros de sesión. Por ejemplo, cambie simultáneamente el tipo de clasificación y aplique filtros.
Filtros
Ya que estamos hablando de filtros. Los filtros son cuando Wi-Fi y cocina italiana. Probablemente tengan la sintaxis más confusa de todas las interfaces de búsqueda en MapKit. Esto se debe al hecho de que se utilizan las mismas estructuras de datos para obtener filtros de la respuesta de búsqueda y para especificar filtros en la nueva solicitud.
Los filtros vienen en dos tipos. Los filtros booleanos suponen solo dos valores mutuamente excluyentes: sí o no. Esto puede ser la presencia de Wi-Fi en un café, un baño en una estación de servicio o un estacionamiento cerca de la organización. Los filtros de enumeración asumen muchos valores que se pueden solicitar juntos. Este, por ejemplo, es el tipo de cocina para una cafetería o los tipos de combustible en una estación de servicio.
Primero, veamos cómo obtener los filtros disponibles para el reinicio actual:
private fun filters(response: Response): String? { fun enumValues(filter: BusinessFilter) = filter .values .enums ?.joinToString(prefix = " -> ") { e -> e.value.id } ?: "" return response .metadata .businessResultMetadata ?.businessFilters ?.joinToString(separator = "\n") { f -> "${f.id}${enumValues(f)}" } }
En la línea resultante, para los filtros booleanos, solo se mostrará el identificador, y para los filtros de enumeración, el identificador del filtro mismo y los identificadores de los valores disponibles. Ahora, armados con el conocimiento de los identificadores disponibles, buscaremos los cafés de la cocina italiana que tienen Wi-Fi. Primero agregue un filtro booleano:
val boolFilter = BusinessFilter( "wi_fi", "", false, BusinessFilter.Values.fromBooleans( listOf(BusinessFilter.BooleanValue(true, true)) ) )
Ahora el filtro de enumeración:
val enumFilter = BusinessFilter( "type_cuisine", "", false, BusinessFilter.Values.fromEnums( listOf(BusinessFilter.EnumValue( Feature.FeatureEnumValue( "italian_cuisine", "", "" ), true, true )) ) )
Finalmente, puede agregar filtros a la sesión y llamar a resubmit()
:
searchSession!!.setFilters(listOf(boolFilter, enumFilter)) searchSession!!.resubmit(this)
Tenga en cuenta que no puede establecer filtros para la primera consulta. Primero debe obtener una respuesta de búsqueda que enumere los filtros disponibles. Y solo entonces para formar un reinicio.
Resultados adicionales
Otra sesión le permite verificar si hay resultados de búsqueda adicionales para su solicitud. Y, si lo son, consíguelos. Por ejemplo, cuando está buscando un café en su ciudad, lo más probable es que no encajen en una página de la respuesta de búsqueda. Se hasNextPage
un par de fetchNextPage
hasNextPage
y fetchNextPage
para ver las siguientes páginas en la lista. Aquí debe saber que, en primer lugar, llamar a fetchNextPage
arrojará una excepción si el método hasNextPage
devuelve false
. Y en segundo lugar, el uso de estos métodos implica que los parámetros restantes no cambian. Es decir, la sesión se usa para refinar la solicitud ( resubmit()
) o para recuperar las siguientes páginas ( fetchNextPage()
). La combinación de estos modos no es necesaria.
Búsqueda inversa
La búsqueda inversa por conveniencia también se llama submit
:
Session submit( Point point, Integer zoom, SearchOptions searchOptions, SearchListener searchListener )
Se diferencia de otros tipos de consultas en que requiere solo un tipo de búsqueda para ingresar. Puede pasar el tipo GEO
y buscar nombres de lugares, o el tipo BIZ
y buscar organizaciones. No hay un tercero.
Al buscar de nuevo con un tipo GEO
, hay puntos que necesitan aclaración. Tenga en cuenta que la respuesta contendrá varios objetos en la jerarquía (es decir, la respuesta incluirá una casa, calle, ciudad, etc.). En casos simples, puede tomar solo el primer objeto. En los más complejos, busque a través de la jerarquía que desee.
El nivel de zoom es necesario para producir resultados adecuados dependiendo de lo que el usuario vea en el mapa. Imagine a un usuario mirando un mapa en todo el país. Entonces será extraño para él hacer clic en una calle o casa separada si el usuario accidentalmente logró entrar en ellas. Bastante ciudades. Para esto es el parámetro de zoom
.
val point = Point(55.734, 37.588)
Búsqueda por URI
Aquí todo está bastante claro: tomamos el URI del URIObjectMetadata
, recuérdenlo, después de un tiempo vamos en la búsqueda y con este URI obtenemos exactamente el objeto que recordamos.
searchSession = searchManager!!.resolveURI(uri, SearchOptions(), this)
De alguna manera incluso aburrido.
Capa de búsqueda y futuro brillante
Al lado de SearchManager
todavía hay una cosa llamada capa de búsqueda . La capa fue concebida para combinar la búsqueda con el mapa. Él mismo sabe cómo agregarle resultados, mover el mapa para que estos resultados se muestren y realicen consultas cuando el usuario mueva el mapa. En muchos sentidos, es similar al SearchManager
y la Session
combinados, pero el trabajo integrado con el mapa agrega nuevas características. Y hablar de ellos está más allá del alcance de este artículo. En el momento del lanzamiento de MapKit 3.1, ya habíamos corrido alrededor de la capa de búsqueda en aplicaciones reales, por lo que puede intentar usarlo en su hogar. Tal vez facilitará su búsqueda de empleo.
Conclusión
Espero que después de leer el artículo comprenda cómo trabajar con la búsqueda en MapKit con toda su fuerza. Seguramente todavía hubo algunos momentos sutiles y no triviales (por ejemplo, casi no hablamos sobre los consejos y la capa de búsqueda). Algo se puede encontrar en la documentación, algo para aclarar en proyectos en GitHub o solicitar nuestro apoyo.
¡Prueba MapKit, usa la búsqueda en él y ve a Maps para hacerlos aún mejores!
PD: Y también visítenos el 29 de noviembre para escuchar cómo se organiza el backend de enrutamiento automotriz . Que, por cierto, también se puede usar en MapKit , pero esta es una historia completamente diferente.