Статьи

OpenMap Tutorial — Часть 1

Вступление

Эта серия учебных пособий покажет вам, как создавать приложения Java с использованием библиотеки OpenMap GIS Java Swing.

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

OpenMap написан на Swing. На момент написания этой статьи последняя версия 5.1.12. Вы можете скачать как исходный код, так и исполняемые файлы из GitHub . После того, как вы скопируете / распакуете / клонируете его в каталог, вы можете выполнить его, запустив соответствующий скрипт для вашей платформы ( openmap.bat или openmap ) или дважды щелкнув по lib/openmap.jar . Вы должны увидеть полное ГИС-приложение, подобное изображенному на рисунке 1. Мы попытаемся создать подобное приложение к концу этой серии. Исходный код OpenMap также содержит некоторые примеры использования OpenMap. В этом уроке мы будем опираться на com.bbn.openmap.app.example.SimpleMap . Во втором уроке мы будем использовать код из com.bbn.openmap.app.example.SimpleMap2 . Последующие уроки будут основаны на других примерах.

В этой серии руководств мы будем использовать новейшую среду IDE NetBeans 8.1 для создания наших приложений.

Урок 1. Создание базового приложения для карт.

Создать приложение JFrame

В этом первом уроке мы JFrame базовое приложение JFrame , содержащее карту (см. Рисунок 2). Откройте NetBeans и создайте новое приложение Java, выполнив следующие действия:

  1. Откройте меню File → New Project и выберите Category : Java и Project : Java Application (рисунок 3). Нажмите Далее .
  2. На следующем шаге укажите имя и местоположение. Убедитесь, что вы используете выделенную папку для библиотек и не выбираете основной класс (рисунок 4). Нажмите на Готово .
  3. Как только ваш новый проект будет создан, создайте новый пакет с именем openmap , щелкнув правой кнопкой мыши на Source Packages и выбрав New → Java Package из всплывающего меню.
  4. Щелкните правой кнопкой мыши папку « Библиотеки » и выберите действие « Добавить JAR / папку» во всплывающем меню. Перейдите в папку lib вашей установки OpenMap и выберите openmap.jar . Вы можете использовать относительный путь или лучше скопировать его в папку « Библиотеки » (рисунок 5). Нажмите Открыть, чтобы закрыть диалоговое окно!
    Рисунок 1: Окно ГИС-приложения OpenMap

    Рисунок 1: Окно ГИС-приложения OpenMap

  5. Вам также необходимо скопировать файлы карты. Наиболее распространенным форматом является .shp (ESRI Shape). Создайте новую папку resources/map , выбрав окно « Файлы» в NetBeans, щелкнув правой кнопкой OpenMap1 проект OpenMap1 и выбрав « Создать» → «Папка» во всплывающем меню. Введите имя resources и нажмите ОК . Щелкните правой кнопкой мыши папку resources и повторите процедуру, чтобы создать в ней папку карты. Скопируйте папку share/data/shape из вашей установки OpenMap в папку map
  6. Создайте новую форму openmap щелкнув правой кнопкой openmap пакет openmap и выбрав New → JFrame Form из всплывающего меню. Дайте ему имя, например, MapFrame и нажмите « Готово» .
    Базовое приложение OpenMap Swing

    Базовое приложение OpenMap Swing

  7. Нажмите кнопку Source , чтобы увидеть сгенерированный код (см. Листинг 1).
  8. Добавьте строку super("Simple Map"); в конструкторе, чтобы установить заголовок окна.
  9. Конструктор инициализирует JFrame . Пока ничего не добавлено. Поскольку это приложение с графическим интерфейсом, оно должно выполняться в потоке EDT, и это то, что NetBeans написал для нас в методе main() .
  10. Нажмите на кнопку « Дизайн» , чтобы увидеть пустую форму.

