Como encontrar o número de todas as letras em todas as placas do tipo "entrada na cidade X" no país? A maneira exata de responder a essas perguntas

Recentemente, no âmbito de uma entrevista, eu precisava resolver um problema, cuja condição é fornecida a seguir:

O melhor empresário do mundo, Penultimo, tem outra ideia brilhante, que você precisa realizar. Ele acredita que o fluxo de turistas para a Ilha dos Educados aumentará se puder contar ao mundo quantas placas rodoviárias maravilhosas com longas inscrições existem na ilha. Você está convidado a criar um algoritmo que permite calcular o número total de letras em todos os sinais "Entrada para a cidade X" na ilha e, em seguida, aplicar o conhecimento adquirido para calcular uma métrica semelhante para a República da Bielorrússia. Preste atenção à linguagem usada para designar os povoados, bem como ao fato de que podem haver várias entradas para a cidade. O Penultimo também incentiva a iniciativa, para que você possa pesquisar esse tema para áreas específicas, fazer comparações com a quantidade de pessoas que vivem na áreae também conduza qualquer outra pesquisa que você achar interessante.


Abaixo do corte, mostrarei a solução exata para este e outros problemas semelhantes, por exemplo: "Quantos postos de gasolina estão localizados em Moscou?"



Método de solução geral



Se você olhar para o mapa do OpenStreetMap, a seguinte ideia vem imediatamente à mente: vamos pegar para cada cidade seus limites e as estradas dentro de seus limites, e então encontrar seus cruzamentos, nos quais haverá sinais! Como procuraremos cruzamentos: pegamos um segmento da fronteira, depois um segmento da estrada e vemos se eles se cruzam (um problema geométrico típico). E assim por diante até que todas as seções e cidades terminem.



Sobre a arquitetura de dados OSM
, : , .

ID, .



  • — , ID
  • — ,
  • — , , ,




Ultrapassar



OverPass - Esta é uma API para obter dados do OpenStreetMap. Ele tem sua própria linguagem para escrever consultas, você pode ler sobre isso em detalhes neste artigo .



Para tornar mais fácil e conveniente a composição de consultas, existe a ferramenta Overpass-turbo , onde o resultado da consulta pode ser visualizado de forma prática e interativa.



Usando a API OverPass em Python



Para processar dados do OSM em Python, você pode usar o pacote Overpy como um wrapper.

Para enviar solicitações e receber dados, você precisa fazer o seguinte:



import overpy

api = overpy.Overpass()
Data = api.query("""
* *
""")


onde a variável (?) Data contém tudo o que o servidor nos forneceu.



Como processar esses dados? Suponha que inserimos a seguinte solicitação para obter as fronteiras de Minsk:



relation["type"="boundary"]["boundary"="administrative"]["name:be"="і"];
//:      
>; out skel qt;


Na saída, temos um arquivo XML (você pode escolher Json) com a seguinte estrutura:



<* *>
<     >
  <node id="277218521" lat="53.8605688" lon="27.3946601"/>
  <node id="4623647835" lat="53.8603938" lon="27.3966685"/>
  <node id="4713906615" lat="53.8605343" lon="27.3998220"/>
  <node id="4713906616" lat="53.8605398" lon="27.3966820"/>
  <node id="4713906617" lat="53.8605986" lon="27.3947987"/>
  <node id="277050633" lat="53.8463790" lon="27.4431241"/>
  <node id="277050634" lat="53.8455797" lon="27.4452681"/>
  <node id="4713906607" lat="53.8460017" lon="27.4439797"/>
<    ID ,    >
<way id="572768148">
    <nd ref="5502433452"/>
    <nd ref="277218520"/>
    <nd ref="4713906620"/>
    <nd ref="277218521"/>
    <nd ref="4713906617"/>
    <nd ref="4623647835"/>
    <nd ref="4713906616"/>
</way>
<way id="29079842">
    <nd ref="277212682"/>
    <nd ref="277051005"/>
    <nd ref="4739822889"/>
    <nd ref="4739822888"/>
    <nd ref="4739845423"/>
    <nd ref="4739845422"/>
    <nd ref="4739845421"/>
</way>


Vamos obter alguns dados:



import overpy

api = overpy.Overpass()
Data = api.query("""
relation["type"="boundary"]["boundary"="administrative"]["name:be"="і"];
>; out skel qt;
""")
Xa=Data.ways[0].nodes[0].lon #     
Ya=Data.ways[0].nodes[0].lat # 
Xb=Data.ways[0].nodes[1].lon
Yb=Data.ways[0].nodes[1].lat
NodeID=Data.ways[0]._node_ids[0] # ID    
print(len(Data.nodes)) #   
print(NodeID)
print(Xa,Ya)
print(Xb,Yb)


Do ponto de vista de trabalhar com OpenStreetMap em python, isso é tudo de que você precisa para obter os dados.



Vamos direto ao problema



Para resolver isso, o código foi escrito em Python, você pode vê-lo no spoiler. Por favor, não repreenda muito pela qualidade do código, este é o primeiro grande projeto nele.



Cabeçalho de spoiler
import overpy


