Архив рубрики: Perl

Раскрасим домики

Итак, после предыдущих упражнений здания на нашей карте обзавелись номерами и высотой. Что дальше? Дальше можно, например, раскрасить здания в разные цвета. Можно раскрасить дома в зависимости от основного их предназначения (то есть, в зависимости от содержимого тэгов amenity и building). Можно — в зависимости от принадлежности здания.

Когда-то на сайте нашего университета располагалась схема студенческого городка, на которой были здания четырёх разных цветов: общежития, учебные корпуса, прочие здания студгородка, а также посторонние здания, в студгородок не входящие. То есть, на той схеме здания выделялись и по предназначению, и по принадлежности.

Достичь подобного результат с картами OpenStreetMap, рисуемыми через библиотеку Leaflet, можно разными путями: можно, взяв какую-либо карту, не трогать её растровый слой, оставить его без изменений, добавив свой векторный слой, куда нанести контуры зданий. Пример — usjeans.ru/map. Другой подход — сделать собственный растровый слой, на котором сразу и отметить необходимое. Так как предыдущие эксперименты касались как раз собственного растрового слоя — продолжим опыты в том же направлении.

Отметить принадлежность зданий в OpenStreetMap можно разными путями: первый, очевидный — задать у каждого нужного нам здания какой-нибудь новый тэг. Тэг может быть практически любым — даже при существующих ограничениях на имена можно придумать что-нибудь. То множество тэгов, что применяется в OSM — результат не жёсткого диктата разработчиков, а договорённости сообщества участников и множество это легко расширяемо. Другой путь, который мне представляется более верным — создать отношение. Для группировки объектов, представляющих собой что-то общее, предназначено отношение с типом site. Как выяснилось, нужное мне отношение уже́ существовало — надо было лишь проверить его корректность и внести необходимые изменения (например, добавить здания, в отношение не попавшие).

На следующем этапе, импорте данных, выяснилось, что в imposm не предусмотрен способ сохранить в базе данных информацию о принадлежности к отношению. Во всяком случае, в документации он не описан. Не беда — эту информацию можно внести и после импорта, напрямую обратившись к базе данных. Для хранения информации о принадлежности нужно создать дополнительное поле. Так как нам нужен только признак принадлежности здания конкретному отношению, достаточно будет типа Bool. После всех изменений описание сопоставления данных для зданий в файле imposm-mapping.py стало выглядеть так:

  1. buildings = Polygons(
  2. name = 'buildings',
  3. fields = (
  4. ('area', PseudoArea()),
  5. ('addr:housenumber',String()),
  6. ('building:levels',Integer()),
  7. ('height',Integer()),
  8. ('belongs_to_susu',Bool()),
  9. ),
  10. mapping = {
  11. 'building': (
  12. '__any__',
  13. ),
  14. 'railway': (
  15. 'station',
  16. ),
  17. 'aeroway': (
  18. 'terminal',
  19. ),
  20. }
  21. )

Выставить флаг принадлежности можно выполнением SQL-запроса

UPDATE osm_buildings
SET belongs_to_susu=1
WHERE osm_id IN (список_id, через запятую);

Чтоб не перечислять объекты вручную, был написан перловый скрипт. Попутно выяснилось, что, несмотря на то, что отношение может в себе содержать другие отношения, нет смысла устраивать рекурсивный обход: в поле osm_buildings.osm_id хранится первый попавшийся номер: он может быть как номером линии (для большинства зданий), так и номером отношения.

Задача определения роли здания после всех предыдущих манипуляций кажется не такой уж и сложной: во-первых, тип здания (точнее, его предназначение) может хранится в тэге building — его содержимое при импорте попадает в поле osm_buildings.type. Среди допустимых типов есть и dormitory — общежития (кстати, не надо путать с tourism:hostel — это совсем разные тэги). Тип «учебные корпуса» (например, academic_building) среди часто применяемых не встречается — есть лишь university, который не совсем подходит: университет — это не только учебные корпуса. Можно, конечно, прямо у самих зданий указывать building=academic, но мне более правильным показался вариант указания роли здания внутри отношения — там и поле для роли есть, и оно не используется совсем. Больше шансов, что данные спокойно сохранятся именно тогда, когда они лежат внутри отношения. А вот в базу данных роль вносится в поле osm_buildings.type:

UPDATE osm_buildings
SET type='academic'
WHERE osm_id IN (список_id, через, запятую);

Как и прежде, для упрощения назначения ролей, написан ещё один перловый скрипт.

Стилевые правила для зданий (в OSM Bright он лежат в файле base.mss) начинаются так:

#buildings[zoom>=12] {
  polygon-fill:@building;
  // Our buildings
  [belongs_to_susu=1] {
    polygon-fill: @susu_building;
    [type='dormitory'] {
     polygon-fill: @susu_dormitory;
    }
    [type='academic'] {
     polygon-fill: @susu_academic;
    }
  }

Определение цветов @susu_building, @susu_dormitory, @susu_academic добавлено в файл palette.mss — там для них самое подходящее место.

