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

30 или 31

Писал как-то на перле валидатор дат и додумался до формулы, вычисляющей число дней в месяце — она заказчику не пригодилась, и была выкинута из кода ради сомнительной простоты, так что буду думать эту мысль ещё раз.

Итак, в языках с си-подобным синтаксисом (хотя подойдёт и для питона) определить, сколько же дней в месяце, можно по формуле

days = 30 + (month + 4) * 13 / 12 % 2

где month — номер месяца начиная с 1.

Работает для всех месяцев кроме февраля. Например, перловый однострочник

perl -e 'printf "%2d → %d\n", $_, 30 + ($_ + 4) * 13 / 12 % 2 for 1 .. 12'

выведет

 1 → 31
 2 → 30
 3 → 31
 4 → 30
 5 → 31
 6 → 30
 7 → 31
 8 → 31
 9 → 30
10 → 31
11 → 30
12 → 31

С февралём ситуация чуть более сложная, потому что число дней в нём зависит от номера года: в григорианском календаре високосным годом является такой, который делится на 4, но не на 100, хотя если при этом делится на 400 — всё равно високосный. Например, 1900 год — обычный, а 2000 — високосный, как и 2004.

year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)

можно вообще переписать как

!!(!(year % 4) && ((year % 100) || !(year % 400)))

однако такой вариант хуже — хоть и компактнее, но менее понятен.

Проверим (в перле «из коробки» нет булевых типов, поэтому выводятся нули и единицы)

1600 → 1
1700 → 0
1799 → 0
1800 → 0
1899 → 0
1900 → 0
1901 → 0
1944 → 1
1988 → 1
1999 → 0
2000 → 1
2001 → 0
2004 → 1

Осталось объединить всё в одну формулу

Без второго питона

Время второго питона прошло — вокруг уже третий, а после обновления операционной системы на компьютере до Debian GNU/Linux 11 (bullseye) из доступных к установке пакетов пропало почти всё, относившееся ко второму питону.

computer with Debian linux and two pythons -- yellow and blue one

Однако встречаются ещё программы, которым для работы нужен именно второй питон — для таких случаев можно воспользоваться методом, описанным на compsmag.com/blog/how-you-can-install-python-3-x-or-2-7-on-debian-11-bullseye-linux/, только подходить к написанному надо критически, не копировать команды бездумно — они там содержат избыточное число пробелов, но в них нет переводов строк.

# python2.7 ./get-pip.py 
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Collecting pip<21.0
  Downloading pip-20.3.4-py2.py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 1.2 MB/s 
Collecting setuptools<45
  Downloading setuptools-44.1.1-py2.py3-none-any.whl (583 kB)
     |████████████████████████████████| 583 kB 5.2 MB/s 
Collecting wheel
  Downloading wheel-0.37.1-py2.py3-none-any.whl (35 kB)
Installing collected packages: pip, setuptools, wheel
Successfully installed pip-20.3.4 setuptools-44.1.1 wheel-0.37.1

Ну а при наличии pip можно ставить и остальные модули, хоть для них и нет готовых пакетов. Например, для редактора карт JOSM есть плагин ext_tools, вызывающий трассировщик снимков Scanaerial, которому нужны модули pillow и pyproj — в одиннадцатом дебиане их нельзя поставить командой apt install python-pillow python-pyproj

# python2.7 -m pip install pillow pyproj
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Collecting pillow
  Downloading Pillow-6.2.2-cp27-cp27mu-manylinux1_x86_64.whl (2.1 MB)
     |████████████████████████████████| 2.1 MB 1.1 MB/s 
Collecting pyproj
  Downloading pyproj-2.2.2-cp27-cp27mu-manylinux1_x86_64.whl (11.2 MB)
     |████████████████████████████████| 11.2 MB 4.2 MB/s 
Collecting aenum; python_version < "3.6"
  Downloading aenum-3.1.8-py2-none-any.whl (120 kB)
     |████████████████████████████████| 120 kB 4.9 MB/s 
Installing collected packages: pillow, aenum, pyproj
Successfully installed aenum-3.1.8 pillow-6.2.2 pyproj-2.2.2

Один белый, другой синий — два слона весёлых

Пробую вышедший недавно четвёртый pgAdmin — а это (внезапно!) веб-приложение, как сейчас модно. Ладно хоть, не тащит за собой ни хром, ни мозиллу, а запускается в новой вкладке существующего браузера. Написано на питоне, весит 22 МБ, из коих половина — картинки, клиентские скрипты, шрифты да переводы.

Из Лилипонда в Музскор

