Commit ddfc80d6 authored by Branko Kokanovic's avatar Branko Kokanovic

Ispravljanje uocenih gresaka

* Transliterizacija kucnih brojeva (sada je 17Г i 17G ista stvar)
* Izlaz u HTML-u moze da prikazuje i relatione
* Izlaz u HTML-u moze da prikazuje vise node-ova i way-ova (ranije je
* mogao samo jedan node/way)
* Sitna unapredjenja
parent 8f05a40b
......@@ -51,24 +51,28 @@ predloge), a pošto spojimo podatke iz registra i iz OSM-a:
## Dohvatanje podataka
Da bi se ovo uradilo, napravljena je prosta Python skripta (`main.py`) koja svaku zgradu iz registra pokušava da nađe u OSM-u.
Korišćeni su .PBF i Overpass. Prvo nađemo sve entitete (nodes, ways) koji imaju odgovarajuću ulicu i broj, a onda im
Korišćeni su .PBF i Overpass. Prvo nađemo sve entitete (nodes, ways, relations) koji imaju odgovarajuću ulicu i broj, a onda im
nalazimo okrug i opštinu preko Overpass Turbo query-ja. Takve rezultate čuvamo u pickle formatu (da možemo da nastavimo
posle restarta programa) i u .CSV-u. Rezultat programa je novi CSV koji je korišćen u daljoj analizi.
posle restarta programa). Rezultat programa je novi CSV (output.csv) koji je korišćen u daljoj analizi.
Osim ako ne želite da ga opet generišete, **nema potrebe da pokrećete ovu skriptu**. Rezultat je već [ovde
kao result.csv (avgust 2019.)](https://stambenezajednice.z6.web.core.windows.net/avgust2019.csv).
Inače, da bi se skripta pokrenula, potreban je Python 3.5 (ili noviji) i biblioteke pomenute u requirements.txt. Posto
se koristi Osmium da se čita .pbf, preporučuje se Linux. Skripti treba oko dan-dva neprekidnog rada da završi.
Inače, da bi se skripta pokrenula, potreban je Python 3.5 (ili noviji) i biblioteke pomenute u requirements.txt. Pošto
se koristi Osmium da se čita .pbf, preporučuje se Linux. Skripti treba oko dan-dva _neprekidnog_ rada da završi.
Po generisanju result.csv fajla, potrebno je i izvršiti `generate_html.py` da bi se dobila konačna HTML strana.
### Uočene mane programa/moguća unapređenja
* Postoje entiteti koji nisu pronađeni pošto im se ime ulice ili broj razlikuju u odnosu na registar. Namerno o njima nije vođeno računa, da primetimo i pogrešno nazvane ulice!
* Nije rađena nikakava transliteralizacija prilikom pretrage entiteta. Npr. kućni broj "17Г" nije nađen ako je u OSM-u unesen kao "17G". Opet - i ovo može da se okarakteriše kao namerna odluka.
* Npr. na Novom Beogradu je tako primećeno da nije promenjeno ime ulice (Đorđa Stanojevića u Bulevar Crvene Armije), pa da smo imali pogrešno ime ulice u OSM-u (Milutina Milankovića je bio bulevar, a nije), da "Dr Agostina Neta" treba da ima `alt_name` zato što "Dr" nema tačku u OSM-u (što je pravilno), a RGZ ima tačku.
* ~~Nije rađena nikakava transliteralizacija prilikom pretrage entiteta. Npr. kućni broj "17Г" nije nađen ako je u OSM-u unesen kao "17G". Opet - i ovo može da se okarakteriše kao namerna odluka.~~
* Update: previše ima ovakvih slučajeva, odlučeno je da se radi normalizacija ovih brojeva tako da se "17Г" i "17G" posmatraju kao ista stvar sada.
* Neke ulice postoji više puta u istom okrugu i istoj opštini (npr. Branka Radičevića postoji i u Staroj Pazovi i u Starim Banovcima). Iz RGZ registra se ovo ne može razaznati, pa ova analiza može da ima false positive-a.
* Nominatim pretraga nekad daje bolje rezultate nego prosta .pbf pretraga. Tako je moguće da Nominatim vrati node koji nema "addr:street", ali je pored te ulice. Odluka je bila da je ovo ipak nepravilno i da je bolje da te rezultate ne vraćamo (ipak na taj node treba da se doda "addr:street" tag).
* Generisani izlaz pamti samo da ima više entiteta za datu ulicu i broj, ali ne pamti ih sve (pamti najviše jedan node i jedan way). Idealno bi bilo da ih pamti sve i da se to prikazuje u generisanom HTML-u.
* ~~Generisani izlaz pamti samo da ima više entiteta za datu ulicu i broj, ali ne pamti ih sve (pamti najviše jedan node i jedan way). Idealno bi bilo da ih pamti sve i da se to prikazuje u generisanom HTML-u.~~
* Update: sređeno u kodu, sledeće generisanje rezultata će imati više node-ova i way-ova.
* Trenutno generisanje traje dan-dva. Kada bi se paralelizovalo, bilo bi brže. Međutim, usko grlo je Overpass, pa više threadova treba da pucaju na različite Overpass instance, nikako na istu!
* Bilo bi dobro kada bi pamtili još neke stvari koje možemo/trebamo imati, kao building:levels i sl., ali tu već ulazimo u teritoriju osmose projekta.
* Ako RGZ da novi dataset i budemo ponovo radili analizu, značilo bi da ne radimo sve, već samo da merge-ujemo postojeće rezultate sa novim (nema potrebe tražiti ponovo node za neku ulicu i broj, dovoljno je proveriti da li dati node ID još uvek postoji i pokazuje na istu ulicu/broj)
......
# -*- coding: utf-8 -*-
import argparse
import csv
from operator import itemgetter
......@@ -7,17 +8,32 @@ from jinja2 import Environment, PackageLoader
def main():
parser = argparse.ArgumentParser(
description='Stambene zajednice HTML generation - helper tool to generate HTML report from output.csv file')
parser.add_argument('-o', '--input-file', default='output.csv',
help='Name of input CSV file. Default value is "output.csv"')
parser.add_argument('--filter-district', default=None, help='If filtering by district should be used')
parser.add_argument('--filter-municipality', default=None, help='If filtering by municipality should be used')
args = parser.parse_args()
input_file = args.input_file
filter_district = args.filter_district
filter_municipality = args.filter_municipality
env = Environment(loader=PackageLoader('__main__', '../templates'))
template = env.get_template('index_template.html')
stambene_zajednice = []
districts = {'': 0}
max_district_id = 0
with open('result.csv') as csvfile:
with open(input_file) as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
#if len(stambene_zajednice) > 100:
# break
if filter_district is not None and row['district'] != filter_district:
continue
if filter_municipality is not None and row['municipality'] != filter_municipality:
continue
stambene_zajednice.append(row)
for sz in stambene_zajednice:
......@@ -27,7 +43,10 @@ def main():
sz['district_id'] = districts[sz['district']]
sz['street'] = sz['street'].replace('"', '\\"')
sz['found'] = sz['node'] != "" or sz['way'] != ""
sz['node'] = [] if len(sz['node']) <= 2 else [int(i) for i in sz['node'][1:-1].split(',')]
sz['way'] = [] if len(sz['way']) <= 2 else [int(i) for i in sz['way'][1:-1].split(',')]
sz['relation'] = [] if len(sz['relation']) <= 2 else [int(i) for i in sz['relation'][1:-1].split(',')]
sz['found'] = len(sz['node']) > 0 or len(sz['way']) > 0 or len(sz['relation']) > 0
sz['multiple_entities_same_housenumber'] = sz['multiple_entities_same_housenumber'] or False
sz['building_tag_present'] = sz['building_tag_present'] or False
sz['building_is_apartments'] = sz['building_is_apartments'] or False
......
This diff is collapsed.
# -*- coding: utf-8 -*-
cyr_to_lat = {
'А': 'A', 'Б': 'B', 'В': 'V', 'Г': 'G', 'Д': 'D', 'Е': 'E',
'Ж': 'Ž', 'З': 'Z', 'И': 'I', 'Ј': 'J', 'К': 'K', 'Л': 'L',
'М': 'M', 'Н': 'N', 'Њ': 'Nj', 'О': 'O', 'П': 'P', 'Р': 'R',
'С': 'S', 'Т': 'T', 'Ћ': 'Ć', 'У': 'U', 'Ф': 'F', 'Х': 'H',
'Ц': 'C', 'Ч': 'Č', 'Џ': 'Dž', 'Ш': 'Š', 'Ђ': 'Đ', 'Љ': 'Lj',
'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e',
'ж': 'ž', 'з': 'z', 'и': 'i', 'ј': 'j', 'к': 'k', 'л': 'l',
'љ': 'lj','м': 'm', 'н': 'n', 'њ': 'nj', 'о': 'o', 'п': 'p',
'р': 'r', 'с': 's', 'т': 't', 'ћ': 'ć', 'у': 'u', 'ф': 'f',
'х': 'h', 'ц': 'c', 'ч': 'č', 'џ': 'dž', 'ш': 'š', 'ђ': 'đ'}
def at_least_some_in_cyrillic(s):
return any(ch for ch in s if ch in cyr_to_lat)
def at_least_some_in_latin(s):
return any(ch for ch in s if ch in cyr_to_lat.values())
def cyr2lat(text):
out = ''
for c in text:
if c in cyr_to_lat:
out += cyr_to_lat[c]
else:
out += c
return out
def lat2cyr(text):
out = text
# Covers case of "lj", "nj", "dž"
two_letters_lat = [lat for lat in cyr_to_lat.values() if len(lat) == 2]
for two_letter_lat in two_letters_lat:
cyr_letter = next(key for key in cyr_to_lat.keys() if cyr_to_lat[key] == two_letter_lat)
out = out.replace(two_letter_lat, cyr_letter)
# Covers case of "LJ", "NJ", DŽ" (not in map directly)
two_letters_lat_cap = [lat for lat in cyr_to_lat.values() if len(lat) == 2 and lat[0].isupper()]
for two_letter_lat_cap in two_letters_lat_cap:
cyr_letter = next(key for key in cyr_to_lat.keys() if cyr_to_lat[key] == two_letter_lat_cap)
out = out.replace(two_letter_lat_cap.upper(), cyr_letter)
one_letters_lat = [lat for lat in cyr_to_lat.values() if len(lat) == 1]
for one_letter_lat in one_letters_lat:
cyr_letter = next(key for key in cyr_to_lat.keys() if cyr_to_lat[key] == one_letter_lat)
out = out.replace(one_letter_lat, cyr_letter)
return out
......@@ -28,6 +28,7 @@
<li><strong>Нађен у OSM-у</strong> &mdash; штиклирано ако је зграда нађена у OSM-у (корисно за филтрирање)</li>
<li><strong>Node</strong> &mdash; ID node-а ако је пронађен, клик води на OSM</li>
<li><strong>Way</strong> &mdash; ID way-a ако је пронађен, клик води на OSM</li>
<li><strong>Relation</strong> &mdash; ID relation-a ако је пронађен, клик води на OSM</li>
<li><strong>Нађено више ентитета</strong> &mdash; у OSM-у је нађено више од једног ентитета за дату улицу и број</li>
<li><strong>Таговано као building</strong> &mdash; way је тагован као building=*</li>
<li><strong>building=apartments</strong> &mdash; way је тагован правилно као building=apartments</li>
......@@ -106,6 +107,11 @@
$way = $("#way_id").is(":checked");
}
var $relation = undefined;
if (!$("#relation_id").is(":indeterminate")) {
$relation = $("#relation_id").is(":checked");
}
return (!filter.d || client.d === filter.d)
&& (!filter.m || client.municipality_latin.toLowerCase().indexOf(transliterate(filter.m)) > -1)
&& (!filter.s || client.street_latin.toLowerCase().indexOf(transliterate(filter.s)) > -1)
......@@ -113,6 +119,7 @@
&& (filter.osm_found === undefined || client.osm_found === filter.osm_found)
&& ($node === undefined || ((client.node != '' && $node === true) || (client.node == '' && $node === false)))
&& ($way === undefined || ((client.way != '' && $way === true) || (client.way == '' && $way === false)))
&& ($relation === undefined || ((client.relation != '' && $relation === true) || (client.relation == '' && $relation === false)))
&& (filter.found_muliple === undefined || client.found_muliple === filter.found_muliple)
&& (filter.building === undefined || client.building === filter.building)
&& (filter.apartments === undefined || client.apartments === filter.apartments);
......@@ -124,11 +131,12 @@
db.clients = [
{# This is ugly and condensed because it shaves multiple MBs in produced HTML! #}
{% for sz in stambene_zajednice %}{"d": {{ sz.district_id }},"m": "{{ sz.municipality }}",
{%- for sz in stambene_zajednice %}{"d": {{ sz.district_id }},"m": "{{ sz.municipality }}",
"s": "<a href=\"https://nominatim.openstreetmap.org/search.php?q={{ sz.street }}, {{ sz.municipality }}\" target=\"_blank\">{{ sz.street }}</a>","s_only": "{{ sz.street }}",
"n": "<a href=\"https://nominatim.openstreetmap.org/search.php?q={{ sz.street }} {{ sz.number }}\" target=\"_blank\">{{ sz.number }}</a>","osm_found": {{ sz.found|lower }},"n_only": "{{ sz.number }}",
"node": {% if sz.node|length %}"<a href=\"https://www.openstreetmap.org/node/{{ sz.node }}\" target=\"_blank\">{{ sz.node }}</a>" {% else %}""{% endif %},
"way": {% if sz.way|length %}"<a href=\"https://www.openstreetmap.org/way/{{ sz.way }}\" target=\"_blank\">{{ sz.way }}</a>" {% else %}""{% endif %},
"node": "{% for node in sz.node %}{% if not loop.first %},<br/>{% endif %}<a href=\"https://www.openstreetmap.org/node/{{ node }}\" target=\"_blank\">{{ node }}</a>{% endfor %}",
"way": "{% for way in sz.way %}{% if not loop.first %},<br/>{% endif %}<a href=\"https://www.openstreetmap.org/way/{{ way }}\" target=\"_blank\">{{ way }}</a>{% endfor %}",
"relation": "{% for relation in sz.relation %}{% if not loop.first %},<br/>{% endif %}<a href=\"https://www.openstreetmap.org/relation/{{ relation }}\" target=\"_blank\">{{ relation }}</a>{% endfor %}",
"found_muliple": {{ sz.multiple_entities_same_housenumber|lower }},"building": {{ sz.building_tag_present|lower }},"apartments": {{ sz.building_is_apartments|lower }} },
{% endfor %}
];
......@@ -220,6 +228,30 @@
return $result;
}
},
{ name: "relation", type: "text", width: 50, title: "Relation",
filterTemplate:function(){
var $result = this.filterControl = $('<div style="text-align:center"><input id="relation_id" type="checkbox"></div>').on("click", function(o,v) {
var $cb = $("#relation_id");
if($cb.prop("readOnly")) {
$cb.prop({
checked: false,
readOnly: false
});
} else if(!$cb.prop("checked")) {
$cb.prop({
readOnly: true,
indeterminate: true
});
}
$("#jsGrid").jsGrid("search");
});
$result.find("#relation_id").prop({
readOnly: true,
indeterminate: true
});
return $result;
}
},
{ name: "found_muliple", type: "checkbox", title: "Нађено више ентитета" },
{ name: "building", type: "checkbox", title: "Таговано као building" },
{ name: "apartments", type: "checkbox", title: "building=apartments" },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment