PROGRAMMING

Lista wszystkich miast i województw w bazie danych

#python , #django , #sql

Do projektu, który aktualnie siedzi w mojej głowie będzie potrzebna lista wszystkich miast polski wraz z przypisanymi do nich województwami. Spędziłem trochę czasu na poszukiwanie gotowych danych, które mógłbym zaimportować do bazy. Jak można się domyślić - bezskutecznie.

Wpierw baza. Jako, że używam django to zaprojektowałem sobie model. Poniżej jego główna część.

class Province(models.Model):
    name = models.CharField('Name', max_length=20, blank=False, null=False)
    slug = models.SlugField('Slug', max_length=20, unique=True, blank=False, null=False)

class City(models.Model):
    province = models.ForeignKey(Province)
    name = models.CharField('Name', max_length=25, blank=False, null=False)
    slug = models.SlugField('Slug', max_length=25, unique=True, blank=False, null=False)

Jak widać nic skomplikowanego. Do wypełnienia postanowiłem użyć TERYTu, pythona oraz slugify z django.

Ze strony http://www.stat.gov.pl/bip/36_PLK_HTML.htm pobrałem sobie plik BIP_tr010111.txt. Część zawartości wygląda tak:

WOJ;POW;GMI;RODZ;NAZWA;NAZDOD;STAN_NA;
02;;;;DOLNOŚLĄSKIE;województwo;2011-01-01;
02;01;;;bolesławiecki;powiat;2011-01-01;
02;01;01;1;Bolesławiec;gmina miejska;2011-01-01;
02;01;02;2;Bolesławiec;gmina wiejska;2011-01-01;
02;01;03;2;Gromadka;gmina wiejska;2011-01-01;
02;01;04;3;Nowogrodziec;gmina miejsko-wiejska;2011-01-01;
02;01;04;4;Nowogrodziec;miasto;2011-01-01;
02;01;04;5;Nowogrodziec;obszar wiejski;2011-01-01;
02;01;05;2;Osiecznica;gmina wiejska;2011-01-01;

Mnie akurat interesuje tylko kolumna pierwsza, piąta i szósta. W pierwszej mamy numer województwa, w piątej nazwę województwa lub miasta, a w szóstej.. nazwijmy to typ. ;-) Zadaniem było wyciągnięcie z pliku wszystkich linii, które w szóstej kolumnie mają wpisane województwo, miasto lub gmina miejska, a następnie przetworzenie tego tak aby można było łatwo umieścić w bazie. Ponieważ jest to operacja, którą wykonam raz to stwierdziłem, że nie będę tutaj wymyślał super skryptów i po prostu wygeneruję sobie dużą liczbę insertów do bazy ;-) Poniżej kod.

# coding=utf-8
from slughifi import *
import string

province = {}
province_teryt = {}
i = 1
a = 1
temp = []
slug = None
with open('BIP_tr010111.txt', 'rb') as file:
    for line in file.readlines():
        line = line.strip().split(';')
        if line[5] == 'województwo':
            province[line[4]] = i
            province_teryt[line[0]] = line[4]
            tidy = string.capitalize(line[4])
            print "INSERT INTO ads_province (id, name, slug) VALUES (%s, '%s', '%s');" % (i, tidy, slughifi(tidy))
            i += 1
        elif line[5] in ['miasto', 'gmina miejska', 'miasto stołeczne, na prawach powiatu', ]:
            if line[4] in temp:
                count = temp.count(line[4])
                slug = line[4] + str(count + 1)
            else:
                slug = line[4]
                temp.append(line[4])
            print "INSERT INTO ads_city (id, province_id, name, slug) VALUES (%s, %s, '%s', '%s');" % (a, province[province_teryt[line[0]]],
            string.capitalize(line[4]), slughifi(slug))
            a += 1

Ponieważ slugify z django usuwało mi literę “ł” z wyrazów to użyłem slughifi.py, które pobrałem z http://trac.django-fr.org/browser/site/trunk/project/links/slughifi.py?rev=47.

Jak widać skrypt nie jest skomplikowany. Odczytujemy każdą linię z pliku i sprawdzamy jaka informacja się w niej znajduje. Jeśli jest to województwo to tworzymy dwa słowniki w celu poprawnego przypisania go do miasta, a następnie wypisujemy INSERT z odpowiednimi wartościami. Jeśli jest to miasto lub gmina miejska to sprawdzamy wpierw czy znajduje się już w liście temp. Wymagane było to z tego powodu, że nazwy miast mogą się powtarzać. Jeśli tak by się zdarzyło to do nazwy, używanej jako slug dodawana jest liczba ilości poprzednich wystąpień + 1. Na końcu tak samo jak wcześniej wypisywany jest INSERT z odpowiednio pobieranymi danymi z wcześniej tworzonych słowników. W końcowym efekcie otrzymałem 923 linie, których część wygląda tak:

INSERT INTO ads_city (id, province_id, name, slug) VALUES (732, 14, 'Węgorzewo', 'wegorzewo');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (733, 14, 'Elbląg', 'elblag');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (734, 14, 'Olsztyn', 'olsztyn');
INSERT INTO ads_province (id, name, slug) VALUES (15, 'Wielkopolskie', 'wielkopolskie');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (735, 15, 'Chodzież', 'chodziez');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (736, 15, 'Margonin', 'margonin');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (737, 15, 'Szamocin', 'szamocin');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (738, 15, 'Czarnków', 'czarnkow');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (739, 15, 'Krzyż wielkopolski', 'krzyz-wielkopolski');
INSERT INTO ads_city (id, province_id, name, slug) VALUES (740, 15, 'Trzcianka', 'trzcianka');

Otrzymałem w prosty sposób (prosty w sensie pierwszy jaki przyszedł mi do głowy ;-)) kod, który skopiowałem do pgadmin i uruchomiłem jako SQL.

Działa. W bazie mam co chciałem. Nie zajęło to dużo czasu. Misja spełniona.

Jedynym małym problemem jest to, że według wikipedii miast w polsce jest 908. Mój programik wyłapał 907. Nie mam pojęcia, którego brakuje ;-) Ale to się okaże w przyszłości.

Jeśli ktoś ma lepszy pomysł to oczywiście czekam na komentarze. ;-)

// EDIT 2011-08-04 01:17

Już wiem. Brakowało rzeczywiście Warszawy. Po prostu jest określona w pliku jako “miasto stołeczne, na prawach powiatu”. Zmodyfikowałem powyższy skrypt więc już generuje zapytania dla wszystkich miast.