Из всего, в чём доводилось набирать ноты последние лет двадцать, самые красивые получаются в Лилипонде — там и шрифт хороший (хотя Bravura ещё лучше), и расположение нот на листе достаточно компактное. Но в MuseScore набирать проще плюс можно услышать любую ноту да и все изменения видны сразу — не надо ждать, пока компиляция завершится. Лет десять назад MuseScore, как и Denemo, можно было использовать лишь для предварительного набора — сборку и доводку приходилось делать в Лилипонде, потому что Музскор не способен был нормально расставить ноты по странице. Со временем Музскор улучшился — теперь его можно использовать для всего процесса — до самого конца — вывода на печать или в готовый PDF-файл.

И встал вопрос открытия лилипондовых файлов Музскором — нынешние его версии умеют работать с MusicXML, но формат Лилипонда не понимают совсем. Сам же LilyPond умеет читать всякое (точнее, в его состав входит скрипт на питоне, перегоняющий в формат Лилипонда ноты из MusicXML, ABC, MIDI и, вроде, ETF), но выводить в это всякое не желает.

Поэтому воспользуемся сторонним ПО. Например, выяснилось, что Frescobaldi умеет экспортировать ноты в формат MusicXML, но по умолчанию эта возможность в нём отключена. Включить можно в настройках: Edit → Preferences → Generap Preferences → Enable experimental features.

Настройки Frescobaldi

После перезапуска Фрескобальди в его меню File → Export можно найти пункт Export to MusicXML. А уж MusicXML можно открыть и в Музскоре, который будет ругаться на на корявость файла,

Сообщение о невалидном XML

Сообщение о повреждённом файле

но всё равно (если повезёт?) его откроет

MuseScore — результат импорта

Вместо экспорта через Frescobaldi можно с командной строки использовать python-ly:


ly musicxml solo.ly -o solo.musicxml

Результат почти полностью совпадает с тем, что выдаёт Фрескобальди — скорее всего, этот же скрипт и вызывается.

Четверть гигабайта

Чего только нет в редакторе Komodo Edit! По сравнению с могучей Komodo IDE нет отладчика, нет профилировщика, нет модульного тестирования, нет интерфейса к системам контроля версий… А весит всё равно дофига!

Komodo Edit

Установочный архив весит четверть гигабайта, потому что внутрь засунули файрфокс, питон и яваскрипт.

Трамвайная схема — можно и в TileMill

В марте я рисовал для википедии челябинскую трамвайную схему при помощи TileMill, однако как выделить трамвайные пути самим тайлмиллом, я не знал. Пришлось тогда экспортировать схему в SVG и затем редактировать полученный SVG-файл в Inkscape, вручную выделяя нужные рельсы и меняя им внешний вид.

На самом деле в том, чтоб TileMill нарисовал трамвайные пути, нет особо хитрой магии. Более того, даже не надо, пугаясь питона, ковырять imposm-mapping.py — трамвайные пути можно выделить при помощи CartoCSS, потому что для железных дорог сохраняется их тип:

SELECT DISTINCT type FROM osm_railways;
type
--------------
preserved
narrow_gauge
light_rail
subway
tram
rail

Стиль трамвайных путей можно задать в файле roads.mss. В OSM Bright все доро́ги: и железные, и обычные — слиты в один слой. Правила рисования дорог весьма витиеваты, поэтому, чтоб не тратить на эксперименты лишние усилия, мы не будем пытаться переопределить стиль трамвайных путей — вместо этого добавим новое правило, рисующее широкие красные линии поверх трамвайных путей, не трогая более ничего. Правило это надо поместить после правил, описывающих слой #roads_high:

#roads_high::tram_highlight [type='tram'] {
  line-width: 5;
  line-color: @tram_line;
  line-comp-op: darken;
}

Результат:

Схема трамвайных путей

Для того, чтоб убедиться в работоспособности метода, этого вполне достаточно.

Бросается в глаза отсутствие подсветки на мостах — связано это с тем, что слой #bridge находится выше, чем #roads_high. Возможный способ решения — завести свой слой железных дорог (а для этого надо уже́ лезть в imposm-mapping.py, но теперь это не страшно), поместить его выше слоя с мостами и написать для железнодорожного слоя свои стилевые правила.

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

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

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

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

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

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

buildings = Polygons(
    name = 'buildings',
    fields = (
        ('area', PseudoArea()),
        ('addr:housenumber',String()),
        ('building:levels',Integer()),
        ('height',Integer()),
        ('belongs_to_susu',Bool()),
    ),
    mapping = {
        'building': (
            '__any__',
        ),
        'railway': (
            'station',
        ),
        'aeroway': (
            'terminal',
        ),
    }
)

Выставить флаг принадлежности можно выполнением 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&gt;=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%);

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