javascript, webgl, maps, databases
0,2
рейтинг
19 марта 2015 в 13:26

Разработка → Районы… Кварталы…

Совсем недавно на хабре была статья от AirBnb — «Создавая карту мира». Хорошая и красивая статья про административное деление мира. Один минус — у статьи один комментарий, и то мой.
Пользуясь случаем проведу опрос — хотели ли бы вы такую карту административных делений?
А то она у меня есть:



Вы наверное замечали, что Google.Карты умеют подсвечивать контура городов. С недавнего времени такое есть и на Яндекс.Картах. Мало кто знает, что геометрия есть и на eSosedi.

А вот когда такая возможность появится на вашем сайте — теперь зависит только от тебя %username%.

Для достижения эффекта достаточно зайти на data.esosedi.org или GitHub, ознакомиться с документацией библиотеки osmeRegions и начать использовать.

P.S.: 3 признака того, что год минувший все сделал красиво: 1. Районы 2. Кварталы. 3. Детализация до «Жилые массивы» доступна для некоторых городов.

OSMeRegions — это и библиотека и сервис, который позволяет отображать данные административного деления на основе OpenStreetMap на карте. Старшый брат модуля регионов Яндекс.Карт.

Изначальная задача заключалась в попытке улучшить адресацию гео-привязанных обьектов на esosedi. Потому что работа у нас такая. Как говорилось на спарке:
«Представим, что в вашем городе есть Улица Ленина. Так вот — в соседнем городе она тоже есть».

Все началось с стандарта ISO3166-2, который определяет буквенную индексацию стран и регионов. Москва, для примера — RU-MOW. Лично мне никогда не нравилось адресация обьектов на «гео» сайтах — в урле нет никакого «ЧПУ подобного» указания локации. Все ссылки одинаковые и не понятно про что идет речь. Одновременно были проблемы с геокодером — с определением административного адреса.
В общем это была присказка, впереди было обьединение данных OSM с GeoNames, парсинг Wikipedia, реализация прямых и обратный геокодеров, нахождение смерти Кащеевой, и написание пачки «детранслитеризаторов» чтобы получить читабельные нашим человеком названия регионов.


(не умею я такие гифки делать, картинка из топика AirBnb)

Что вы хотите — раскрашивать страны по некой схеме, или делать из Новой Москвы Старую — ваше дело.

Команда только одна:

osmeRegions.geoJSON(addr, options, callback)


Где addr — имя региона мира(или просто мира), код страны или региона в формате 3166-2, или OpenStreetMap RelationId. А остальное — схемы, фильтры, рекомбинации — по желанию.
Hint: relationId можно получить зайдя на нужное место на esosedi — вся информация так указана. Либо воспользоваться «навигатором» на data.esosedi.org — он для этого и создан.
Это не какой-то там topojson, это АПИ к «ручке» которая готова отдавать вам данные по паре сотен тысяч административных элементов. При этом из ручки выходит GeoJSON, которым можно кормить тот же d3. В общем это решение.

Мир
Большая Москва
Старая Москва (рекомбинация)
Новая Москва
Крым Наш (рекомбинация)
Крым Ваш

Рекомбинация — это когда уже на клиенте вы сами определяете как собрать регионы.

P.S.: Сервис может содержать, содержит и будет содержать ошибки.
P.P.S.: Ручка отдачи данных будет работать «вечно» на свой страх и риск. Когда начинала писаться эта статья я хотел дополнить эту фразу «покуда евро по 70 не будет». Как в воду глядел. Но ни что не мешает сохранять json файлы данных себе.
P.P.P.S.: Лицензия простая, но не одна — esosedi(CC BY-SA), OSM(CC-BY-SA+ODbL), Wikipedia(CC BY-SA), GeoNames(CC BY). Отдельное спасибо всеми забытому geo.webnabor.com.
Образно говоря:
esosedi, « Участники OpenStreetMap», Wikipedia, GeoNames(Хотя они и не требуют)