Мы можем добавить OpenMap JavaBeans в палитру. Для этого:

  1. Щелкните правой кнопкой мыши палитру и выберите « Диспетчер палитр».
  2. Нажмите на новую категорию и введите OpenMap в качестве имени категории. Нажмите кнопку « Добавить из JAR» , перейдите к openmap.jar , openmap.jar переключатель « Показать все JavaBeans » и выберите все доступные компоненты. Нажмите Далее.
  3. Выберите категорию палитры OpenMap и нажмите « Готово» . Новая категория палитры была добавлена ​​в палитру.
    3: Создать новое приложение Java

    3: Создать новое приложение Java

Добавить карту

  1. Найдите MapBean и перетащите его в MapFrame .
  2. В окне навигатора NetBeans щелкните правой кнопкой мыши mapBean1 , выберите « Изменить имя переменной» и установите для него значение mapBean .
  3. В окне навигатора щелкните правой кнопкой мыши JFrame и измените его макет на BorderLayout
  4. Полученный код показан в листинге 2.
    Рисунок 4. Укажите имя и местоположение проекта.

    Рисунок 4. Укажите имя и местоположение проекта.

Рисунок 5: Добавьте openmap.jar в вашу папку Libraries

Рисунок 5: Добавьте openmap.jar в вашу папку Libraries

Компонент com.bbn.openmap.MapBean является основным компонентом окна карты в наборе инструментов OpenMap . MapBean производным от класса java.awt.Container . Поскольку это компонент Swing, его можно добавить в иерархию окон Java, как и любой другой компонент пользовательского интерфейса.

Чтобы создать карту в MapBean , Layers (com.bbn.openmap.Layer) добавляются в MapBean . Слои происходят от java.awt.Component и они являются единственными компонентами, которые могут быть добавлены в MapBean. Поскольку Layers являются Components содержащимися в контейнере MapBean , рендеринг Layers на карту контролируется механизмом рендеринга компонентов Java. Этот механизм контролирует, как слоистые компоненты окрашены друг на друга. Чтобы убедиться, что каждый компонент отображается в окне в правильном порядке, класс Component включает метод, который позволяет ему сообщать механизму рендеринга, что он хотел бы быть нарисованным. Эта функция позволяет Layers работать независимо друг от друга и позволяет MapBean не знать, что происходит на слоях.

