Обработка и фильтрация osm-файла (osm.pbf) занимает слишком много времени в C# - PullRequest
2 голосов
/ 10 января 2020

Сценарий: Я хочу написать свой собственный Autocomplete-API для адресов, как и тот, который предлагает Google. (Very Basi c: улица, номер дома, город, почтовый индекс, страна). Он предназначен только для личного использования и в учебных целях. Я хочу покрыть около 1 миллиона адресов для начала.

Используемая технология: . Net Framework (не Core), C#, Visual Studio, OSMSharp, Microsoft SQL -Server, Web Api 2 (хотя я, вероятно, переключусь на ASP. Net Core в будущем.)

Подход:

  • Set Up Project (Web Api 2 или консольный проект для демонстрационных целей)
  • Загрузить соответствующий файл из OpenStreetMaps с помощью DownloadClient () (https://download.geofabrik.de/)
  • Читать в файле использование OSMSharp и фильтрация соответствующих данных.
  • Преобразование отфильтрованных данных в таблицу данных.
  • Использование DataTable для подачи метода SQLBulkCopy для импорта данных в базу данных.

Проблема: Шаг 4 занимает слишком много времени. Для файла типа "Regierungsbezirk Köln" в формате osm.pbf, который составляет около 160 МБ (несжатый файл osm составляет около 2,8 ГБ), где речь идет о 4-5 часах. Я хочу оптимизировать это. С другой стороны, массовое копирование DataTable в базу данных (около 1 миллиона строк) занимает всего около 5 секунд. (Вау. Удивительно.)

Минимальное воспроизведение: https://github.com/Cr3pit0/OSM2Database-Minimal-Reproduction

Что я пробовал:

  • Использовать хранимую процедуру в SQL -Server. Это связано с совершенно другим набором проблем, и мне не удалось заставить его работать (в основном потому, что размер несжатого файла osm.pbf превышает 2 ГБ, а SQL серверу это не нравится)

  • Придумайте другой подход к фильтрации и преобразованию данных из файла в таблицу данных (или CSV).

  • Используйте Overpass-API. Хотя я где-то читал, что Overpass-API не предназначен для DataSets выше 10000 записей.

  • Обратитесь за помощью к гроссмейстерам-джедаям в StackOverflow. (В настоящее время в процессе ...: D)

Извлечение кода:

public static DataTable getDataTable_fromOSMFile(string FileDownloadPath)
{

    Console.WriteLine("Finished Downloading. Reading File into Stream...");

    using (var fileStream = new FileInfo(FileDownloadPath).OpenRead())
    {
        PBFOsmStreamSource source = new PBFOsmStreamSource(fileStream);

        if (source.Any() == false)
        {
            return new DataTable();
        }

        Console.WriteLine("Finished Reading File into Stream. Filtering and Formatting RawData to Addresses...");
        Console.WriteLine();

        DataTable dataTable = convertAdressList_toDataTable(
                    source.Where(x => x.Type == OsmGeoType.Way && x.Tags.Count > 0 && x.Tags.ContainsKey("addr:street"))
                    .Select(Address.fromOSMGeo)
                    .Distinct(new AddressComparer())
                );

        return dataTable;
    }
};
private static DataTable convertAdressList_toDataTable(IEnumerable<Address> addresses)
{
    DataTable dataTable = new DataTable();

    if (addresses.Any() == false)
    {
        return dataTable;
    }

    dataTable.Columns.Add("Id");
    dataTable.Columns.Add("Street");
    dataTable.Columns.Add("Housenumber");
    dataTable.Columns.Add("City");
    dataTable.Columns.Add("Postcode");
    dataTable.Columns.Add("Country");

    Int32 counter = 0;

    Console.WriteLine("Finished Filtering and Formatting. Writing Addresses From Stream to a DataTable Class for the Database-SQLBulkCopy-Process ");

    foreach (Address address in addresses)
    {
        dataTable.Rows.Add(counter + 1, address.Street, address.Housenumber, address.City, address.Postcode, address.Country);
        counter++;

        if (counter % 10000 == 0 && counter != 0)
        {
            Console.WriteLine("Wrote " + counter + " Rows From Stream to DataTable.");
        }
    }

    return dataTable;
};

1 Ответ

1 голос
/ 14 января 2020

Хорошо, я думаю, что понял. У меня осталось около 12 минут для файла размером около 600 МБ и около 3,1 млн. Строк данных после фильтрации.

Первое, что я попробовал, - это заменить логи c, которые заполняют мою таблицу данных, на FastMember. Это сработало, но не дало повышения производительности, на которое я надеялся (я отменил процесс через 3 часа ...). После дополнительных исследований я наткнулся на старый проект, который называется «osm2ms sql» (https://archive.codeplex.com/?p=osm2mssql). Я использовал небольшую часть кода, которая непосредственно считывает данные из файла osm.pbf и модифицирует его в моем сценарии использования (→ который предназначен для извлечения адресных данных из путей). Я действительно использовал FastMember для записи IEnumerable<Address> в Datatable, но мне не нужен OSM-Sharp и любые дополнительные зависимости, которые у них есть. Так что большое спасибо за предложение FastMember. Я, безусловно, буду держать эту библиотеку в уме в будущих проектах.

Для тех, кто заинтересован, я соответствующим образом обновил свой Github-проект (https://github.com/Cr3pit0/OSM2Database-Minimal-Reproduction) (хотя я не проверил его полностью, потому что я перешел от Test-Project к Real Deal, то есть к веб-интерфейсу)

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

Так что большое спасибо всем, кто написал "osm2ms sql".

...