Эта история началась 7 лет назад, в 2008 году, в глубинах Wikimapia. Это топик писался два раза с промежутком в год, и в итоге написан на полтора года позже первых сроков. Было много интерлюдий, комедий и трагедий, и сейчас время антракта. Кушать подано — github.com/esosedi/regions
Корзунов Антон @kashey
карма
31,0
рейтинг 0,2
javascript, webgl, maps, databases
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (22)

  • +1
    Сейчас начнётся про Крым :(
  • 0
    Написано очень эмоционально, но немного непонятно.

    Я могу воспользоваться этим сервисом и нарисовать границы районов Парижа, Франция на Google Maps?
    Или отобразить границы neighborhoods в San Francisco?

    Спасибо.
    • 0
      Париж? Это который №71525? Берем и показываем — jsfiddle.net/9o9ak7fb/24/
      Перевод в коллекции G или Y карт, и даже приведение в некий общий интерфейс тут есть.
      osmeRegions.geoJSON(71525/*Париж*/, {
                  lang: 'ru', 
                  quality:0,
              }, function (data, pure) {
                  if(mtype=='Y'){
                      collection = osmeRegions.toYandex(data, ymaps);
                      collection.add(geoMap);    
                      geoMap.setBounds(collection.collection.getBounds(), {duration: 300});
                  }else{
                      collection = osmeRegions.toGoogle(data);
                      collection.add(geoMap);    
                      Gzoom(geoMap,collection.collection);
                  }
                  
          });
      


      Соседи US-CA? Настраиваем схему и показываем — jsfiddle.net/9o9ak7fb/25/
      osmeRegions.geoJSON('US-CA', {
                  lang: 'en', 
                  quality:0,
                  scheme: {
                      165475:function (region){
      // Для 165475(калифорния) выборка всех соседей СанФранциско.
                      return region.hasBorderWith(396487) && region.osmId!=396487;
                  }
                  },
                  postFilter: function (region){
      // Оставляем только Калифорнию
                      return region.osmId==165475;
                  }
              }
      
      • +1
        Спасибо.

        Еще вопрос — обязательно ли подключать Yandex Maps API? Он подключен везде в примерах на jsfiddle.

        Под San Francisco neighborhoods я подразумевал примерно такую карту (районы города):

        San Francisco neighborhoods

        Без больших названий районов, разумеется.
        Карта отсюда.
        • 0
          Яндекс Карты не обязательны, вот районов нужных нет, как и всех внутренностей Египта, половины Китая и тд. Ничто не идеально :(
        • 0
          В OSM отмечены данные районы как точки, а в Wiki прописано, что данные единицы не имеют четких границ и рекомендуется отмечать просто точкой. Потому думаю в большинстве случаев не будет их ни у кого, если только кто-то не нанесет их сам.
          Границы есть только для отношений с тэгом boundary=administrative + admin_level=*
          • 0
            В моем случае есть еще два хитрый момента:
            1. В данные попали не всегда верные файлы — например церковные приходы
            2. В экспорт попали только те данные, которые смогли замкнуться в контур. Очень многие relations, к сожалению, не смогли.
            3. За время подготовки данных многие relations изменились или были вообще стерты.
            Сейчас есть заново обработанные данные, чуть более корректные. Но пока не в проде.
            По районам что LA, что Подольска или Мытищ ситуация одинаковая — они вроде как есть, но не совсем публичные. При этом всегда можно взять геометрию улиц, и собрать из них районы, кварталы, зоны обслуживания почтовых отделений…
            • 0
              При этом всегда можно взять геометрию улиц, и собрать из них районы, кварталы, зоны обслуживания почтовых отделений…

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

              У нас есть похожаю выгрузка, в ней получилось 254505 объектов административных границ. Если верить taginfo, всего отношений-границ 286 987.

              Сейчас есть заново обработанные данные, чуть более корректные.

              Чем обрабатываете и на чем?
              • 0
                У меня в базе 195к обьектов которые смогли собраться хотя бы в один контур. Из них 20 тысяч ушли в «дубликаты» — в OSM 3 или 4 Парижа вложенных друг в друга, три Амстердама и тд…
                Обрабатываю самописными ручками. В начале экспорт из planet файла, потом две стадии предобработки ways, потом сборка, потом связка с родителями, потом мержинг с внешними БД (geoNames, booking итд).
                Потом понимаешь, что в NY у тебя находится в Бруклине, и таблице регионов у него admin_level=8, и совершенно не понятно почему, когда, кто виноват....
                • 0
                  Посмотрел Париж, там не все так однозначно, да есть два Парижа, но они являются разными административными единицами в составе Франции, ИМХО не совсем корректно так сливать их.
                  Еще возник вопрос на примере NY, согласно вашей схеме (визуализации) у вас слились воедино NY город и NY штат, возможно для вашей задачи это не критично, но все же вводит в заблуждение.
                  • 0
                    NY и NewYorkCity — две большие разницы.
                    Один — OpenStreetMap 175905, GeoNames 7163824, Wikipedia ru: Нью-Йорк
                    Второй — OpenStreetMap 61320, GeoNames 5128638, Wikipedia ru: Нью-Йорк (штат)
                    Да — называются одинаково, но все так и есть. И тот, который имеет ISO3166-2 код US-NY — штат.

                    С Парижем все хитрее — не стоит забывать, что базу в общем случае делал для себя. И для «пользователей».
                    Если человек три раза тыкает мышкой и не может попасть «ниже» — это не совсем правильно.
                    В итоге — в экспорте «поID» есть все регионы, и у всех есть полный перечень их «родителей» (ну почти*). Но вниз спускается хитро:
                    Для «стран» — спуск до прямых детей, и до регионов второго уровня. Так в России есть и ФО(дети) и Регионы(ISO). Тоже самое с Крымом — он вообще геометрически в контуре Украины. Но по ISO — в России.
                    В «Парижах» примерно тоже самое — под детьми подразумеваются те, чей родитель вы, или ваш «клон».

                    (ну почти*) вот в Париже поле parents у райнов или пустое, или кривое, сейчас поправим :(
  • 0
    О, поскольку вы в теме, хочу спросить, нет или в Яндексе, Гугле, или еще где либо возможности подсветить административные единицы уровня района или области? Не так, чтобы я вручную создал пару сотен или тысяч полигонов, а автоматического деления и команд вроде highlightRegion('Crimea')? Мне нужно вывести статистику по районам, то есть подсветить его, и напечатать поверх некоторые цифры.
    • 0
      Вы не поверите — именно для этого данная чтука и создана. В каждый файл входит как контур запрашиваемого обьекта, так и контура его подразбиений. Плюс вся нужная информация — кто кому родитель, ссылки на википедию и тд и тп.
      Надо просто знать где этот крым — 72639 или 'RU-CR'. По этим именам и он доступен.
  • 0
    А саму базу опубликовать не собираетесь?
    Раз уж ваши данные основаны на OSM, то как я понимаю,
    в соответствии с лицензией ODbL www.openstreetmap.org/copyright
    пункт 4.6 Access to Derivative Databases вы обязаны выложить ее в открытый доступ.
    • 0
      На гитхабе лежит github.com/theKashey/osme, который может собрать «такую» базу данных. В принципе этого достаточно для соблюдения условий дериватива «как базы данных». Но данный сервис никаким боком не БД, а обычный Produced Work, те на него распространяется пункт 4.3
      А вообще если OSM был бы не таким манерным и сам бы замержился с тем же GeoNames — мир стал бы лучше. Но нет, нельзя.....
  • +1
    Долго пытался это к LeafLet применить, в итоге вот простой пример:

    osmeRegions.geoJSON('ru', {
      lang: 'ru', 
      quality:2,
      type: 'coast'
      }, function (data, pure) {
          var geojson = new L.geoJson(data).addTo(map);
    });
    
    • 0
      Думаю стоит добавить его в примеры. Эх, еще бы кто интерактивность для Леафлета докрутил…
      • 0
        Пока вы тут и быстро реагируете, задам вопрос.
        Как использовать osmeRegions на локальном хосте, без интернета?
        С учётом того, что ответ с data.esosedi.org закеширован.
        Не хочется лезть в чужой код, но вижу что производятся какие-то преобразования ответа, в чистом виде он совсем не GeoJSON.
        • 0
          Формат там достаточно упоротый, и по сути нужен только для передачи данных.
          Вариантов использования на локалхосте парочка:
          1. Сохранить себе geoJSON. В принципе из .geoJSON он выходит в чистом виде.
          2. Сохранить себе «оригинальные данные», парсить в geoJSON через osmeRegions.parseData.
          3. Изменить хост (.setHost) или функцию .loadData так, чтобы ходить в свою проксирующую «ручку».
    • 0
      И тут же, возникла проблема с 180м меридианом, которая решается патчем к regions.js:

      Патч
      # This patch file was generated by NetBeans IDE
      # It uses platform neutral UTF-8 encoding and \n newlines.
      --- a/<html>regions.js (<b>12.10.2015 14:40:57</b>)</html>
      +++ b/<html><b>Текущий файл</b></html>
      @@ -93,6 +93,10 @@
      
                   while (index < byteVectorLength) {
                       var position = [clampy(read() * fx + bounds[0][0]), clampx(read() * fy + bounds[0][1])];
      +                if (position[1]<0) {
      +                    position[1] = 360+position[1];
      +                }
                       result.push([position[1], position[0]]);
                   }
                   return result;

      • 0
        Это не правильное решение, так как вводятся "unbounded" координаты, те что-то запределами -180:180. Яндекс.Карты, например их не понимают. Google Maps вообще тоже — такой формат координат в принципе недопустим в geojson.
        Суть в отсутствие алгоритма shortestPath в Лефлете, и это проблема Леафлета. В том числе он тут не совсем корректно воспроизведен, в том числе потому что должен работать уже после проекции.

        Имеет смысл произвести специальную обработку координат перед тем как отдать их в L, раз ему нужно. Но не будет правильным решением впиливать это в основной блок декодирования.
        • 0
          "Правильный" shortestPath

          function getShortestPath (contour) {
              var halfWorld = 180;
              var result = [contour[0]], point = contour[0];
              for (var i = 1, l = contour.length; i < l; ++i) {
                  var delta = point[1] - contour[i][1];
                  if (Math.abs(delta) > halfWorld) {
                      delta = delta < 0 ? -360:360;
                  } else {
                      delta = 0;
                  }
          
                  var nextPoint = [contour[i][0], contour[i][1] + delta]
                  result.push(nextPoint);
                  point = nextPoint;
              }
              return result;
          }

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.