Листинг 1: Базовое приложение Swing

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class MapFrame extends javax.swing.JFrame {
 
   /** Creates new form MapFrame */
   public MapFrame() {
      super("Simple Map");
      initComponents();
   }
 
  @SuppressWarnings("unchecked")
   // <editor-fold defaultstate="collapsed" desc="Generated Code">                         
   private void initComponents() {
     // Content suppressed
   
 
   /**
    * @param args the command line arguments
    */
   public static void main(String args[]) {
       
      /* Create and display the form */
      java.awt.EventQueue.invokeLater(new Runnable() {
         @Override
         public void run() {
            new MapFrame().setVisible(true);
         }
      });
   }
}

Листинг 2: Добавить MapBean

1
2
3
4
5
6
7
8
9
@SuppressWarnings("unchecked")
  // <editor-fold defaultstate="collapsed" desc="Generated Code">                         
  private void initComponents() {
    mapBean = new com.bbn.openmap.MapBean();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
     getContentPane().add(mapBean, java.awt.BorderLayout.CENTER);
 
     pack();
  

Листинг 3: Добавление ShapeLayer в MapBean

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
/**
  * Create a ShapeLayer to show world political boundaries. Set the properties of the layer. This assumes that
  * the datafiles {@code dcwpo-browse.shp} and {@code dcwpo-browse.ssx} are in a path specified in the CLASSPATH variable.
  * These files are distributed with OpenMap and reside in the toplevel "share" subdirectory.
  */
private void initMap() {
      Properties shapeLayerProps = new Properties();
      shapeLayerProps.put("prettyName", "Political Solid");
      shapeLayerProps.put("lineColor", "000000");
      shapeLayerProps.put("fillColor", "BDDE83");
      shapeLayerProps.put("shapeFile", "resources/map/shape/dcwpo-browse.shp");
      shapeLayerProps.put("spatialIndex", "resources/map/shape/dcwpo-browse.ssx");
 
      ShapeLayer shapeLayer = new ShapeLayer();
      shapeLayer.setProperties(shapeLayerProps);
 
      // Add the political layer to the map
      mapBean.add(shapeLayer);
}

Слои в приложении OpenMap могут использовать данные из многих источников:

  • Вычисляя их
  • Из файлов данных локального жесткого диска.
  • Из файлов данных из URL.
  • Из файлов данных, содержащихся в JAR-файле.
  • Использование информации, полученной из базы данных (JDBC).
  • Использование информации, полученной от картографического сервера (изображения или объекты карты).
  1. В листинге 3 показан новый метод initMap() добавленный в конструктор MapFrame после initComponents() который показывает, как добавить ShapeLayer в MapBean , чтобы визуализировать карту политических границ, извлеченную из файлов shape (.shp) . Щелкните правой кнопкой MapFrame класс MapFrame и выберите « Запустить файл» . Вы должны увидеть окно рисунка 2. Хорошо сделано.

    Приложение OpenMap настроено с помощью openmap.properties file . Содержимое этого файла указывает, какие компоненты создаются и добавляются в платформу приложения, включая слои. Приложения можно настроить без перекомпиляции, просто изменив файл o penmap.properties с помощью текстового редактора. Компоненты, которые были написаны с пониманием структуры, могут быть добавлены в приложение, просто сделав дополнения в вышеупомянутый файл свойств. Компоненты, написанные для использования свойств, получат свои настройки для правильной инициализации. Например, слои, которые зависят от расположения файлов данных или серверов, обычно имеют свойства, позволяющие устанавливать эти расположения во время выполнения. Этот файл свойств обычно находится либо в папке приложения, либо лучше в домашней папке пользователя. В последнем случае каждый пользователь может настроить приложение под свои нужды.

    Давайте переместим свойства слоя формы в файл свойств и прочитаем их оттуда.

  1. Щелкните правой кнопкой мыши проект OpenMap и выберите « Создать» → «Свойства файла» во всплывающем меню. Дайте ему имя свойства и нажмите « Готово» .
  2. Вы можете просмотреть этот файл в окне » Проекты» , показать щелчок в окне » Файлы» и дважды щелкнуть по нему, чтобы открыть его в редакторе NetBeans.
  3. Вставьте содержимое листинга 4.
  4. Закомментируйте строки, в которых свойства слоя формы установлены в initMap() и замените их кодом из листинга 5.
  5. Запустите приложение еще раз, чтобы увидеть то же самое окно (рисунок 2). Листинг 4: openmap.properties
    1
    2
    3
    4
    5
    prettyName=Political Solid
    lineColor=000000
    fillColor=BDDE83
    shapeFile=resources/map/shape/dcwpo-browse.shp
    spatialIndex=resources/map/shape/dcwpo-browse.ssx

    OpenMap предоставляет специальный класс для обработки свойств. com.bbn.openmap.PropertyHandler — это компонент, который использует файл openmap.properties для настройки приложения. Можно openmap.properties из openmap.properties файла считывать свойства, или оставить его самостоятельно, чтобы найти файл openmap.properties в пути к классам Java и в домашнем каталоге пользователя приложения.

    Листинг 5: содержимое initMap ()

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    InputStream is = null;
    try {
       is = new FileInputStream("openmap.properties");
       shapeLayerProps.load(is);
    } catch (FileNotFoundException ex) {
       Logger.getLogger(OpenMap.class.getName()).log(Level.SEVERE, null, ex);
    } catch (IOException ex) {
       Logger.getLogger(OpenMap.class.getName()).log(Level.SEVERE, null, ex);
    } finally {
       if (is != null) {
          try {
             is.close();
          } catch (IOException ex) {
            Logger.getLogger(OpenMap.class.getName()).log(Level.SEVERE, null, ex);
          }
       }
    }
  1. В листинге 6 показан обновленный initMap() Вам больше не нужен экземпляр Properties . Просто убедитесь, что вы openmap.properties PropertyHandler.Builder() использовать openmap.properties в локальном каталоге (он же ./openmap.properties), в противном случае он может забрать один из домашнего каталога пользователя или другого местоположения. Конечно, PropertyHandler может сделать намного больше, чем это, как мы увидим в будущих уроках. Листинг 6: содержимое initMap () с использованием PropertyHandler
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    private void initMap() {
       PropertyHandler propertyHandler = null;
       try {
          propertyHandler = new PropertyHandler.Builder().setPropertiesFile("./openmap.properties").build();
       } catch (IOException ex) {
           Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
       }
       ShapeLayer shapeLayer = new ShapeLayer();
       if (propertyHandler != null) {
          shapeLayer.setProperties(propertyHandler.getProperties());
       }
     
       // Add the political layer to the map
       mapBean.add(shapeLayer);
    }

А как насчет параллелизма?

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

Есть (по крайней мере) четыре способа сделать это:

  • javax.swing.SwingWorker
  • com.bbn.openmap.util.SwingWorker
  • java.awt.SecondaryLoop
  • java.util.concurrent.CompletableFuture

Давайте начнем смотреть на каждого из них.

javax.swing.SwingWorker

Традиционным способом является использование SwingWorker для грязной работы (листинг 7). SwingWorker класс SwingWorker предоставляет два параметризованных типа. Первый параметризованный тип ( ShapeLayer ) является типом возврата для методов doInBackground() и get() . Объект, возвращаемый doInBackground() становится доступным doInBackground() get() после завершения фоновой задачи. Второй параметризованный тип применяется к периодически публикуемым значениям. Это полезно, когда долгосрочные задачи публикуют частичные результаты. Здесь мы используем Void , так как мы не публикуем частичные результаты. Код внутри doInBackground() выполняется в фоновом потоке. Здесь мы читаем свойства с помощью PropertyHandler и создаем и возвращаем ShapeLayer .

Чтобы запустить фоновый поток, мы вызываем SwingWorker's execute() . Это планирует поток для выполнения и немедленно возвращает. overridden done() метод overridden done() вызывается в EDT после завершения фоновой задачи. Этот метод — то, куда вы помещаете код, чтобы обновить или обновить GUI. Method get() GUI. Method get() блокирует, пока фоновая задача не завершится. Однако если вы вызываете get() внутри метода done() , блок не возникает, поскольку фоновая задача завершена. В этом методе мы добавляем слой в mapBean . Однако, поскольку MapFrame уже визуализирован, его также необходимо MapFrame для отображения слоев карты. Это достигается путем повторной MapFrame .

Листинг 7: содержимое initMap () с использованием javax.swing.SwingWorker

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void initMap() {
   SwingWorker<ShapeLayer, Void> worker = new SwingWorker<ShapeLayer, Void>() {
 
     @Override
     public ShapeLayer doInBackground() {
       PropertyHandler propertyHandler = null;
       try {
           propertyHandler = new PropertyHandler.Builder().setPropertiesFile("./openmap.properties").build();
       } catch (IOException ex) {
          Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
       }
       ShapeLayer shapeLayer = new ShapeLayer();
       if (propertyHandler != null) {
          shapeLayer.setProperties(propertyHandler.getProperties());
       }
       return shapeLayer;
     }
 
     @Override
     protected void done() {
        try {
            if (!isCancelled()) {
               // Add the political layer to the map
               mapBean.add(get());
               MapFrame.this.revalidate();
            }
        } catch (InterruptedException | ExecutionException ex) {
          Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
     }
   };
   // invoke background thread
   worker.execute();
}

com.bbn.openmap.util.SwingWorker

Второе решение использует SwingWorker предоставляемый OpenMap (листинг 8). Это упрощенная версия SwingWorker Java Swing. Параметризованный тип ( ShapeLayer ) является типом возврата метода construct (). Здесь мы реорганизовали создание ShapeLayer для его собственного метода getShapeLayer() (листинг 9).

Листинг 8: содержимое initMap () с использованием com.bbn.openmap.util.SwingWorker

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
private void initMap() {
      com.bbn.openmap.util.SwingWorker<ShapeLayer> worker = new com.bbn.openmap.util.SwingWorker<ShapeLayer>() {
 
         @Override
         public ShapeLayer construct() {
            return getShapeLayer();
         }
 
         @Override
         public void finished() {
            // Add the political layer to the map
            mapBean.add(get());
            MapFrame.this.revalidate();
         }
 
      };
      // invoke background thread
      worker.execute();
}

Чтобы запустить фоновый поток, мы вызываем метод execute() SwingWorker. Это планирует поток для выполнения и немедленно возвращает. Переопределенный метод finish finished() вызывается в EDT после завершения фоновой задачи. Этот метод — то, куда вы помещаете код, чтобы обновить или обновить GUI. Method get() блокирует, пока фоновая задача не завершится. Однако, если вы вызываете get() в методе finished() , блок не возникает, поскольку фоновая задача завершена. В этом методе мы добавляем слой в mapBean . Однако, поскольку MapFrame уже визуализирован, его также необходимо MapFrame для отображения слоев карты. Это достигается путем повторной MapFrame .

Вы можете сделать это более очевидно на быстрой машине, если добавите Thread.sleep(10_000); оператор перед оператором return в методе construct() . Вы должны увидеть, что окно приложения не ждет, пока SwingWorker завершит свою работу для отображения.

Листинг 9: метод рефакторинга getShapeLayer ()

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private ShapeLayer getShapeLayer() {
    PropertyHandler propertyHandler = null;
    try {
        propertyHandler = new PropertyHandler.Builder().setPropertiesFile("./openmap.properties").build();
    } catch (IOException ex) {
        Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
    }
    ShapeLayer shapeLayer = new ShapeLayer();
    if (propertyHandler != null) {
        shapeLayer.setProperties(propertyHandler.getProperties());
    }
//    try {
//        Thread.sleep(10_000);
//    } catch (InterruptedException ex) {
//        Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
//    }
    return shapeLayer;
}

java.awt.SecondaryLoop

Третье решение использует SecondaryLoop (листинг 11). Интерфейс предоставляет два метода, enter() и exit() , которые можно использовать для запуска и остановки цикла событий. Несмотря на то, что загрузка свойств и создание слоя формы выполняются в другом потоке, пользовательский интерфейс не реагирует и ожидает завершения рабочего потока, прежде чем он будет отображен на экране.

Из JavaDoc: «Когда вызывается метод enter() , текущий поток блокируется, пока цикл не завершится методом exit() . Кроме того, новый поток событий запускается в потоке диспетчеризации событий, который может быть или не быть текущим потоком. Цикл можно завершить в любом потоке, вызвав его метод exit() . […] Типичный вариант применения этого интерфейса — модальные диалоги AWT и Swing. Когда модальный диалог отображается в потоке диспетчеризации событий, он входит в новый вторичный цикл. Позже, когда диалог скрыт или расположен, он выходит из цикла, и поток продолжает свое выполнение ». Другими словами, он блокирует текущий поток, поэтому он не является «заменой» SwingWorker для всех случаев. Не существует метода обратного вызова done() как в SwingWorker где вы можете вызвать get() не блокируя текущий поток.

Листинг 10: содержимое initMap () с использованием SecondaryLoop

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private void initMap() {
   final ShapeLayer shapeLayer = new ShapeLayer();
   final SecondaryLoop loop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
   Thread work = new Thread() {
 
       @Override
       public void run() {
         PropertyHandler propertyHandler = null;
         try {
             propertyHandler = new PropertyHandler.Builder().setPropertiesFile("./openmap.properties").build();
         } catch (IOException ex) {
            Logger.getLogger(MapFrame.class.getName()).log(Level.SEVERE, null, ex);
         }
 
         if (propertyHandler != null) {
            shapeLayer.setProperties(propertyHandler.getProperties());
         }
         loop.exit();
      }
 
   };
 
   // We start the thread to do the real work 
   work.start();
 
   // Blocks until loop.exit() is called 
   loop.enter();
 
   // Add the political layer to the map
   mapBean.add(shapeLayer);
}

java.util.concurrent.CompletableFuture

Java 8 предоставляет новый класс, CompletableFuture . CompletableFuture<T> расширяет Future<T> , предоставляя функциональные монадические операции и продвигая асинхронную модель программирования, управляемую событиями, в отличие от блокировки в более старой Java.

Вы должны иметь JDK 8 или более поздней версии, чтобы иметь возможность использовать его. Если вы этого не сделаете, щелкните правой кнопкой мыши проект OpenMap и выберите Свойства . Выберите категорию « Библиотеки» и выберите платформу Java 8 Java (вам может потребоваться добавить новую платформу Java 8, нажав кнопку « Управление платформами» и перейдя в папку, в которую вы загрузили и установили JDK 8). Затем выберите категорию « Источники » и измените « Исходный / двоичный формат» на JDK 8 .

Обычно Futures представляют фрагмент кода, выполняемый другим потоком, но они не являются асинхронными, то есть вы не можете сказать им, чтобы они выполняли задачу асинхронно и возвращались когда-нибудь в будущем с результатом. В этом случае вы можете просто создать CompletableFuture, вернуть его своему клиенту, и, когда вы считаете, что ваши результаты доступны, просто complete() будущее и разблокируйте всех клиентов, ожидающих этого будущего. Конечно, есть блокирующий метод get() как в случае с SwingWorker.

CompletableFuture предоставляет асинхронные методы, которые выполняют свою задачу в другом потоке, чем предыдущая, а также не асинхронные методы, которые выполняют свою задачу в том же потоке, что и предыдущая задача. В асинхронных методах задача передается в пул потоков fork-join, а когда она заканчивается, результат передается следующей задаче. Когда следующая задача заканчивается, ее результат отправляется дальше и так далее. Это довольно аккуратно и просто.

Листинг 11: содержимое initMap () с использованием CompletableFuture

1
2
3
4
5
6
7
8
private void initMap() {
   CompletableFuture.supplyAsync(() -> getShapeLayer()).thenAcceptAsync(
      shapeLayer -> {
          // Add the political layer to the map
          mapBean.add(shapeLayer);
          MapFrame.this.revalidate();
      });
}

Модифицированный initMap () показан в листинге 11. Вы можете предоставить новую задачу глобальному ForkJoinPool.commonPool() общего назначения, представленному в JDK 8, вызывая supplyAsync() и передавая Supplier ( () -> getShapeLayer() ) , Существует также переопределенный supplyAsync() который принимает Executor, если вы не хотите использовать общий пул потоков. Supplier<R> — это новый интерфейс, представленный в Java 8, который не принимает параметров и возвращает значение типа R (в нашем случае это ShapeLayer ).

Вы можете применить дальнейшую обработку, используя thenApply() или thenApplyAsync() (которые принимают Function<T, R> ), но в нашем примере это не требуется.

Вы можете получить результат обратно асинхронно, используя неблокирующие thenAccept() или thenAcceptAsync() , которые принимают Consumer<T> . Они позволяют вам потреблять будущую стоимость, когда она будет готова. Consumer<T> является противоположностью Supplier<R>; он принимает параметр типа T и возвращает void .

Посмотрите, как элегантно это последнее решение.

Вывод

Мы прошли долгий путь в этом первом уроке OpenMap. Мы узнали, как создать MapFrame в IDE NetBeans, которая представляет собой Swing JFrame и увидели, как использовать IDE для добавления OpenMap JavaBeans в палитру, а затем перетащить MapBean на MapFrame . Мы узнали, как добавлять слои в MapBean для отображения файлов карты MapBean . Слои настраиваются через файлы свойств. Мы увидели, как использовать PropertyHandler для чтения наших свойств. Мы также увидели четыре способа загрузки файлов нашей карты из другого потока, чтобы обеспечить MapFrame нашего MapFrame даже если файлы карты загружаются слишком долго.

В следующем уроке мы углубимся во внутренности OpenMap и узнаем о MapHandler .