O MapKit é uma biblioteca de software que permite usar dados de mapa e tecnologias Yandex em aplicativos móveis. Ela possui documentação oficial que já contém uma descrição detalhada dos métodos da API, então hoje falaremos sobre outra coisa.
Neste post, vou contar aos leitores da Habr sobre os recursos da pesquisa no MapKit e compartilhar recomendações e truques que podem ser úteis para você.
TL; DR Se você não quiser ler o artigo inteiro, aqui estão dois dos pontos mais úteis como compensação pela leitura do prefácio:
- Não se esqueça de salvar as sessões, caso contrário, a pesquisa não funcionará.
- Todas as informações mais interessantes são armazenadas nos metadados do objeto. Se você quiser descobrir o endereço completo, o horário de funcionamento ou quanto custa uma xícara de cappuccino em um café específico, precisará de metadados.
Os links para a documentação no texto serão para Android, classes e métodos para iOS são chamados da mesma maneira.
O que pode pesquisar
Primeiro, vamos falar sobre o que a pesquisa no MapKit pode fazer. A pesquisa pode fazer o que você espera de um aplicativo de mapa quando quiser encontrar algo lá.
Quando você digita na barra de pesquisa “cafe”, “Lev Tolstoy street, 16” ou “bonde 3”, a pesquisa de texto funciona. Este é o tipo de pesquisa mais sofisticado. Empilhados no sentido de que ele suporta o conjunto máximo de parâmetros para personalização. Você pode tentar procurar imediatamente ao longo de uma rota ou rua em que esteja interessado, esclarecer o número desejado de resultados, definir a posição do usuário e assim por diante. Se após a primeira pesquisa você desejar mover o mapa ou aplicar filtros à solicitação ("farmácias com piscina"), essas são solicitações novamente .
A pesquisa reversa é familiar para a maioria dos usuários na pergunta "O que está aqui?". Ele permite que você clique no mapa para determinar qual rua ou casa está "sob o cursor" ou quais organizações estão próximas desse ponto. A pesquisa por URI é necessária quando você deseja encontrar um objeto específico. Pode ser usado, por exemplo, para criar marcadores em um aplicativo. Encontramos uma cafeteria favorita, marcada com um asterisco - da próxima vez será possível encontrar exatamente essa organização pelo URI, onde quer que a janela do mapa esteja localizada. Nem a pesquisa reversa nem as pesquisas de URI oferecem suporte a solicitações, porque não há nada a ser especificado para elas.
Outra possibilidade que existe na pesquisa são as dicas de pesquisa , que permitem complementar automaticamente a consulta enquanto você a digita. Mas adiaremos uma história detalhada sobre eles por outro tempo.
Como a solicitação é organizada
A pesquisa, como muitas partes do MapKit, funciona de forma assíncrona. O principal objeto para trabalhar com essa assincronia é a sessão de pesquisa . Vejamos um pequeno exemplo.
Um pouco sobre exemplosOs exemplos no artigo estarão no Kotlin para facilitar o trabalho com valores opcionais e menos código padrão. O MapKit tem um aplicativo de demonstração . Pode ser usado para testar exemplos, mas, para isso, o SearchActivity
deve ser convertido de Java para Kotlin. showMessage
, que periodicamente aparece no código, é uma maneira conveniente de exibir uma linha de texto na tela ou no log.
Imediatamente após a chamada de submit
controle retornará ao seu código e, quando o MapKit receber uma resposta do servidor, o SearchListener será chamado.
A sessão de pesquisa permite:
- Cancelar pedido . Por exemplo, se o usuário fechar a tela de pesquisa.
- Repita a solicitação em caso de erro. Por exemplo, se houver algum problema com a rede.
- Continue a interação com a pesquisa após a resposta recebida. Re-solicitações são feitas durante a sessão.
Uma sessão após o cancelamento é automaticamente cancelada. Isso significa que, se não for salvo no lado do código do cliente, a pesquisa não funcionará.
Não se esqueça de salvar a sessão, a sessão é sua amiga!
Opções de pesquisa
Uma maneira geral de configurar consultas de pesquisa é através da classe SearchOptions
, que permite modificar os parâmetros da consulta.
- O principal desses parâmetros é
SearchType
. Ele permite que você especifique se deseja ver topônimos, organizações ou transporte na resposta (você provavelmente não precisará de outros tipos). - Outro parâmetro de consulta importante são trechos . Falaremos sobre eles em mais detalhes na seção sobre o dispositivo de resposta.
- Se você deseja obter a geometria do topônimo (por exemplo, ruas ou áreas), é necessário ordená-lo através de
setGeometry(true)
. Lembre-se de que a geometria é bastante "pesada" em termos de dados transmitidos. - Por padrão, a pesquisa não retorna organizações fechadas (temporária ou permanentemente), mas se você precisar delas, precisará definir
setSearchClosed(true)
.
Além dos parâmetros listados, há mais alguns que podem ser úteis para você, eles podem ser encontrados na documentação da classe. Observe que nem todas as consultas oferecem suporte a todas as combinações de parâmetros. A documentação para cada método SearchManager
ou Session
indica quais parâmetros do SearchOptions
ele entende.
Como a resposta está organizada
A julgar pelas perguntas de suporte, a maioria dos usuários fica confusa com o formato da resposta da pesquisa. Se você olhar para a classe de resposta, ela parecerá bastante simples (pelo menos, a parte interessante para nós):
public class Response { public synchronized SearchMetadata getMetadata(); public synchronized GeoObjectCollection getCollection();
Aqui, getCollection()
retorna os objetos na resposta e getMetadata()
são alguns dados adicionais que contêm, por exemplo, informações sobre a janela de resposta , o tipo de classificação e o número de resultados encontrados . Se você olhar dentro do GeoObjectCollection
poderá ver que ele contém alguns Item
que podem ser outros GeoObjectCollection
ou GeoObject
.
Não há coleções dentro das coleções na pesquisa (pelo menos ainda não), então vamos dar uma olhada no GeoObject
. Dentro do objeto há um nome ( getName()
), uma descrição ( getDescriptionText()
), um quadro ( getBoundingBox()
), um conjunto de geometrias ( getGeometry()
) e alguns outros métodos não muito claros. Onde estão os números de telefone da organização? Como entender a qual cidade o topônimo se refere?
De acordo com os métodos do objeto, isso não está muito claro.
Objeto geográfico
É hora de falar mais sobre o GeoObject
.
GeoObject
é um objeto "básico" tão básico. No interior, pode haver um evento na estrada, um objeto separado do resultado da pesquisa, uma manobra na rota ou um objeto no mapa (POI), como um monumento ou alguma organização notável.
O mais interessante sobre o objeto é armazenado em metadados. Eles podem ser acessados usando o método getMetadataContainer()
. A chave neste contêiner é o tipo de metadados. Se você vir algo na documentação que termina com a palavra Metadata
, provavelmente está aqui. Na busca de diferentes partes de "metadados" 15.

Os metadados podem ser divididos em vários tipos. O primeiro tipo são os metadados que determinam a que tipo de objeto o objeto pertence: topônimos ( ToponymObjectMetadata
), organizações ( BusinessObjectMetadata
) ou transporte ( TransitObjectMetadata
). Nos metadados do topônimo, você pode encontrar um endereço estruturado e uma geometria detalhada. Os metadados da organização são o horário de operação ou o site da empresa. Esses metadados são determinados pelo tipo de pesquisa na solicitação - se você pesquisou apenas topônimos, cada objeto na resposta deve ter metadados correspondentes. Se você estava procurando nomes ou organizações de locais, cada objeto terá pelo menos um dos dois "metadados".
Veja como encontrar os números de telefone da empresa:
val phones = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(BusinessObjectMetadata::class.java) ?.phones
E aqui está como encontrar a cidade em um endereço estruturado:
val city = response.collection.children.firstOrNull()?.obj ?.metadataContainer ?.getItem(ToponymObjectMetadata::class.java) ?.address ?.components ?.firstOrNull { it.kinds.contains(Address.Component.Kind.LOCALITY) } ?.name
O segundo tipo são os metadados que acompanham o objeto, embora você não tenha perguntado sobre ele. O principal tipo que você precisa conhecer é o URIObjectMetadata
. Dentro do URIObjectMetadata
o identificador exclusivo do objeto é armazenado, que deve ser passado na pesquisa pelo URI .
E o terceiro tipo são os metadados, que virão na resposta somente se você solicitar especificamente uma pesquisa sobre ele. De uma maneira diferente, esses metadados são chamados de snippets . Snippets são pequenos dados adicionais que são alterados com mais frequência do que os dados básicos de "referência" ou que nem todos precisam. Pode ser uma classificação, um link para fotos ou panoramas, a taxa de câmbio ou o preço do combustível em um posto de gasolina. A lista de trechos deve ser definida usando as opções de pesquisa. Se o servidor tiver um trecho ordenado, ele será adicionado ao objeto correspondente.
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 os metadados listados acima são adicionados a objetos individuais na resposta. Também há metadados que são adicionados a toda a resposta. Mas eles são destacados nos métodos SearchMetadata
e não precisam ser extraídos de nenhuma coleção especial.
Exemplos de uso
Agora, vamos examinar os principais métodos das classes de pesquisa, ver exemplos de uso e alguns pontos não óbvios associados a elas.
Pesquisa de Texto
O método principal para pesquisa de texto (e para toda a pesquisa, provavelmente) é submit
:
Session submit( String text, Geometry geometry, SearchOptions searchOptions, SearchListener searchListener );
- O parâmetro
text
deve conter o texto que você deseja pesquisar. - O parâmetro
geometry
é um pouco mais complicado. Dependendo do tipo de geometria transferida, a pesquisa se comportará de maneira diferente:
- Se você passar um ponto, a pesquisa será realizada em uma pequena janela ao lado deste ponto.
- Se você passar por uma janela retangular (
BoundingBox
) ou um polígono de quatro pontos, ela será usada como uma caixa de pesquisa. Um exemplo simples dessa janela é a área visível do mapa. - Finalmente, se você passar a polilinha , a janela que a descreve será usada como uma janela de pesquisa e a classificação será realizada levando em consideração essa polilinha.
- Já falamos sobre
SearchOptions
e SearchListener
acima.
O servidor pode considerar que a resposta correta não está na janela em que a pesquisa inicial foi realizada ("café em Vladivostok" quando a janela de pesquisa está em Moscou). Nesse caso, você precisará pegar a janela de resposta e mover o cartão para lá, para que os resultados sejam visíveis na tela (as solicitações não se permitem isso e não pedem para mover o cartão).
O método de submit
possui um gêmeo de submit
:
Session submit( String text, Polyline polyline, Geometry geometry, SearchOptions searchOptions, SearchListener searchListener );
com um parâmetro adicional. Este parâmetro pode ser usado para transferir uma polilinha grande (por exemplo, uma rota para outra cidade) e uma pequena janela de pesquisa. Em seguida, a própria pesquisa cortará a parte necessária da polilinha transferida e a usará apenas para a consulta.
Re-solicitações
Solicitações diferentes, ao contrário de outros tipos de solicitações, são feitas com a ajuda de uma sessão de pesquisa, que retorna o mesmo submit
e seu irmão gêmeo. Parte dos métodos da sessão é simples e direta:
Para executar uma pesquisa refinada, você precisa usar o método de resubmit
. Ele aceita o mesmo SearchListener
que uma pesquisa regular. Antes de chamá-lo, você pode alterar vários parâmetros de sessão. Por exemplo, altere simultaneamente o tipo de classificação e aplique filtros.
Filtros
Já que estamos falando de filtros. Os filtros são quando Wi-Fi e cozinha italiana. Eles provavelmente têm a sintaxe mais confusa de todas as interfaces de pesquisa no MapKit. Isso se deve ao fato de as mesmas estruturas de dados serem usadas para obter filtros da resposta da pesquisa e para especificar filtros na solicitação novamente.
Os filtros vêm em dois tipos. Os filtros booleanos assumem apenas dois valores mutuamente exclusivos - sim ou não. Pode ser a presença de Wi-Fi em um café, um banheiro em um posto de gasolina ou estacionamento perto da organização. Os filtros de enumeração assumem muitos valores que podem ser solicitados juntos. Este, por exemplo, é o tipo de cozinha para um café ou os tipos de combustível em um posto de gasolina.
Primeiro, vamos ver como disponibilizar os filtros para a reinicialização atual:
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)}" } }
Na linha resultante, para filtros booleanos, apenas o identificador será mostrado e, para filtros de enumeração, o identificador do próprio filtro e identificadores dos valores disponíveis. Agora, armados com o conhecimento dos identificadores disponíveis, procuraremos os cafés da culinária italiana que têm Wi-Fi. Primeiro adicione um filtro booleano:
val boolFilter = BusinessFilter( "wi_fi", "", false, BusinessFilter.Values.fromBooleans( listOf(BusinessFilter.BooleanValue(true, true)) ) )
Agora o filtro enum:
val enumFilter = BusinessFilter( "type_cuisine", "", false, BusinessFilter.Values.fromEnums( listOf(BusinessFilter.EnumValue( Feature.FeatureEnumValue( "italian_cuisine", "", "" ), true, true )) ) )
Por fim, você pode adicionar filtros à sessão e chamar resubmit()
:
searchSession!!.setFilters(listOf(boolFilter, enumFilter)) searchSession!!.resubmit(this)
Observe que você não pode definir filtros para a primeira consulta. Primeiro, você precisa obter uma resposta de pesquisa que lista os filtros disponíveis. E só então para formar uma reinicialização.
Resultados adicionais
Outra sessão permite que você verifique se há resultados de pesquisa adicionais para sua solicitação. E, se estiverem, pegue-os. Por exemplo, quando você procura um café em sua cidade, provavelmente todos eles não cabem em uma página da resposta da pesquisa. fetchNextPage
e fetchNextPage
necessários para exibir as seguintes páginas da lista. Aqui você precisa saber que, em primeiro lugar, chamar fetchNextPage
lançará uma exceção se o método hasNextPage
retornar false
. E segundo, o uso desses métodos implica que os parâmetros restantes não mudam. Ou seja, a sessão é usada para refinar a solicitação ( resubmit()
) ou recuperar as seguintes páginas ( fetchNextPage()
). A combinação desses modos não é necessária.
Pesquisa reversa
A busca reversa por conveniência também é chamada de submit
:
Session submit( Point point, Integer zoom, SearchOptions searchOptions, SearchListener searchListener )
Difere de outros tipos de consultas, pois requer apenas um tipo de pesquisa para entrar. Você passa o tipo GEO
e procura nomes de lugares ou o tipo BIZ
e procura organizações. Não há terceiro.
Ao pesquisar com um tipo GEO
, há pontos que precisam de esclarecimentos. Observe que a resposta conterá vários objetos na hierarquia (ou seja, a resposta incluirá uma casa, rua, cidade e assim por diante). Em casos simples, você pode pegar apenas o primeiro objeto. Nos mais complexos, pesquise na hierarquia desejada.
O nível de zoom é necessário para produzir resultados adequados, dependendo do que o usuário vê no mapa. Imagine um usuário vendo um mapa em todo o país. Será estranho para ele clicar em uma rua ou casa separada se o usuário acidentalmente conseguir entrar nelas. Cidades suficientes. É para isso que serve o parâmetro zoom
.
val point = Point(55.734, 37.588)
Pesquisa por URI
Tudo está bem claro aqui - pegamos o URI do URIObjectMetadata
, lembre-se, depois de um tempo entramos na pesquisa e, por esse URI, obtemos exatamente o objeto que lembramos.
searchSession = searchManager!!.resolveURI(uri, SearchOptions(), this)
De alguma forma, mesmo chato.
Camada de pesquisa e futuro brilhante
Ao lado do SearchManager
, ainda existe uma coisa chamada camada de pesquisa . A camada foi concebida para combinar a pesquisa com o mapa. Ele próprio sabe como adicionar resultados a ele, mover o mapa para que esses resultados sejam exibidos e fazer novas consultas quando o usuário move o mapa. De muitas maneiras, é semelhante ao SearchManager
e Session
combinados, mas o trabalho SearchManager
com o mapa adiciona novos recursos. E falar sobre eles está além do escopo deste artigo. Na época do lançamento do MapKit 3.1, já tínhamos percorrido a camada de pesquisa em aplicativos reais, para que você pudesse tentar usá-lo em sua casa. Talvez isso facilite seu trabalho de pesquisa.
Conclusão
Espero que, depois de ler o artigo, você tenha uma compreensão de como trabalhar com a pesquisa no MapKit com força total. Certamente ainda houve alguns momentos sutis e não triviais (por exemplo, quase não falamos sobre as dicas e a camada de pesquisa). Algo pode ser encontrado na documentação, algo para esclarecer em projetos no GitHub ou pedir nosso apoio.
Experimente o MapKit, use a pesquisa e acesse o Google Maps para torná-lo ainda melhor!
PS E também nos visite em 29 de novembro para ouvir como o backend de roteamento automotivo é organizado . Que, a propósito, também pode ser usado no MapKit , mas essa é uma história completamente diferente.