###########################
def line_intersection(line1, line2): #  
    ax1 = line1[0][0]
    ay1 = line1[0][1]
    ax2 = line1[1][0]
    ay2 = line1[1][1]
    bx1 = line2[0][0]
    by1 = line2[0][1]
    bx2 = line2[1][0]
    by2 = line2[1][1]
    v1 = (bx2 - bx1) * (ay1 - by1) - (by2 - by1) * (ax1 - bx1)
    v2 = (bx2 - bx1) * (ay2 - by1) - (by2 - by1) * (ax2 - bx1)
    v3 = (ax2 - ax1) * (by1 - ay1) - (ay2 - ay1) * (bx1 - ax1)
    v4 = (ax2 - ax1) * (by2 - ay1) - (ay2 - ay1) * (bx2 - ax1)
    return (v1 * v2 < 0) & (v3 * v4 < 0)


#######################################
citytmp = []
city = []
Borderway = []
Roadway = []
Total = 0
A = [0, 0]
B = [0, 0]
C = [0, 0]
D = [0, 0]
amount = 0
progressbar = 0 
ReadyData = open(' .txt','w')
with open(" .txt", "r", encoding='utf8') as file:
    for i in range(115):
        citytmp.append(file.readline())
citytmp = [line.rstrip() for line in citytmp]
for i in range(115):
    city.append('"' + citytmp[i] + '"')
city[0]='"і"'

api = overpy.Overpass()
for number in range(0,115):#  ,  
    borderstring = """(
relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=town]; 
relation["type"="boundary"]["boundary"="administrative"]["name:be"=""" + city[number] + """][place=city];
);
>; out skel qt;"""
    roadstring = """(
area[place=town]["name:be"=""" + city[number] + """]; 
way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"]
    ["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area);
area[place=city]["name:be"=""" + city[number] + """]; 
way["highway"][highway!=service]["highway"!="footway"]["highway"!="track"]["highway"!="path"]
    ["highway"!="cycleway"]["highway"!="pedestrian"]["highway"!="steps"]["highway"!="residential"](area);
);
out body; >; out skel qt;"""
    print('Getting data about', city[number],'...')
        road = api.query(roadstring)
        border = api.query(borderstring)
    print('got data!, city:', city[number]) # 
    for w in range(len(border.ways)): #  
        for i in range(len(border.ways[w]._node_ids)):#    
            progressbar = i / len(border.ways[w]._node_ids) * 100
            print(progressbar, "%;", w, "of", len(border.ways), "parts ready; city-", city[number])
            A[0] = border.ways[w].nodes[i].lon
            A[1] = border.ways[w].nodes[i].lat
            if i == len(border.ways[w]._node_ids) - 1:
                break
            B[0] = border.ways[w].nodes[i+1].lon
            B[1] = border.ways[w].nodes[i+1].lat
            for j in range(len(road.ways)):
                for k in range(len(road.ways[j]._node_ids)):
                    C[0] = road.ways[j].nodes[k].lon
                    C[1] = road.ways[j].nodes[k].lat
                    if k == len(road.ways[j]._node_ids) - 1:
                        break
                    D[0] = road.ways[j].nodes[k+1].lon
                    D[1] = road.ways[j].nodes[k+1].lat
                    if line_intersection((A, B), (C, D)) == 1:
                        amount += 1
                        print(road.ways[j]._node_ids[k])
    print(amount)
    Total += amount * len(city[number])
    ReadyData.write(str(city[number]))
    ReadyData.write(str(amount))
    ReadyData.write('\n')
    amount = 0
print('Total', Total) #  





Notas de código



Fazia um pedido há muito tempo, escolhendo diferentes tipos de estradas para que fosse menos para contar e para não perder a sinalização. A consulta final simplesmente remove aquelas estradas em que não há sinais, por exemplo residencial, serviço, passeio, trilha, etc.



Analisei a lista de cidades da Wikipedia e salvei no formato. Se



o código está sendo executado há muito tempo, eu até tive um desejo uma vez reescrevê-lo em C ++, mas decidi deixar como está. Demorei dois dias, tudo por causa de problemas com a ditadura da Internet bielorrussa e a sobrecarga do servidor OverPass. Para resolver o segundo problema, você precisa fazer uma solicitação para todas as cidades, mas ainda não descobri como fazer isso normalmente.



Minha resposta para o problema

18981





O que quero dizer sobre a exatidão da figura: tudo depende da qualidade dos dados do próprio OSM, ou seja, há lugares onde, por exemplo, uma estrada cruza duas linhas de fronteira, ou em algum ponto no entroncamento a fronteira está desenhada um pouco errada e, como resultado, temos muito / interseção ausente. Mas esta é uma característica desta tarefa em particular que não tem significado prático, caso contrário, o OSM é a força.



Segunda tarefa



Agora vamos calcular o número de postos de gasolina em Moscou:

area[name=""];
(
  node["amenity"="fuel"](area);
  way["amenity"="fuel"](area);
  relation["amenity"="fuel"](area);
);
out body;
>;
out skel qt;


O código
import overpy

api = overpy.Overpass()
Data = api.query("""
area[name=""];
(
  node["amenity"="fuel"](area);
  way["amenity"="fuel"](area);
  relation["amenity"="fuel"](area);
);
out body;
>;
out skel qt;
""")
print(len(Data.nodes)) #   




Resultado - 489 recheios:






All Articles