«1. Обзор

В этой статье мы рассмотрим основы библиотеки Java с открытым исходным кодом GeoTools для работы с геопространственными данными. Эта библиотека предоставляет совместимые методы для реализации географических информационных систем (ГИС), а также реализует и поддерживает многие стандарты Open Geospatial Consortium (OGC).

Поскольку OGC разрабатывает новые стандарты, они реализуются с помощью GeoTools, что делает его весьма удобным для работы с геопространственными данными.

2. Зависимости

Нам нужно добавить зависимости GeoTools в наш файл pom.xml. Поскольку эти зависимости не размещены в Maven Central, нам также необходимо объявить их репозитории, чтобы Maven мог их загрузить:

<repositories>
    <repository>
        <id>osgeo</id>
        <name>Open Source Geospatial Foundation Repository</name>
        <url>http://download.osgeo.org/webdav/geotools/</url>
    </repository>
    <repository>
        <id>opengeo</id>
        <name>OpenGeo Maven Repository</name>
        <url>http://repo.opengeo.org</url>
    </repository>
</repositories>

После этого мы можем добавить наши зависимости:

<dependency>
    <groupId>org.geotools</groupId>
    <artifactId>gt-shapefile</artifactId>
    <version>15.2</version>
</dependency>
<dependency>
    <groupId>org.geotools</groupId>
    <artifactId>gt-epsg-hsql</artifactId>
    <version>15.2</version>
</dependency>

3. ГИС и шейп-файлы

Для практического применения библиотеки GeoTools нам необходимо знать некоторые сведения о географических информационных системах и шейп-файлах.

3.1. ГИС

Если мы хотим работать с географическими данными, нам понадобится географическая информационная система (ГИС). Эта система может использоваться для представления, сбора, хранения, обработки, анализа или управления географическими данными.

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

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

3.2. Шейп-файлы

Для работы с геопространственными данными доступны различные форматы. Растр и вектор — два основных типа данных.

В этой статье мы рассмотрим, как работать с векторным типом данных. Этот тип данных может быть представлен точками, линиями или полигонами.

Для хранения векторных данных в файле мы будем использовать шейп-файл. Этот формат файла используется при работе с геопространственным векторным типом данных. Кроме того, он совместим с широким спектром программного обеспечения ГИС.

Мы можем использовать GeoTools для добавления таких объектов, как города, школы и ориентиры, в шейп-файлы.

4. Создание объектов

В документации GeoTools указано, что объект — это все, что можно нарисовать на карте, например, город или ориентир. И, как мы уже упоминали, после создания функции могут быть сохранены в файлы, называемые шейп-файлами.

4.1. Хранение геопространственных данных

Перед созданием объекта нам необходимо знать его геопространственные данные или координаты долготы и широты его местоположения на Земле. Что касается данных атрибутов, нам нужно знать имя объекта, который мы хотим создать.

Эту информацию можно найти в Интернете. Некоторые сайты, такие как simplemaps.com или maxmind.com, предлагают бесплатные базы данных с геопространственными данными.

Когда мы знаем долготу и широту города, мы можем легко сохранить их в каком-нибудь объекте. Мы можем использовать объект Map, который будет содержать название города и список его координат.

Давайте создадим вспомогательный метод для облегчения хранения данных внутри нашего объекта Map:

private static void addToLocationMap(
  String name,
  double lat,
  double lng,
  Map<String, List<Double>> locations) {
    List<Double> coordinates = new ArrayList<>();

    coordinates.add(lat);
    coordinates.add(lng);
    locations.put(name, coordinates);
}

Теперь давайте заполним наш объект Map:

Map<String, List<Double>> locations = new HashMap<>();

addToLocationMap("Bangkok", 13.752222, 100.493889, locations);
addToLocationMap("New York", 53.083333, -0.15, locations);
addToLocationMap("Cape Town", -33.925278, 18.423889, locations);
addToLocationMap("Sydney", -33.859972, 151.211111, locations);
addToLocationMap("Ottawa", 45.420833, -75.69, locations);
addToLocationMap("Cairo", 30.07708, 31.285909, locations);

Если мы загрузим некоторую базу данных CSV, содержащую эти данные, мы можем легко создать средство чтения для извлечения данных вместо того, чтобы хранить их в объекте, как здесь.

4.2. Определение типов объектов

Итак, теперь у нас есть карта городов. Чтобы иметь возможность создавать объекты с этими данными, нам нужно сначала определить их тип. GeoTools предлагает два способа определения типов пространственных объектов.

Один из способов — использовать метод createType класса DataUtilites:

SimpleFeatureType TYPE = DataUtilities.createType(
  "Location", "location:Point:srid=4326," + "name:String");

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

SimpleFeatureTypeBuilder builder = new SimpleFeatureTypeBuilder();
builder.setName("Location");
builder.setCRS(DefaultGeographicCRS.WGS84);

builder
  .add("Location", Point.class);
  .length(15)
  .add("Name", String.class);

