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

Красим школы и стоянки

В прошлых сериях экспериментов с ТайлМиллом доводилось раскрашивать находящиеся в университетском городке здания и добавлять номера домов. Теперь попробуем раскрасить какую-нибудь территорию, да и здания можно перекрасить как-нибудь иначе. Заодно и повод подвернулся: авторы сайта mapstr.ru решили поменять самодельную карту Стерлитамака на OpenStreetMap — там город неплохо отрисован (хотя, конечно, есть ещё, что улучшать), им в целом нравится стиль, которым отрисована карта окрестностей нашего университета, но им хочется, чтоб было как в ДубльГИСе — чтоб стоянки, дворы и частный сектор были залиты разными цветами. Ну и школы с детсадами хочется выделить цветом.

Делаем так:

1. Перед импортом данных OSM в PostGIS в файле imposm-mapping.py указываем, что нам нужно предназначение здания, а относится ли оно к университету, нас не интересует:

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

там же указываем, что у территорий нас интересует тэг residential — его значение rural можно использовать для обозначения деревенских жилых территорий.

landusages = Polygons(
    name = 'landusages',
    fields = (
        ('area', PseudoArea()),
        ('residential', String()),
        ('z_order', ZOrder([
            'pedestrian',
             ...

2. Импортируем данные.

3. Запускаем TileMill, выбираем нужный проект, идём в свойства слоёв, путём редактирования фрагмента SQL-запроса добавляем поля:

  • amenity — к слою #buildings
  • residential — к слоям #landuse, #landuse_gen0, #landuse_gen1

4. Исправляем стилевые правила:

В базовом файле base.mss указываем необходимость особенной заливки частного сектора

#landuse[zoom>12] {
  [type='allotments']    { polygon-fill: @agriculture; }
  // ...
  [type='residential']   {
    polygon-fill: @residential;

    [residential='rural']   {
      polygon-fill: @rural;
    }
  }
}

а также зданий образовательных учреждений: детских садов, школ, ПТУ/техникумов/колледжей и институтов/академий/университетов:

// At the highest zoom levels, render buildings in fancy pseudo-3D.
// Ordering polygons by their Y-position is necessary for this effect
// so we use a separate layer that does this for us.
    [type != 'hedge'] {
     building-fill: @building;
     building-height: 2 + [building:levels] * 2;
     [height>0] {
       building-height: 2 + [height] / 1.5;
     }

      // Our buildings
      [amenity = 'kindergarten'] {
        building-fill: @kindergarten_building;
        line-color:darken(@kindergarten_building,10%);
      }

      [amenity = 'school'] {
        building-fill: @school_building;
        line-color:darken(@school_building,10%);
      }

      [amenity = 'college'] {
        building-fill: @college_building;
        line-color:darken(@college_building,10%);
      }
      [amenity = 'university'] {
        building-fill: @university_building;
        line-color:darken(@university_building,10%);
      }
    }

    [type = 'hedge'] {
      building-fill:@wooded;
      building-height:1.25;
    }
  }
}

добавляем цвета в палитру palette.mss

@residential:       #e9e1cd;
@rural:             #ece2b5;
@commercial:        #e7e0f4;
@industrial:        #eeeeee;
@parking:           #e5eaf0;

/* Buildings */
@building:          #bbaa99;
@educational_mix:   #0099ff;

@kindergarten_building: mix(@building, @educational_mix, 90%);
@school_building:       mix(@building, @educational_mix, 85%);
@college_building:      mix(@building, @educational_mix, 80%);
@university_building:   mix(@building, @educational_mix, 75%);

Результат — на http://mapstr.ru/map/

Фрагмент карты Стерлитамака

Рельеф и дороги

Недавняя запись в ЖЖ-сообществе «Челябинский Челябинск» напомнила, что хотел я как-то собрать рельеф с дорогами на одной карте. Хотел — сделал.

Рельеф и дороги Челябинска

Сделано при помощи Maperitive — он кроссплатформенный (хоть и выглядит по-виндовому), написан на питоне, падает часто. Делаем так:

1. Масштабируем карту, чтоб захватить нужную область.

2. Генерируем фон, с цветом, зависящим от высоты. Если запустить генерацию без дополнительных параметров, то всё зальёт зелёным без заметного контраста, поэтому надо указать свои цвета:

generate-hypsometric ramps=-200:#003399;200:#33cc66;250:#eeee99;350:#993300

Что даст шкалу, где наиболее заметный контраст приходится на высоты от 200 до 350 метров над уровнем моря
Шкала рельефа

3. Для красоты добавляем отмывку: Tools → Generate Hillshading. В меню три подобных пункта — можно выбрать тот, чьи результаты больше нравятся. Если хочется большего контраста, добавлени слоя с отмывкой можно повторить.

4. Скачиваем данные OpenStreetMap для выбранной области: Map → Download Data (Overpass API) Ctrl+Shift+D

5. Создаём стилевой файл с дорогами и границами, но без всего остального, например, такой:

// rendering rules for Maperitive - roads and boundaries
// http://maperitive.net

// Created by Alexander Sapozhnikov, based on style by Igor Brejc
// Released under the Creative Commons Attribution-ShareAlike 3.0 License (http://creativecommons.org/licenses/by-sa/3.0/)

features
	lines
		road major : highway=motorway OR highway=trunk OR highway=primary
		road minor : highway=motorway_link OR highway=trunk_link OR highway=primary_link OR highway=secondary
		boundary country   : relation[type=boundary AND boundary=administrative AND admin_level=2]
		boundary region    : relation[type=boundary AND boundary=administrative AND admin_level=4]
		boundary province  : relation[type=boundary AND boundary=administrative AND admin_level=6]
		boundary municipal : relation[type=boundary AND boundary=administrative AND admin_level=7]

properties
	map-background-color	: #F1EEE8
	map-background-opacity	: 1
	map-sea-color : #B5D0D0
	map.rendering.lflp.min-buffer-space : 5
	map.rendering.lflp.max-allowed-corner-angle : 40

rules

	target : road*
		if : road major
			define
				line-color : #ff9900
				line-width : 2
				border-width : 10%
				border-color : #000000
				border-style : solid
		elseif : road minor
			define
				line-color : #cc6600
				line-width : 1
		draw : line

	target : boundary*
		define
			line-color : #cc3399
			line-style : dash
		if : boundary country
			define
				line-width : 5
		elseif : boundary region
			define
				line-width : 4
		elseif : boundary province
			define
				line-width : 2
		elseif : boundary municipal
			define
				line-width : 1
		draw : line

6. Выбираем этот стиль:

use-ruleset location="/home/as/map/osm/maperitive/rules/road.mrules"

7. Получившуюся карту экспортируем в желаемый формат — см. меню Tools.

И слово было — два байта

Когда-то давно мельком заметил я, что счётчик кадров в фотоаппарате Nikon D70s какой-то странный: номер не всегда возрастает. Порывшись в фотоархиве, нашёл я пачку кадров, где номер сбрасывается, проверил:

#!/bin/sh
for file in `ls 442*jpg`
do
    echo  -n $file
    exiftool $file | grep 'Shutter Count' | cut -c33-44
done

Так и есть!

4420.jpg: 65534
4421.jpg: 65535
4422.jpg: 0
4423.jpg: 1
4424.jpg: 2
4425.jpg: 3
4426.jpg: 4
4427.jpg: 5
4428.jpg: 6
4429.jpg: 7

Выходит, счётчик всего-навсего двухбайтовый. Что же, предполагалось, что камера не может сделать больше, чем 65535 кадров? И ведь ме́ста-то в EXIF для нормального счётчика хватает — поле имеет тип LONG (32-битное беззнаковое целое), но используется лишь наполовину.

Кстати, geeqie показывает некоторые тэги, которые exiftool не отображает и среди них есть Shutter Count 1 (Exif.NikonSi01xx.ShutterCount1), в котором хранятся целые числа со значениями около 869×10⁶ (хотя есть и другие, сильно отличающиеся значения), а также поле Shutter Count 2 (Exif.NikonSi01xx.ShutterCount2), где лежат па́ры небольших целых чисел. Что это за числа — не вышло ни понять, ни нагуглить.

Из одной бочки разливали

Как и ожидалось, Open Conference Systems, якобы не имеющая русской локализации, при должном применении напильника вполне способна использовать великий и могучий. В каталоге lib/pkp даже можно найти русские локализационные XML-файлы. Вообще весь этот каталог lib/pkp — общий и для OCS, и для OJS, что видно по гитхабу. Правда, в свежей версии Open Journal Systems переводов всё-таки побольше. Похоже, OCS, как не особо активно развиваемый продукт, содержит в себе копию lib/php трёхлетней давности, во всяком случае файлы lib/php/locale/ru_RU/*.xml — как раз 2012 года. Надо провести эксперимент — подсунуть в древнюю OCS 2.3 переводы из свежей OJS 2.4.7-1 — скорее всего, хуже не будет. Я пока заметил только один недостаток, мешающий тупо скопировать локализационные файлы: “User Home” переведено как «Мои журналы» — как-то неправильно показывать такое на сайте конференции.

Кстати, коллеги, кто-нибудь пробовал использовать Open Conference Systems для создания сайтов конференций? WordPress и mojowka для этого плохо подходят (хотя можно и с ними — я так делал) — хочется всё-таки использовать специализированное решение, избежав при этом танцев по граблям.

Атомный редактор

Пробую очередной текстовый редактор — Atom. Поначалу не удавалось его запустить — на сайте выложена версия для архитектуры amd64, а собрать из исходников не получилось (не очень-то и хотелось), однако нашёлся вариант для ленивых — PPA с поддержкой 32-битных версий убунты.

Проверка синтаксиса в редакторе Atom

Хоть, конечно, и требует напильника, но пока нравится: работает шустро, клавиатурные сочетания для основных операций с текстом по умолчанию вполне подходящие плюс можно легко скачиватьи ставить модули для функций, которых по умолчанию нет. Вот, например, модуль linter-perl позволяет проверить синтаксис перловых файлов, а svn подсвечивает изменённые строки и файлы при использовании Subversion — до полноценного клиента, правда, ещё далеко.

Внедрение лилипондовых нот в википедию

Случайно обнаружил, что в википедию можно вставлять ноты в виде лилипондового исходного текста и оно само преобразуется в картинку и, если надо, плеер. Для этого достаточно обернуть исходный текст в тэг <score>. Пример — ноты в статье про Атикву.

Ноты

Преимущество такого подхода — простота вставки и отсутствие необходимости держать лилипонд у себя.

Недостатки — неаккуратный внешний вид (это настраивается?) и, видимо, невозможность использовать свои шрифты (кириллица в лилипонде по умолчанию ужасна).

Karta ne po-russki

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

Для того, чтоб иметь контроль над внешним видом подложки, не зависеть от размещающих тайлы (квадратные растровые фрагменты карт) сторонних сервисов, и не платить им денег в конце концов, тайлы генерируются из геоданных OpenStreetMap самостоятельно. OpenStreetMap для любого объекта может содержать множество имён — это и то, что хранится с ключом name — имя вообще, и int_name — международное имя, и куча имён с ключами вида name:ru, name:en, name:что_попало. Если делать тайлы с помощью TileMill, а свой стиль создавать на основе OSM Bright, то доступно только одно имя — name, однако в настройках сопоставления для imposm можно выбрать нужный язык — по умолчанию эта строка в файле imposm-mapping.py закомментирована:

set_default_name_type(LocalizedName(['name:en', 'int_name', 'name']))

Запускаем импорт, английские имена попадают в базу… Однако английских имён мало, сильно меньше, чем объектов с именами, записанными кириллицей.

Выхода из этой ситуации два — правильный и быстрый.
Правильный заключается в аккуратном переводе имён в OSM — слишком долго, да и неохота руками ковыряться.
Быстрый способ — не трогать OSM, а имена транслитерировать локально, в своём экземпляре базы данных. Так и поступим: создадим функцию транслитерации (прообраз подсмотрел на sql.ru) и выполним кучу UPDATE, вызывающих эту функцию. Мне, как перловому программисту, больше был симпатичен вариант с написанием перловой функции внутри PostgreSQL, но сразу такой вариант у меня не заработал, а разбираться было лень.

Итак, скармливаем постгресу такой код:

CREATE OR REPLACE FUNCTION ru_translit(p_string character varying)
  RETURNS character varying AS
$BODY$
-- Transliteration of Cyrillic letters
select
replace(
  replace(
    replace(
      replace(
        replace(
          replace(
            replace(
              replace(
                replace(
                  replace(
                    replace(
                      replace(
                        replace(
                          replace(
                            replace(
                              replace(
                                replace(
                                  replace(
                                    replace(
                                      replace(
                                        replace(
                                          replace(
                                            replace(
                                              replace(
                                                translate(
                                                  $1, 
                                                  'АБВГДЕЗИЙКЛМНОПРСТУФЫЭабвгдезийклмнопрстуфыэ',
                                                  'ABVGDEZIYKLMNOPRSTUFYEabvgdeziyklmnoprstufye'
                                                ),
                                              'ё', 'yo'),
                                            'ж', 'zh'),
                                          'х', 'kh'),
                                        'ц', 'ts'),
                                      'ч', 'ch'),
                                    'ш', 'sh'),
                                  'щ', 'shch'),
                                'ъ', ''),
                              'ь', ''),
                            'э', 'e'),
                          'ю', 'yu'),
                        'я', 'ya'),
                      'Ё', 'Yo'),
                    'Ж', 'Zh'),
                  'Х', 'Kh'),
                'Ц', 'Ts'),
              'Ч', 'Ch'),
            'Ш', 'Sh'),
          'Щ', 'Shch'),
        'Ъ', ''),
      'Ь', ''),
    'Э', 'E'),
  'Ю', 'Yu'),
'Я', 'Ya');
$BODY$
  LANGUAGE sql IMMUTABLE
  COST 100;

UPDATE osm_admin            SET name=ru_translit(name);
UPDATE osm_aeroways         SET name=ru_translit(name);
UPDATE osm_amenities        SET name=ru_translit(name);
UPDATE osm_barrierpoints    SET name=ru_translit(name);
UPDATE osm_barrierways      SET name=ru_translit(name);
UPDATE osm_buildings        SET name=ru_translit(name);
UPDATE osm_landusages       SET name=ru_translit(name);
UPDATE osm_landusages_gen0  SET name=ru_translit(name);
UPDATE osm_landusages_gen1  SET name=ru_translit(name);
UPDATE osm_mainroads        SET name=ru_translit(name);
UPDATE osm_mainroads_gen0   SET name=ru_translit(name);
UPDATE osm_mainroads_gen1   SET name=ru_translit(name);
UPDATE osm_minorroads       SET name=ru_translit(name);
UPDATE osm_motorways        SET name=ru_translit(name);
UPDATE osm_motorways_gen0   SET name=ru_translit(name);
UPDATE osm_motorways_gen1   SET name=ru_translit(name);
UPDATE osm_places           SET name=ru_translit(name);
UPDATE osm_railways         SET name=ru_translit(name);
UPDATE osm_railways_gen0    SET name=ru_translit(name);
UPDATE osm_railways_gen1    SET name=ru_translit(name);
UPDATE osm_transport_points SET name=ru_translit(name);
UPDATE osm_waterareas       SET name=ru_translit(name);
UPDATE osm_waterareas_gen0  SET name=ru_translit(name);
UPDATE osm_waterareas_gen1  SET name=ru_translit(name);
UPDATE osm_waterways        SET name=ru_translit(name);

Если мы добавляли номера домов на карту — транслитерируем и их заодно:

UPDATE osm_buildings SET "addr:housenumber"=ru_translit("addr:housenumber");

После чего можно запускать отрисовку своих тайлов — кириллицы там уже не будет.

Карта без кириллицы

Больше букв

Функция selectall_arrayref перлового модуля DBI хороша для тех, кому лень писать:

This utility method combines «prepare», «execute» and «fetchall_arrayref» into a single call. It returns a reference to an array containing a reference to an array (or hash, see below) for each row of data fetched.

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

Реальный пример: скрипт, извлекающий тайлы из пакета, созданного Тайлмиллом, пытался читать данные как раз функцией selectall_arrayref. Зная, что применяется запрос

SELECT * FROM tiles

и что представление tiles содержит, помимо прочего, содержимое тайлов, занимающее места больше всего остального, нетрудно догадаться, что попытка выполнения запроса потребует выделения памяти в объёме, сопоставимом с размером файла, в котором сидит база (пакет с тайлами — это база SQLite).

Набор тайлов для территории размером 600×400 км в средних широтах — например, с Челябинском по центру, Ашой на западе, Карталами на юге и Тюменью на северо-востоке — займёт больше гигабайта для набора масштабов не больше шестнадцатого. На практике так и получилось: скрипт отжирал больше гигабайта памяти и всё никак не мог приступить к полезной части, пытаясь отожрать ещё. Если же увеличивать масштаб, затраты вырастут ещё сильнее: добавим семнадцатый зум масштаб — понадобятся ещё три-четыре гигабайта, Добавим восемнадцатый, которого хватит даже для любопытных исследователей карт — ещё на десять-двадцать объём вырастет. Если будем сохранять тайлы с глубиной цвета 24 бита, а не восемь — ещё больше места израсходуем. Получается, что средних размеров российская область может занять своими тайлами десятки гигабайт. И скрипт бы безуспешно пытался эти десятки получить.

Переписал:

-my $tiles = $dbh-&gt;selectall_arrayref(
-    'SELECT * FROM tiles',
-    { Slice => {} }
-);
-
-foreach my $tile ( @$tiles ) {

+my $sth = $dbh->prepare('SELECT * FROM tiles');
+   $sth->execute;
+
+while ( my $tile = $sth->fetchrow_hashref ) {

и всё наладилось: скрипт перестал жрать память (ему хватило десяти мегабайт) и ждать её выделения — сразу работает.

Вывод: не всегда надо экономить рабочее время программиста — иногда надо и о машинном времени задумываться.

Интерактивная карта Ильменки

На сайте Ильменского фестиваля теперь используется правильная карта — её можно двигать и масштабировать, включать отображение нужных маркеров и кликать по ним, чтоб получить подсказку.

Карта

Карта — OpenStreetMap, стиль отображения — свой собственный на основе OSM Bright, отрисованный с помощью TileMill, иконки — The Map Icons Collection. Библиотека для отображения — Leaflet c плагинами Leaflet-hash и Leaflet.fullscreen.

Схема с ёлками, если кому-то всё-таки нужна, спрятана под ссылкой, чуть ниже карты. Кроме того, при клике на кнопку с ромбами можно включить наложение этой схемы на карту.