├── calc_stat_all.sh ├── sum_stat.py ├── calc_stat.sh ├── config.txt ├── README.md └── format_stat.py /calc_stat_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | 4 | if [ $# -lt 3 ]; then 5 | echo "Compare mwm files in two directories. Prints CSV." 6 | echo "Usage: $0 [threshold]" 7 | exit 1 8 | fi 9 | 10 | THRESHOLD=0 11 | [ $# -gt 3 ] && THRESHOLD=$4 12 | 13 | HEAD=h 14 | for old in "$2"/*.mwm; do 15 | BASE_NAME="$(basename "$old" .mwm)" 16 | if [ -f "$3/$BASE_NAME.mwm" ]; then 17 | echo 18 | echo "$BASE_NAME" 19 | bash "$(dirname "$0")/calc_stat.sh" --csv$HEAD "$1" "$2/$BASE_NAME.mwm" x $THRESHOLD "$3/$BASE_NAME.mwm" 20 | HEAD= 21 | fi 22 | done 23 | -------------------------------------------------------------------------------- /sum_stat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import sys, re 4 | 5 | RE_STAT = re.compile(r'(?:\d+\. )?([\w:|-]+?)\|: size = \d+; count = (\d+); length = ([0-9.e+-]+) m; area = ([0-9.e+-]+) m²\s*') 6 | 7 | if len(sys.argv) < 3: 8 | print "Usage: {0} [etc etc]".format(sys.argv[0]) 9 | sys.exit(1) 10 | 11 | result = {} 12 | order = [] 13 | with open(sys.argv[1], 'r') as f: 14 | for line in f: 15 | m = RE_STAT.match(line) 16 | if m: 17 | k = m.group(1) 18 | order.append(k) 19 | result[k] = [int(m.group(2)), float(m.group(3)), float(m.group(4))] 20 | 21 | for i in range(2, len(sys.argv)): 22 | with open(sys.argv[i], 'r') as f: 23 | for line in f: 24 | m = RE_STAT.match(line) 25 | if m: 26 | k = m.group(1) 27 | if not k in result: 28 | order.append(k) 29 | result[k] = [0, 0.0, 0.0] 30 | result[k][0] += int(m.group(2)) 31 | result[k][1] += float(m.group(3)) 32 | result[k][2] += float(m.group(4)) 33 | 34 | for k in order: 35 | print "{0}|: size = 0; count = {1}; length = {2} m; area = {3} m²".format(k, result[k][0], result[k][1], result[k][2]) 36 | -------------------------------------------------------------------------------- /calc_stat.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u -e 3 | 4 | usage() { 5 | echo "This script calculates statistics for a given MWM file." 6 | echo "It requires a configuration file for classificator types." 7 | echo 8 | echo "Usage: $0 [--csv] [ ...] [ x [] [ ...] ]" 9 | echo 10 | echo "Threshold is for printing values in percents, default is 1, must be integer" 11 | exit 1 12 | } 13 | 14 | [ $# -lt 2 ] && usage 15 | 16 | if [ $# -gt 0 -a \( "$1" == "--csv" -o "$1" == "--csvh" \) ]; then 17 | CSV=--csv 18 | if [ "$1" == "--csvh" ]; then 19 | echo "sep=;" 20 | echo "Критерий;Было;Стало;Ед.;Разница;Ед.;В процентах" 21 | fi 22 | shift 23 | [ $# -lt 2 ] && usage 24 | fi 25 | 26 | CONFIG="$1" 27 | shift 28 | FORMAT_PY="$(dirname "$0")/format_stat.py" 29 | SUM_PY="$(dirname "$0")/sum_stat.py" 30 | THRESHOLD=1 31 | [ ! -f "$FORMAT_PY" ] && echo "Cannot find $FORMAT_PY" && exit 1 32 | export OMIM_PATH="${OMIM_PATH:-$(cd "$(dirname "$0")/../omim"; pwd)}" 33 | source "$OMIM_PATH/tools/unix/find_generator_tool.sh" 1>&2 34 | 35 | if [ "$(uname)" == "Darwin" ]; then 36 | INTDIR=$(mktemp -d -t calcstat) 37 | else 38 | INTDIR=$(mktemp -d) 39 | fi 40 | trap "rm -rf \"${INTDIR}\"" EXIT SIGINT SIGTERM 41 | 42 | TARGET="$INTDIR/old" 43 | TMP="$INTDIR/tmp" 44 | 45 | while [ $# -gt 0 ]; do 46 | if [ "$1" == "x" ]; then 47 | TARGET="$INTDIR/new" 48 | if [ $# -gt 1 -a -n "${2##*[!0-9.]*}" ]; then 49 | THRESHOLD=$2 50 | shift 51 | fi 52 | elif [[ $1 == *.mwm ]]; then 53 | SOURCE_PATH="$(dirname "$1")" 54 | SOURCE_NAME="$(basename "$1" .mwm)" 55 | [ ! -f "$SOURCE_PATH/$SOURCE_NAME.mwm" ] && fail "Cannot find $SOURCE_PATH/$SOURCE_NAME.mwm" && exit 2 56 | "$GENERATOR_TOOL" --data_path="$SOURCE_PATH" --user_resource_path="$OMIM_PATH/data/" --type_statistics --output="$SOURCE_NAME" > "$TMP" 2>&1 57 | if [ -e "$TARGET" ]; then 58 | python "$SUM_PY" "$TARGET" "$TMP" > "$TARGET.2" 59 | mv "$TARGET.2" "$TARGET" 60 | rm "$TMP" 61 | else 62 | mv "$TMP" "$TARGET" 63 | fi 64 | fi 65 | shift 66 | done 67 | 68 | if [ -e "$INTDIR/new" ]; then 69 | python "$FORMAT_PY" ${CSV-} "$CONFIG" "$INTDIR/old" "$INTDIR/new" $THRESHOLD 70 | else 71 | python "$FORMAT_PY" ${CSV-} "$CONFIG" "$INTDIR/old" 72 | fi 73 | -------------------------------------------------------------------------------- /config.txt: -------------------------------------------------------------------------------- 1 | barrier-(fence|gate);len;Заборы 2 | building;cnt;Здания 3 | (amenity|shop|historic)-.*;cnt;POI 4 | amenity-(cafe|restaurant|fast_food).*;cnt;Кафе и рестораны 5 | amenity-(pub|bar);cnt;Бары и пабы 6 | amenity-kindergarten;cnt;Детские сады 7 | amenity-(school|university|college);cnt;Школы и университеты 8 | amenity-parking.*;cnt;Автостоянки 9 | amenity-parking.*;area;Автостоянки 10 | amenity-pharmacy;cnt;Аптеки 11 | amenity-place_of_worship.*;cnt;Храмы 12 | amenity-(hospital|doctors);cnt;Больницы и поликлиники 13 | amenity-toilets;cnt;Туалеты 14 | amenity-(waste_disposal|recycling);cnt;Мусорные баки 15 | highway-(motorway|trunk|primary|secondary|tertiary|residential|unclassified|service|track|living_street)(_link)?(-.*)?;len;Автодорожная сеть 16 | highway-(footway|path|pedestrian|steps).*;len;Пешеходные дорожки 17 | highway-.*-bridge;len;Мосты 18 | highway-.*-tunnel;len;Туннели 19 | highway-(footway|path|steps)-bridge;len;Пешеходные мосты 20 | highway-(footway|path|steps)-tunnel;len;Пешеходные туннели 21 | highway-steps.*;len;Лестницы 22 | highway-speed_camera;cnt;Камеры контроля скорости 23 | internet_access-wlan;cnt;Точки доступа Wi-Fi 24 | leisure-(pitch|stadium|playing_fields|track|sports_centre).*;cnt;Спортплощадки и комплексы 25 | leisure-playground;cnt;Детские площадки 26 | man_made-lighthouse;cnt;Маяки 27 | man_made-windmill;cnt;Ветряные мельницы 28 | man_made-pipeline.*;len;Трубопроводы 29 | natural-beach;cnt;Пляжи 30 | natural-tree;cnt;Отдельностоящие деревья 31 | natural-waterfall;cnt;Водопады 32 | piste:type.*;len;Лыжни 33 | place-(city.*|town|village|hamlet);cnt;Населённые пункты 34 | place-island;cnt;Острова 35 | power-(minor_)?line.*;len;Линии электропередачи 36 | power-(pole|tower);cnt;Опоры ЛЭП 37 | railway-(rail|monorail|light_rail|narrow_gauge|preserved|siding|spur|yard|disused|incline).*;len;Железные дороги 38 | railway-.*-(bridge|tunnel);len;Железнодорожные мосты и туннели 39 | railway-(razed|abandoned).*;len;Снятые ветки ж/д 40 | railway-narrow_gauge.*;len;Узкоколейные ж/д 41 | railway-tram(-.*)?;len;Трамвайные пути 42 | railway-(halt|station);cnt;Станции железной дороги 43 | railway-subway.*;len;Линии метро 44 | highway-bus_stop|railway-tram_stop;cnt;Остановки наземного транспорта 45 | shop-bakery;cnt;Пекарни 46 | shop-books;cnt;Книжные магазины 47 | shop-clothes;cnt;Магазины одежды 48 | shop-shoes;cnt;Магазины обуви 49 | shop-(convenience|supermarket);cnt;Продуктовые магазины 50 | shop-florist;cnt;Цветочные салоны 51 | shop-(hairdresser|beauty);cnt;Парикмахерские и салоны красоты 52 | tourism-(guest_house|hos?tel|motel);cnt;Гостиницы и хостелы 53 | tourism-(attraction|viewpoint);cnt;Достопримечательности и точки обзора 54 | waterway-(canal|river|stream)(-.*)?;len;Реки, каналы и ручьи 55 | landuse-cemetery.*;area;Кладбища 56 | leisure-park.*;area;Парки 57 | natural-beach;area;Пляжи 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Статистика по объектам в MWM 2 | 3 | Этот набор скриптов считает статистику по фичам в заданной MWM, 4 | а также разницу между двумя наборами MWM. Например, можно посчитать, 5 | насколько выросла дорожная сеть во всей России между релизами. 6 | 7 | Основной режим работы — сравнение целых каталогов. Чтобы сравнить 8 | два релиза и получить файл CSV для загрузки в электронные таблицы, 9 | запустите как-то так: 10 | 11 | ./calc_stat_all.sh config.txt /opt/mapsme/mwm/150613 /opt/mapsme/mwm/150624 10 12 | 13 | Здесь первым параметром идёт имя файла со списком критериев. Он прост: через 14 | точку с запятой идут регулярное выражения для типа по классификатору, тип 15 | метрики (`cnt` для количества, `len` для длины, `area` для площади), название критерия на русском языке. 16 | Второй и третий параметры — предыдущий и новый релизы. Сравнение идёт по всем 17 | файлам `*.mwm`, присутствующим в обоих каталогах. Последнее число — минимум 18 | изменения значения в процентах, при котором оно выводится в итоговый файл. 19 | По умолчанию выводятся все критерии, но для 300+ файлов это может быть многовато. 20 | 21 | ## calc_stat.sh 22 | 23 | Сравнить два отдельных файла, или два набора файлов, можно этим скриптом. 24 | Его же использует `calc_stat_all.sh`. Для сравнения двух файлов формат прост: 25 | 26 | ./calc_stat.sh config.txt old.mwm x new.mwm 27 | 28 | Внимание на `x`, который разделяет старый и новый файл. Очевидно, что с таким 29 | форматом файлов может быть много, и их нужно указывать через пробел. Все 30 | значения для каждого набора файлов суммируются. 31 | 32 | ./calc_stat.sh config.txt old/Russia_*.mwm x 10 new/Russia_*.mwm 33 | 34 | Число 10 — это та же нижняя граница изменения значений для фильтрации результатов. 35 | Скрипт по умолчанию выводит список в человекочитаемом формате, чтобы 36 | получить данные в формате CSV, нужно перед config.txt добавить ключ `--csv`. 37 | 38 | Наконец, этот скрипт позволяет вывести информацию без сравнения, только для 39 | одного или одной группы файлов MWM. Для этого просто не добавляйте `x` и вторую 40 | группу файлов. Например: 41 | 42 | ./calc_stat.sh --csv config.txt Australia.mwm 43 | 44 | ### Проблемы? 45 | 46 | Если скрипт пишет, что не найден `find_generator_tool.sh`, то нужно указать 47 | путь к репозиторию omim: 48 | 49 | OMIM_PATH=../omim-zv ./calc_stat.sh config.txt Australia.mwm 50 | 51 | Если скрипт выводит нули для всех полей, скрипт находит неправильный 52 | `generator_tool`, собранный не из ветки статистики. Например, релиз 53 | вместо дебага. Который из них, можно посмотреть в первой строчке вывода. 54 | Придётся указать путь к файлу (`GENERATOR_TOOL`) или к каталогу сборки: 55 | 56 | BUILD_PATH=../build-omim-debug OMIM_PATH=../omim-zv ./calc_stat.sh config.txt Austria.mwm 57 | 58 | ## Как оно работает 59 | 60 | Для получения количества объектов и длины линий по каждому типу используется 61 | `generator_tool` с ключом `--type_statistics`. Он выводит количество, длину 62 | и площадь для каждого типа объектов в файле mwm. 63 | 64 | Из вывода генератора текст в понятном формате или CSV получается скриптом 65 | `format_stat.py`. Он же сравнивает два файла между собой. Когда на входе 66 | набор файлов, для суммирования значений используется `sum_stat.py`. 67 | 68 | ## Автор и лицензия 69 | 70 | Скрипты написаны Ильёй Зверевым из MAPS.ME и опубликованы под лицензией MIT. 71 | -------------------------------------------------------------------------------- /format_stat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | import sys, re 4 | 5 | RE_STAT = re.compile(r'(?:\d+\. )?([\w:|-]+?)\|: size = \d+; count = (\d+); length = ([0-9.e+-]+) m; area = ([0-9.e+-]+) m²\s*') 6 | 7 | def read_stat(f): 8 | stats = [] 9 | for line in f: 10 | m = RE_STAT.match(line) 11 | if m: 12 | stats.append({ "name": m.group(1).replace('|', '-'), "cnt": int(m.group(2)), "len": float(m.group(3)), "area": float(m.group(4)) }) 13 | return stats 14 | 15 | def read_config(f): 16 | config = [] 17 | for line in f: 18 | columns = [c.strip() for c in line.split(';', 2)] 19 | if len(columns) == 3 and len(columns[0]) > 1: 20 | columns[0] = re.compile(columns[0]) 21 | columns[1] = columns[1].lower() 22 | config.append(columns) 23 | return config 24 | 25 | def process_stat(config, stats): 26 | result = {} 27 | for param in config: 28 | res = 0 29 | for typ in stats: 30 | if param[0].match(typ['name']): 31 | if param[1] == 'len': 32 | res += typ['len'] 33 | elif param[1] == 'area': 34 | res += typ['area'] 35 | else: 36 | res += typ['cnt'] 37 | result[str(param[0])+param[1]] = res 38 | return result 39 | 40 | def format_res(res, typ): 41 | if typ == 'len': 42 | if abs(res) < 1: 43 | res *= 100 44 | unit = 'см' 45 | elif abs(res) < 1000: 46 | unit = 'м' 47 | elif abs(res) < 1000000: 48 | res /= 1000 49 | unit = 'км' 50 | else: 51 | res /= 1000000 52 | unit = 'тыс. км' 53 | if res != 0: 54 | res = '{0:.2f}'.format(res) 55 | elif typ == 'area': 56 | if abs(res) < 10000: 57 | unit = 'м²' 58 | elif abs(res) < 1000000000: 59 | res /= 1000000 60 | unit = 'км²' 61 | else: 62 | res /= 1000000000 63 | unit = 'тыс. км²' 64 | if res != 0: 65 | res = '{0:.2f}'.format(res) 66 | else: 67 | unit = 'шт.' 68 | return (res, unit) 69 | 70 | if len(sys.argv) > 1 and sys.argv[1] == '--csv': 71 | csv = True 72 | del sys.argv[1] 73 | else: 74 | csv = False 75 | 76 | if len(sys.argv) <= 2: 77 | print "Usage: {0} [--csv] [new_stats.txt] [threshold]".format(sys.argv[0]) 78 | sys.exit(1) 79 | 80 | with open(sys.argv[1], 'r') as f: 81 | config = read_config(f) 82 | with open(sys.argv[2], 'r') as f: 83 | stats = process_stat(config, read_stat(f)) 84 | if len(sys.argv) > 3: 85 | with open(sys.argv[3], 'r') as f: 86 | new_stats = process_stat(config, read_stat(f)) 87 | threshold = 0 if len(sys.argv) <= 4 else int(sys.argv[4]) 88 | else: 89 | threshold = -1 90 | 91 | for param in config: 92 | k = str(param[0]) + param[1] 93 | st = format_res(stats[k], param[1]) 94 | if threshold < 0: 95 | if st[0] > 0.0: 96 | if csv: 97 | #print ';'.join([str(c) for c in (param[2], stats[param[0]], param[1])]) 98 | print ';'.join([str(c) for c in (param[2], st[0], st[1])]) 99 | else: 100 | print "{0}: {1} {2}".format(param[2], st[0], st[1]) 101 | else: 102 | nst = format_res(new_stats[k], param[1]) 103 | diff = format_res(new_stats[k] - stats[k], param[1]) 104 | percent = 0 if stats[k] <= 0 else (new_stats[k] - stats[k]) * 100.0 / stats[k] 105 | if abs(percent) >= threshold: 106 | if csv: 107 | print ';'.join([str(c) for c in (param[2], st[0], nst[0], st[1], diff[0], diff[1], percent / 100)]) 108 | #print ';'.join([str(c) for c in (param[2], stats[param[0]], new_stats[param[0]], param[1], new_stats[param[0]] - stats[param[0]], percent)]) 109 | else: 110 | print "{0}: было {1} {2}, стало {3} {4} ({5}{6} {7}, {8:.1f}%)".format(param[2], st[0], st[1], nst[0], nst[1], '+' if float(diff[0]) > 0 else '', diff[0], diff[1], percent) 111 | --------------------------------------------------------------------------------