Wyodrębnianie samych ways

Hej,
W jaki sposób wyodrębnić ways aby uzyskać coś w takim stylu:

{
“12345”: [
[30.0, 10.0],
[40.0, 40.0],
[20.0, 40.0],
[10.0, 20.0]
],
“67890”: [
[35.0, 15.0],
[45.0, 45.0],
[25.0, 45.0],
[15.0, 25.0]
]
}

Abym mógł uzyskać granicę województwa/powiatu/gminy itp. Bo np. Tak wygląda województwo:

relation = {
“relation_id”: 123,
“admin_level”: “8”,
“name”: “Example Municipality”,
“tags”: {
“boundary”: “administrative”,
“admin_level”: “8”,
“name”: “Example Municipality”
},
“members”: [
{“type”: “way”, “ref”: “12345”, “role”: “outer”},
{“type”: “way”, “ref”: “67890”, “role”: “outer”}
]
}

Liczę na każde wsparcie! Stanąłem w martwym punkcie ponieważ mój kod w pythonie ciągle sypie błędy ;((

Jak wygląda twój kod w Pythonie? Co usiłujesz osiągnąć?

Zachęcam do skorzystania z istniejących bibliotek do przetwarzania geometrii, a nie pisania swojej

1 Like

Znasz jakąś sensowną z dobrą dokumentacją?

Chciałbym uzyskać podział terytorialny. Pokażę Ci moją bazę danych:

  1. administrative_levels

  2. administrative_divisions
    (nie mogę wstawić drugiego multimedialnego obrazka ponieważ jestem nowym użytkownikiem)

Chciałbym aby w administrative_divisions był podział:
województwo → powiat → gmina → miejscowość itd.

A to mój kod w pythonie który tworzy mi podział terytorialny ale bez granic tych terytorium.

import osmium
import json
import os
from multiprocessing import Pool, Manager, current_process
import logging
import time

# Ustawienia logowania
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')


class OsmInitialBuilder(osmium.SimpleHandler):
    def __init__(self, target_relation_id):
        super(OsmInitialBuilder, self).__init__()
        self.target_relation_id = target_relation_id
        self.relations = {}
        self.parent_map = {}
        self.relation_ids_to_process = set([target_relation_id])

    def relation(self, r):
        if r.id == self.target_relation_id or r.id in self.relation_ids_to_process:
            if 'boundary' in r.tags and r.tags['boundary'] == 'administrative':
                admin_level = r.tags.get('admin_level', None)
                self.relations[r.id] = {
                    'relation_id': r.id,
                    'admin_level': admin_level,
                    'name': r.tags.get('name', '-'),
                    'tags': {tag.k: tag.v for tag in r.tags},
                    'members': [{'type': member.type, 'ref': member.ref, 'role': member.role} for member in r.members],
                    'postcode': r.tags.get('addr:postcode', 'No postcode')
                }
                logging.info(f"{current_process().name} - Processed relation ID: {r.id}, Name: {r.tags.get('name', '-')}, Admin level: {admin_level}")
                if admin_level == '4':
                    self.relation_ids_to_process.remove(r.id)
                # Track parent relations
                for member in r.members:
                    if member.type == 'r':
                        self.parent_map[member.ref] = r.id
                        if member.ref not in self.relations:
                            self.relation_ids_to_process.add(member.ref)


def parse_initial_relations(target_relation_id, filename):
    logging.info(f"{current_process().name} - Starting parse_initial_relations")
    handler = OsmInitialBuilder(target_relation_id)
    handler.apply_file(filename)
    logging.info(f"{current_process().name} - Finished parse_initial_relations")
    return handler.relations, handler.parent_map


class OsmHierarchyBuilder(osmium.SimpleHandler):
    def __init__(self, initial_relations, initial_parent_map, relations, parent_map, processed_relations, relation_ids_to_process):
        super(OsmHierarchyBuilder, self).__init__()
        self.initial_relations = initial_relations
        self.initial_parent_map = initial_parent_map
        self.relations = relations
        self.parent_map = parent_map
        self.processed_relations = processed_relations
        self.relation_ids_to_process = relation_ids_to_process
        self.start_time = time.time()
        self.total_relations = len(relation_ids_to_process)

    def relation(self, r):
        if r.id in self.initial_relations or r.id in self.relation_ids_to_process:
            if 'boundary' in r.tags and r.tags['boundary'] == 'administrative':
                admin_level = r.tags.get('admin_level', None)
                self.relations[r.id] = {
                    'relation_id': r.id,
                    'admin_level': admin_level,
                    'name': r.tags.get('name', '-'),
                    'tags': {tag.k: tag.v for tag in r.tags},
                    'members': [{'type': member.type, 'ref': member.ref, 'role': member.role} for member in r.members],
                    'postcode': r.tags.get('addr:postcode', 'No postcode')
                }
                if r.id not in self.processed_relations:
                    self.processed_relations.append(r.id)
                logging.info(f"{current_process().name} - Processed relation ID: {r.id}, Name: {r.tags.get('name', '-')}, Admin level: {admin_level}")

                # Calculate and log estimated time remaining
                processed_count = len(self.processed_relations)
                elapsed_time = time.time() - self.start_time
                estimated_total_time = (elapsed_time / processed_count) * self.total_relations
                estimated_remaining_time = estimated_total_time - elapsed_time
                logging.info(f"{current_process().name} - Progress: {processed_count}/{self.total_relations}, Estimated remaining time: {estimated_remaining_time:.2f} seconds")

                # Track parent relations
                for member in r.members:
                    if member.type == 'r':
                        self.parent_map[member.ref] = r.id
                        if member.ref not in self.processed_relations and member.ref not in self.relation_ids_to_process:
                            self.relation_ids_to_process.append(member.ref)


def parse_relations(initial_relations, initial_parent_map, filename, relations, parent_map, processed_relations, relation_ids_to_process):
    logging.info(f"{current_process().name} - Starting parse_relations")
    handler = OsmHierarchyBuilder(initial_relations, initial_parent_map, relations, parent_map, processed_relations, relation_ids_to_process)
    handler.apply_file(filename)
    logging.info(f"{current_process().name} - Finished parse_relations")
    return list(handler.relations.items()), list(handler.parent_map.items()), list(handler.processed_relations), list(handler.relation_ids_to_process)


def build_hierarchy(relations, parent_map):
    hierarchy = {}
    for relation_id, details in relations.items():
        hierarchy_list = []
        current_id = relation_id
        while current_id in parent_map:
            parent_id = parent_map[current_id]
            if parent_id not in relations:
                break
            parent = relations[parent_id]
            hierarchy_list.append({
                'admin_level': parent['admin_level'],
                'name': parent['name'],
                'relation_id': parent['relation_id'],
                'postcode': parent['postcode']
            })
            current_id = parent_id
        hierarchy[relation_id] = list(reversed(hierarchy_list))
    return hierarchy


def save_to_file(data, filename):
    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(data, f, ensure_ascii=False, indent=4)
    logging.info(f"Data saved to {filename}")


def load_from_file(filename):
    with open(filename, 'r', encoding='utf-8') as f:
        data = json.load(f)
    logging.info(f"Data loaded from {filename}")
    return data


def main():
    # Target relation ID for Poland
    target_relation_id = 49715
    relations_file = 'data/PL/multiprocessing/relations.json'
    hierarchy_file = 'data/PL/multiprocessing/hierarchy.json'
    filename = 'data/PL/OSM.osm.pbf'

    # Check if the relations file already exists
    if os.path.exists(relations_file) and os.path.exists(hierarchy_file):
        # Load relations and hierarchy from files
        relations = load_from_file(relations_file)
        hierarchy = load_from_file(hierarchy_file)
        # Rebuild the parent map
        parent_map = {}
        for relation_id, details in relations.items():
            for member in details['members']:
                if member['type'] == 'r':
                    parent_map[member['ref']] = relation_id
    else:
        # Parse initial relations to get admin_level 4
        initial_relations, initial_parent_map = parse_initial_relations(target_relation_id, filename)

        # Filter initial relations to get only those with admin_level 4
        admin_level_4_relations = {r_id: rel for r_id, rel in initial_relations.items() if rel['admin_level'] == '4'}

        # Create a manager to handle shared data between processes
        with Manager() as manager:
            relations = manager.dict()
            parent_map = manager.dict()
            processed_relations = manager.list()
            relation_ids_to_process = manager.list(admin_level_4_relations.keys())

            # Add initial relations to shared data
            for r_id, rel in initial_relations.items():
                relations[r_id] = rel
            for p_id, parent in initial_parent_map.items():
                parent_map[p_id] = parent

            # Create a pool of workers
            with Pool(processes=len(admin_level_4_relations)) as pool:
                results = []
                for admin_relation_id in admin_level_4_relations.keys():
                    logging.info(f"Main process - Dispatching relation ID {admin_relation_id} for processing")
                    result = pool.apply_async(parse_relations, (initial_relations, initial_parent_map, filename, relations, parent_map, processed_relations, relation_ids_to_process))
                    results.append(result)

                # Collect results
                for result in results:
                    rel_items, par_items, proc_list, ids_list = result.get()
                    relations.update(rel_items)
                    parent_map.update(par_items)
                    processed_relations.extend(proc_list)
                    relation_ids_to_process.extend(ids_list)

            # Convert manager.dict to normal dict for saving
            relations = dict(relations)
            parent_map = dict(parent_map)

            # Build the hierarchy
            hierarchy = build_hierarchy(relations, parent_map)

            # Save relations and hierarchy to files
            save_to_file(relations, relations_file)
            save_to_file(hierarchy, hierarchy_file)

    # Example usage: Get hierarchy for a specific relation
    relation_id = 3275052  # Example: Relation ID for Prawda
    hierarchy_list = hierarchy.get(relation_id, [])
    print(f"Hierarchy for relation ID {relation_id}:")
    for level in hierarchy_list:
        print(f"  Admin level: {level['admin_level']}, Name: {level['name']}, Relation ID: {level['relation_id']}, Postcode: {level['postcode']}")

    # Example usage: Get hierarchy for another specific relation
    relation_id = 454648  # Example: Relation ID for Pieńsk
    hierarchy_list = hierarchy.get(relation_id, [])
    print(f"\nHierarchy for relation ID {relation_id}:")
    for level in hierarchy_list:
        print(f"  Admin level: {level['admin_level']}, Name: {level['name']}, Relation ID: {level['relation_id']}, Postcode: {level['postcode']}")


if __name__ == "__main__":
    main()

Uzyskuję 2 pliki:

  1. hierarchy.json
{
"6252503": [
        {
            "admin_level": "2",
            "name": "Polska",
            "relation_id": 49715,
            "postcode": "No postcode"
        },
        {
            "admin_level": "4",
            "name": "województwo dolnośląskie",
            "relation_id": 224457,
            "postcode": "No postcode"
        },
        {
            "admin_level": "6",
            "name": "powiat oleśnicki",
            "relation_id": 451525,
            "postcode": "No postcode"
        },
        {
            "admin_level": "7",
            "name": "gmina Syców",
            "relation_id": 2942236,
            "postcode": "No postcode"
        }
    ],
    "6252504": [
        {
            "admin_level": "2",
            "name": "Polska",
            "relation_id": 49715,
            "postcode": "No postcode"
        },
        {
            "admin_level": "4",
            "name": "województwo dolnośląskie",
            "relation_id": 224457,
            "postcode": "No postcode"
        },
        {
            "admin_level": "6",
            "name": "powiat oleśnicki",
            "relation_id": 451525,
            "postcode": "No postcode"
        },
        {
            "admin_level": "7",
            "name": "gmina Syców",
            "relation_id": 2942236,
            "postcode": "No postcode"
        }
    ]
}
  1. relations.json
{
"4454375": {
        "relation_id": 4454375,
        "admin_level": "8",
        "name": "Nieświń",
        "tags": {
            "admin_level": "8",
            "boundary": "administrative",
            "name": "Nieświń",
            "type": "boundary",
            "wikidata": "Q11791957",
            "wikipedia": "pl:Nieświń"
        },
        "members": [
            {
                "type": "n",
                "ref": 2422705569,
                "role": "admin_centre"
            },
            {
                "type": "w",
                "ref": 320168064,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 320120443,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 320119364,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 320119368,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 319979168,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 319979166,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 319979167,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 319980853,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 320168063,
                "role": "outer"
            }
        ],
        "postcode": "No postcode"
    },
    "4454496": {
        "relation_id": 4454496,
        "admin_level": "8",
        "name": "Czerwony Most",
        "tags": {
            "admin_level": "8",
            "boundary": "administrative",
            "name": "Czerwony Most",
            "type": "boundary",
            "wikidata": "Q5202092",
            "wikipedia": "pl:Czerwony Most"
        },
        "members": [
            {
                "type": "n",
                "ref": 6166867262,
                "role": "admin_centre"
            },
            {
                "type": "w",
                "ref": 320172666,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 320172678,
                "role": "outer"
            },
            {
                "type": "w",
                "ref": 218046826,
                "role": "outer"
            }
        ],
        "postcode": "No postcode"
    }
}

Napisałem drugi kod w których chciałem uzyskać takie dane:

{
“12345”: [
[30.0, 10.0],
[40.0, 40.0],
[20.0, 40.0],
[10.0, 20.0]
],
“67890”: [
[35.0, 15.0],
[45.0, 45.0],
[25.0, 45.0],
[15.0, 25.0]
]
}

Abym mógł pobrać z tych wyżej danych i wstawić w “members”: [ z relations.json.

Trochę masło maślane ale mam nadzieję, że zrozumiałeś o co mi chodzi :smiley:

Jak to w bazie danych ląduje to bym rozważył https://osm2pgsql.org/ (ale z importowaniem przefiltrowanych danych, nie całej Polski - chyba ze nie tylko granice potrzeba)

osmtogeojson też używałem (nawet jak w Pythonie coś robiłem)

Właśnie nie chce używać pgsql ;(

Dlatego chciałbym właśnie pobrać granicę dla konkretnej relacji np. Województwa/gminy itp.

no to musisz uzyskać z relacji jakie linie w nie wchodzą, z lini uzyskać punkty a z punktów położenie

A mógłbyś mi w tym pomóc? ;( bo już nad tym co napisałem siedziałem tydzień… w jaki sposób pobrać granicę relacji jako geometry

A osmium nie ma czasem dokumentacji co do tego? Sprawdzałeś ją?

@Sebastian_Szyja

Dobrze by było jak byś napisał co chcesz z tym dalej zrobić, bo zależnie od tego można zupełnie inaczej podejść do tematu, czy chcesz te dane w OSM walidować, czy jakoś je analizować pod kątem OSMa, czy po prostu potrzebujesz granic administracyjnych i uznałeś, że dane OSM będą dobrym wyborem?

Inna kwestia, to to, że nie wyciągniesz z OSMa granic wszystkich miejscowości, bo ich najzwyczajniej nie ma, jest tylko część, a to wynika z tego, że oficjalnie też ich nie ma. Oficjalne kończą się na gminach, dalej co prawda masz jednostki/obręby ewidencyjne, ale to trochę inny temat.

Jeśli chcesz mieć wszystkie granice województw/powiatów/gmin w Polsce, a niekoniecznie muszą to być dane OSM, to może być zdecydowanie lepszym wyborem pobranie paczki z danych urzędowych np. z Geoportalu:
https://www.geoportal.gov.pl/pl/dane/panstwowy-rejestr-granic-prg/

Te z poprzedniego roku mają od razu więcej formatów np. w takim .shp są pewnie gotowe biblioteki w Pythonie, albo możesz przekonwertować, choćby mapshaperem, który polecam – działa bardzo szybko dla dużych zbiorów danych i pozwala jest ładnie uprościć. Dlaczego wspominam o uproszczeniu?
Jest szansa (zależnie od celu wykorzystania danych), że jak będziesz chciał coś z tym geometriami robić niezależnie od tego, czy z danych OSM, czy urzędowych, to zaraz możesz natknąć na kolejny problem zbyt dużej szczegółowości.

Inna opcja wyciągania danych z OSM to skorzystanie np. z query overpassa:

[out:json][timeout:25];

{{geocodeArea:Poland}}->.searchArea;
rel["boundary"="administrative"]["admin_level"="7"](area.searchArea);
out geom;

które również ma różne opcje eksportu, ale raczej do wyższego admin level lepiej unikać, chyba że porcjami.

1 Like

Ogólnie mam już zrobiony podział administracyjny tzn. mam miejscowość WROCŁAW a więc wiem że znajduje się w powiecie Wrocław w województwie dolnośląskim.

Czyli mam hierarchy:


{
"6252503": [
        {
            "admin_level": "2",
            "name": "Polska",
            "relation_id": 49715,
            "postcode": "No postcode"
        },
        {
            "admin_level": "4",
            "name": "województwo dolnośląskie",
            "relation_id": 224457,
            "postcode": "No postcode"
        },
        {
            "admin_level": "6",
            "name": "powiat oleśnicki",
            "relation_id": 451525,
            "postcode": "No postcode"
        },
        {
            "admin_level": "7",
            "name": "gmina Syców",
            "relation_id": 2942236,
            "postcode": "No postcode"
        }
    ],
    "6252504": [
        {
            "admin_level": "2",
            "name": "Polska",
            "relation_id": 49715,
            "postcode": "No postcode"
        },
        {
            "admin_level": "4",
            "name": "województwo dolnośląskie",
            "relation_id": 224457,
            "postcode": "No postcode"
        },
        {
            "admin_level": "6",
            "name": "powiat oleśnicki",
            "relation_id": 451525,
            "postcode": "No postcode"
        },
        {
            "admin_level": "7",
            "name": "gmina Syców",
            "relation_id": 2942236,
            "postcode": "No postcode"
        }
    ]
}

To wszystko jest narazie bez geometrii może nawet obejdzie się bez niej jeżeli znajdę jakieś na to rozwiązanie…

I skoro mam już podział terytorialny to chce pobrać wszystkie gminy i przypisać je do hierarchii np. ulica Wojska Polskiego znajduje się we Wrocławiu. Alee… pojawia się problem - o ile dodanie ulic nie jest problemem to później przypisanie ich do gmin już tak. Już tłumaczę o co mi chodzi.

W przypadku małych jednostek administracyjnych np. Sycow (gmina) ma ona jeden kod pocztowy 56-500 i dotyczy on całej gminy wraz z mniejszymi miejscowościami i ulicami ale w przypadku Wrocławia czy Warszawy jest kilkadziesiąt kodów pocztowych i działają one na zasadzie np. 3ulic i nie mogę znaleźć rozwiązania dla tego problemu dlatego pomyślałem o przechowywaniu danych geometrycznych tzn. Przechowuje granicę gminy, przechowuje geometrię ulic więc mogę wyciągną informacje jaka gmina przypisana jest do danej ulicy.

Może macie inny pomysł na znalezienie rozwiązania? Pewnie zapytacie a czemu nie mogę skorzystać z API Teryt - właśnie nie chce z niej korzystać z kilku powodów.

  1. Nie ma tam gmin
  2. Chce pracować również na innych państwach a nie tylko POLSKA - docelowo muszę mieć podzielone (Polskę, Niemcy, Czechy i Austrię)

W pythonie nie pomogę, natomiast służę przykładami w R: Open Data sources – 2  OpenStreetMap as a universal spatial data source. Szczególnej uwadze polecam pakiety osmdata i osmextract. Ogólnodostępnym źródłem granic jest GADM: GADM, którego granice są jakimś tam przybliżeniem danych OSM, ale dostępne już w wersji wektorowej/przestrzennej.

Jeśli chcesz uzyskać relacje między danymi przestrzennymi, użyj formatu danych przestrzennych (sądząc po wstawionym obrazku, używasz MySQLa, który również dane przestrzenne obsługuje (https://dev.mysql.com/doc/refman/8.0/en/gis-data-formats.html)). Do zaimportowania danych użyłbym ogr2ogr (ogr2ogr — GDAL documentation), który bez problemu czyta dane OSM i zapisuje do MySQLa (Vector drivers — GDAL documentation). A następnie użył analizy przestrzennej, coś w rodzaju ST_Within (https://dev.mysql.com/doc/refman/8.0/en/spatial-function-reference.html).

EDIT: nie bazowałbym na kodach pocztowych, dość dobrze zmapowane są w Polsce, natomiast w Niemczech – nie bardzo. Również zwróciłbym uwagę na aktualność granic - nawet w .pl nie wszystkie granice gmin są aktualne. Przypisanie ulicy do konkretnej miejscowości/gminy też może napotkać na problemy, zwłaszcza w miejscach gdy ulica przebiega przez lub nawet pokrywa się z granicą. Dla .pl rozważyłbym wykorzystanie danych państwowych: BDOT10k (przebiegi granic) i TERYT.