@susu_building:     saturate(darken(@building, 15%), 10);
@susu_dormitory:    mix(@susu_building, #f60, 95%);
@susu_academic:     mix(@susu_building, #06f, 93%);

Результат:
Карта с разноцветными домами

Отображение закодированной кириллицы в отладчике

Перловый модуль  Data::Dumper, который умеет преобразовывать хитро завёрнутые структуры данных (хотя всякие умеет) в вид, хоть как-то пригодный для чтения, а также Data::Dumper::Perltidy, который делает этот вид ещё лучше, избавляя получившийся текст от безумного количества пробелов. Однако кириллица, добытая при преобразовании JSON-файла в перловый объект, выводится последовательностями видав последовательностями вида \x{43e}\x{43f}\x{430}.

Один из возможных путей обхода — расшифровывать: можно объявить функцию

sub unx {
    my @out;
    while ( local $_ = shift ) {
        s/\\x\{([0-9a-z]+)\}/chr hex $1/ge;
        push @out, $_;
    }
    return @out;
}

и потом её вызывать:

print unx '\x{421}\x{442}\x{430}\x{440}\x{448}\x{438}\x{439}';

Можно, записав её в одну строку, применять её и в отладчике:

  DB‹1› sub unx{my@out;while(local$_=shift){s/\\x\{([0-9a-z]+)\}/chr hex $1/ge;push@out,$_}return@out}
  DB‹2› p unx '\x{421}\x{442}\x{430}\x{440}\x{448}\x{438}\x{439}

С другой стороны, объекту, объявленному в модуле JSON, вроде, можно указать, как отображать не-ASCII данные:

my $json = JSON->new->ascii(0)->utf8;

Однако на преобразование JSON в объект это не влияет.

Perl Debugger Cheatsheet

Чтоб не вспоминать кажыдй раз команды перлового отладчика, сделал шпаргалку — скопировал на лист размера A4 текст, выводимый отладчиком по команде h, и добавил верблюда:

Perl Debugger Cheatsheet

Большой файл доступен в форматах PDF и PNG (300 dpi).

Без лишних движений мышью

Отлаживаю скрипт, получаю развесистую структуру данных, чей дамп, сделанный модулем Data::Dumper, занимает почти 9000 строк. Вывел дамп на экран, потянулся к мышке, чтоб скопировать и засунуть в файл, да вовремя остановился — есть же простой метод: достаточно написать в отладчике всего пару строк:

use File::Slurp
write_file 'big-structure.pl', Dumper $structure

P. S. А если вместо Data::Dumper использовать Data::Dumper::Perltidy, результат получается более симпатичным.

Не zip

На сайте prostopleer.com есть, вроде бы, возможность скачать целиком музыкальный альбом. Но потом — сюрприз! — скачавшийся zip-файл вовсе не является ZIP-архивом: ни unzip, ни 7z не желают его открывать. Лишь всемогущий mplayer спокойно играет одну пьесу за другой.

Анализ содержимого показал: в начале архива идёт стандартная сигнатура PK♥♦ (50 4B 03 04), но за ней — имена файлов вместе с мусором и сами файлы, без какого-либо сжатия. Достать музыку из такого файла при наличии перла — легко!

#!/usr/bin/perl -0777 -n

=head1 DESCRIPTION

Split big pseudo zip-archive from prostopleer.com into small mp3 files.

=head1 USAGE

 ./split.pl big-pseudo.zip

=head1 SEE ALSO

L<< https://gist.github.com/shoorick/7785185 >>

=head1 AUTHOR

Alexander Sapozhnikov
L<< http://shoorick.ru/ >>
L<lt>shoorick@cpan.orgE<gt>

=cut

binmode(STDIN);

my $divisor = "ID3\x04\x00";
my @chunks = split $divisor;
shift @chunks;

my $i = 1;
foreach my $chunk ( @chunks ) {
    open(my $fh, '<', sprintf('%02d.mp3', $i++))
        or die "cannot open for writing: $!";
    print $fh $divisor, $chunk;
    close $fh;
}

Оно же — на гитхабе: https://gist.github.com/shoorick/7785185

Без звёздочек

У Subversion (во всяком случае, у линуксовой версии 1.7) есть странная на первый взгяд особенность: свежедобавленные PHP-файлы при первом же коммите получают свойство svn:executable и становятся исполняемыми, что вовсе не требуется. Предполагая, что это не баг, а фича, заглянул в конфигурационный файл ~/.subversion/config — так и есть. Subversion может выставлять некоторые свойства свежедобавленным файлам. Такое поведение можно настроить. Во-первых, глобально включить — нужный параметр находится в секции [miscellany]:

### Set enable-auto-props to 'yes' to enable automatic properties
### for 'svn add' and 'svn import', it defaults to 'no'.
### Automatic properties are defined in the section 'auto-props'.
enable-auto-props = yes

Во-вторых, настроить, какие именно свойства надо установить:

### Section for configuring automatic properties.
[auto-props]
### The format of the entries is:
### file-name-pattern = propname[=value][;propname[=value]...]
### The file-name-pattern can contain wildcards (such as '*' and
### '?'). All entries which match will be applied to the file.
### Note that auto-props functionality must be enabled, which
### is typically done by setting the 'enable-auto-props' option.
# *.c = svn:eol-style=native
# *.cpp = svn:eol-style=native
# *.h = svn:eol-style=native
# *.dsp = svn:eol-style=CRLF
# *.dsw = svn:eol-style=CRLF
*.pl = svn:eol-style=native;svn:executable
*.pm = svn:eol-style=native
*.php = svn:eol-style=native

В моём случае и для перловых модулей, и для PHP-файлов среди свойств значилось svn:executable — убрал.