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->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 ) {

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

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

Канал пройден

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

Схема выхода кабеля из канала в потолке

Теперь можно штукатурить открытые каналы. Следующую часть, по большой комнате, в канал, наверное, пихать не буду — там будет натяжной потолок, и кабель можно пустить над ним. Или всё-таки попробовать? :-)

Re: Не лезет

Кабели, особенно самый толстый, упорно не желают лезть в канал. Картина пока что такая:

Кабели в потолке

Половина маршрута пройдена всеми тремя кабелями (рыжий — 3×6 мм² для плиты, бордовый — 3×2,5 на розетки и жёлтый — осветительный 2×1,5), а вот вторая пройдена только толстым кабелем. Стальная проволока, за которую я его тянул, всё-таки порвалась; хоть кончик кабеля всё же удалось достать из канала, дальше вылезать он не желает — слишком жёсткий да и застрял где-то внутри. Розеточный кабель во вторую половину канала ещё не заходил, а осветительному туда не надо — он уже дошёл до своей лампочки.

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

Не лезет

Пытаюсь проложить пучок свежих медных кабелей вместо старых алюминиевых — не выходит. По отдельности некоторые из них (гибкий 2×0,75 и жёсткий 3×2,5) даже удалось завести, но вслед за ними толстый жёсткий 3×6 не лезет. Вынул уже проложенную медь, соединил всё в общий пучок — всё равно не лезет. С одной стороны толкаю кабель в канал, с другой — тяну за стальную проволоку — не получается. Проволока рвётся, а кабель всё равно не лезет.

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

Схема канала в потолке

Диаметр канала — сантиметра полтора-два. Раньше в этом канале лежала пара хилых алюминиевых двухпроводных кабелей и три толстенных алюминиевых провода, каждый диаметром в полсантиметра (то есть, сечением не меньше 10 мм²). Медные провода — меньшего сечения (хотя и с более толстой изоляцией), значит, должны влезть. А нифига! Ситуацию осложняет малая гибкость толстых кабелей — похоже, кабели, зайдя в канал кривыми, упираются в изгибами в его стенки.

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

А другие варианты есть?

Дайте, пожалуйста, таблеток от жадности. Да побольше!

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

Розетка

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

Осталось ещё засунуть в потолок метров семь кабеля средней толщины (3×2,5) и десять — самого толстого, для электроплиты. Ну и выкинуть оттуда алюминиевые провода. После этого можно будет переходить к отделочным работам. Ремонт движется!

Земля!

Кусачки

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

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

Прозвонил кабель в квартире, аккуратно смотрел в подъездный щит (потому что один хрен — измерение сопротивления не помогло) и выяснил:

  • Защитный нулевой проводник PE есть не только для плиты — он есть и для нормальных потребителей: один из входящих в квартиру кабелей — трёхжильный и центральная его жила — PE. И лежит этот кабель с самого момента сдачи дома.
  • Трёхжильный кабель доходит до кухни, но третья его жила бездарно не использовалась. Почти. Во всяком случае, штатные розетки были двухпроводными.
  • Временный кабель в обход сгоревшего нуля, висящий, наверное, больше четверти века, был почему-то прицеплен электриком не на рабочий ноль N, а на защитный PE. Зачем?

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

И наконец построили

Площадь Науки

Вызвавшая неоднозначную реакцию народа стройка полуподземного комплекса перед памятником Курчатову, кажется, завершается. Будут не «кафешки всякие и никакого алкоголя», как мне говорил южанин-бригадир — будет продуктовый магазин, откроется в начале августа. Хотя, насколько я понимаю, выпивку там продавать всё равно нельзя — вроде, ближе ста метров от образовательных учреждений не положено. С другой стороны, в «Радуге» на Российской, в двух шагах (а точнее, в тридцати метрах) от школы продают и бухло, и курево — и ничего. Может, нормы поменялись?

Ну и вместо года стройка тянулась два. Ну и подземной автостоянки там нет.