SimpleFeatureType CITY = builder.buildFeatureType();

Оба типа хранят одинаковую информацию. Расположение города сохраняется как точка, а название города сохраняется как строка.

«Вы, наверное, заметили, что имена переменных типа TYPE и CITY пишутся с большой буквы, как и константы. Переменные типа должны рассматриваться как окончательные переменные и не должны изменяться после их создания, поэтому этот способ именования может использоваться только для обозначения этого.

4.3. Создание функций и наборы функций

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

Давайте создадим экземпляр SimpleFeatureBuilder, предоставляющий наш тип объекта:

SimpleFeatureBuilder featureBuilder = new SimpleFeatureBuilder(CITY);

Нам также понадобится коллекция для хранения всех созданных объектов объектов:

DefaultFeatureCollection collection = new DefaultFeatureCollection();

Так как мы объявили в нашем типе объекта, что он содержит точку для местоположения нам нужно будет создать точки для наших городов на основе их координат. Мы можем сделать это с помощью JTSGeometryFactoryFinder из GeoTools:

GeometryFactory geometryFactory
  = JTSFactoryFinder.getGeometryFactory(null);

Обратите внимание, что мы также можем использовать другие классы Geometry, такие как Line и Polygon.

Мы можем создать функцию, которая поможет нам поместить функции в коллекцию:

private static Function<Map.Entry<String, List<Double>>, SimpleFeature>
  toFeature(SimpleFeatureType CITY, GeometryFactory geometryFactory) {
    return location -> {
        Point point = geometryFactory.createPoint(
           new Coordinate(location.getValue()
             .get(0), location.getValue().get(1)));

        SimpleFeatureBuilder featureBuilder
          = new SimpleFeatureBuilder(CITY);
        featureBuilder.add(point);
        featureBuilder.add(location.getKey());
        return featureBuilder.buildFeature(null);
    };
}

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

locations.entrySet().stream()
  .map(toFeature(CITY, geometryFactory))
  .forEach(collection::add);

Коллекция теперь содержит все объекты, созданные на основе нашего объекта Map, содержащего геопространственные данные.

5. Создание хранилища данных

GeoTools содержит API хранилища данных, который используется для представления источника геопространственных данных. Этот источник может быть файлом, базой данных или какой-либо службой, возвращающей данные. Мы можем использовать DataStoreFactory для создания нашего хранилища данных, которое будет содержать наши функции.

Давайте установим файл, который будет содержать функции:

File shapeFile = new File(
  new File(".").getAbsolutePath() + "shapefile.shp");

Теперь давайте установим параметры, которые мы собираемся использовать, чтобы сообщить DataStoreFactory, какой файл использовать, и указать, что нам нужно сохранять пространственный индекс, когда мы создаем наш DataStore:

Map<String, Serializable> params = new HashMap<>();
params.put("url", shapeFile.toURI().toURL());
params.put("create spatial index", Boolean.TRUE);

Давайте создадим DataStoreFactory, используя только что созданные параметры, и используем эту фабрику для создания DataStore:

ShapefileDataStoreFactory dataStoreFactory
  = new ShapefileDataStoreFactory();

ShapefileDataStore dataStore 
  = (ShapefileDataStore) dataStoreFactory.createNewDataStore(params);
dataStore.createSchema(CITY);

6. Запись в шейп-файл

Последний шаг, который нам нужно сделать, это записать наши данные в шейп-файл. Чтобы сделать это безопасно, мы будем использовать интерфейс Transaction, который является частью API GeoTools.

Этот интерфейс дает нам возможность легко зафиксировать изменения в файле. Он также предоставляет способ выполнить откат неудачных изменений, если при записи в файл возникает какая-либо проблема:

Transaction transaction = new DefaultTransaction("create");

String typeName = dataStore.getTypeNames()[0];
SimpleFeatureSource featureSource
  = dataStore.getFeatureSource(typeName);

if (featureSource instanceof SimpleFeatureStore) {
    SimpleFeatureStore featureStore
      = (SimpleFeatureStore) featureSource;

    featureStore.setTransaction(transaction);
    try {
        featureStore.addFeatures(collection);
        transaction.commit();

    } catch (Exception problem) {
        transaction.rollback();
    } finally {
        transaction.close();
    }
}

SimpleFeatureSource используется для чтения функций, а SimpleFeatureStore используется для доступа для чтения/записи. В документации GeoTools указано, что использование метода instanceof для проверки возможности записи в файл является правильным способом сделать это.

Этот шейп-файл впоследствии можно будет открыть с помощью любой программы просмотра ГИС, поддерживающей шейп-файлы.

7. Заключение

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

Несмотря на то, что пример был простым, его можно расширить и использовать для создания богатых шейп-файлов для различных целей.

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

Вы можете найти полный пример кода, использованный в этой статье, в нашем проекте GitHub. Это проект Maven, поэтому вы должны иметь возможность импортировать его и запускать как есть.