Comment trouver le nombre de toutes les lettres sur tous les panneaux du type "entrée ville X" dans le pays? La manière exacte de répondre à ces questions

Récemment, dans le cadre d'un entretien, j'avais besoin de résoudre un problème dont la condition est donnée ci-dessous:

Le meilleur manager du monde nommé Penultimo a une autre idée brillante, qu'il faut réaliser. Il croit que le flux de touristes vers Isla de Educados augmentera s'il peut dire au monde entier combien de merveilleux panneaux routiers avec de longues inscriptions ils ont sur l'île. Vous êtes invité à proposer un algorithme qui vous permet de calculer le nombre total de lettres sur tous les panneaux "Entrée de la ville X" sur l'île, puis d'appliquer les connaissances acquises pour calculer une métrique similaire pour la République de Biélorussie. Faites attention à la langue utilisée pour désigner les colonies, ainsi qu'au fait qu'il peut y avoir plusieurs entrées dans la ville. Penultimo encourage également l'initiative, vous pouvez donc rechercher cette question pour des domaines spécifiques, comparer avec le nombre de personnes vivant dans la région,et effectuez également toute autre recherche que vous trouvez intéressante.


Sous la coupe, je vais vous montrer la solution exacte à ce problème et à d'autres problèmes similaires, par exemple: "Combien de stations-service sont situées à Moscou?"



Méthode de solution générale



Si vous regardez la carte OpenStreetMap, alors l'idée suivante vient immédiatement à l'esprit: prenons pour chaque ville ses frontières et les routes à l'intérieur de ses frontières, puis trouvons leurs intersections, sur lesquelles il y aura des panneaux! Comment nous allons chercher les intersections: nous prenons un segment de la frontière, puis un segment de la route et voyons si elles se croisent (problème géométrique typique). Et ainsi de suite jusqu'à ce que toutes les sections et villes soient terminées.



À propos de l'architecture de données OSM
, : , .

ID, .



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




Dépasser



OverPass - Ceci est une API pour obtenir des données d'OpenStreetMap. Il a son propre langage pour rédiger des requêtes, vous pouvez en lire plus en détail dans cet article .



Afin de rendre la rédaction des requêtes plus facile et plus pratique, il existe un outil Overpass-turbo , où le résultat de la requête peut être visualisé sous une forme pratique et interactive.



Utilisation de l'API OverPass en Python



Pour traiter les données d'OSM en Python, vous pouvez utiliser le package Overpy comme wrapper.

Pour envoyer des demandes et recevoir des données, vous devez procéder comme suit:



import overpy

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


où la variable (?) Data contient tout ce que le serveur nous a donné.



Comment traiter ces données? Supposons que nous ayons saisi la requête suivante pour obtenir les limites de Minsk:



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


En sortie, nous avons un fichier XML (vous pouvez choisir Json) avec la structure suivante:



<* *>
<     >
  <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>


Prenons quelques données:



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)


Du point de vue du travail avec OpenStreetMap en python, c'est tout ce qui est nécessaire pour obtenir les données.



Allons directement au problème



Pour le résoudre, le code a été écrit en Python, vous pouvez le voir sous le spoiler. Veuillez ne pas trop gronder pour la qualité du code, c'est le premier projet de ce type.



En-tête 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) #  





Notes de code



J'ai fait une demande pendant longtemps, choisissant différents types de routes pour qu'il fût moins à compter et ne pas rater les panneaux. La requête finale supprime simplement les routes sur lesquelles il n'y a aucun signe, par exemple résidentiel, service, voie piétonne, piste, etc.



J'ai analysé la liste des villes de Wikipedia et les ai sauvegardées au format.tht



Le code prend beaucoup de temps, j'ai même eu une réécrivez-le en C ++, mais a décidé de le laisser tel quel. Cela m'a pris deux jours, tout cela à cause de problèmes avec la dictature de l' Internet biélorusse et la surcharge du serveur OverPass. Pour résoudre le deuxième problème, vous devez faire une demande pour toutes les villes, mais je n'ai pas encore compris comment procéder normalement.



Ma réponse au problème

18981





Ce que je veux dire à propos de l'exactitude de la figure: tout repose sur la qualité des données de l'OSM lui-même, c'est-à-dire qu'il y a des endroits où, par exemple, une route traverse deux lignes frontalières, ou quelque part à la jonction la frontière est un peu tracée, et par conséquent nous en avons trop / intersection manquante. Mais c'est une caractéristique de cette tâche particulière qui n'a aucune signification pratique, sinon OSM est la force.



Deuxième tâche



Calculons maintenant le nombre de stations-service à Moscou:

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


Le code
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)) #   




Résultat - 489 obturations:






All Articles