pcl_openmap

1.简介

欢迎使用OpenMap系列教程的第5个教程。 OpenMap是一个免费的开源Java GIS库。

这是以前的教程列表:

  • 在第一个教程中,我们创建了一个基本的OpenMap GIS应用程序,该应用程序在JFrame中显示一个从文件系统加载的具有一个形状图层的地图。 该教程基于com.bbn.openmap.app.example.SimpleMap
  • 在第二个教程中,我们扩展了基本应用程序以使用MapHandler
  • 在第三个教程中,我们看到了如何利用BeanContext技术在openmap.properties文件中声明我们的类并以声明方式构建整个应用程序。
  • 第四个教程介绍了地图图层。

在本教程中,我们将讨论如何基于OpenMap构建3层GIS应用程序。 我们将在探索新的OpenMap功能方面稍作休息,并将主要回顾我们在先前教程中学到的内容。

2.需求和架构概述

您的老板或客户提出了一些要求。 在第一个冲刺(例如Scrum)中,应用程序应该能够:

  • 从数据库读取数据/向数据库写入数据
  • 在GIS地图上显示数据
  • 与数据交互并显示其属性
  • 将地理数据移动到其他位置并将其保存回数据库
  • 创建/更新/删除地图数据

您可能会说得很简单,然后继续草绘应用程序的3层架构草案:

图1: 三层架构

3层架构遵循“ 模型-视图-控制器(MVC)”架构模式。 从数据库(后端)创建模型 。 我们的View是我们在之前的教程中构建的OpenMap GIS应用程序,能够将数据显示为点,线,多边形等。并且Controller将所有内容连接起来。
类似的架构是Model-View-ViewModel(MVVM) ,我们还将对其进行简要讨论。

3.技术

3.1后端

后端主要是数据库,或更准确地说,是数据库管理系统(DBMS) 。 在这里,您可以选择:

*具有或不具有地理空间扩展的关系数据库 (Oracle,MySQL,Postgresql,MS SQL Server,Sqlite,Hsqldb,JavaDB等)。 地理空间扩展存在于MySQL , Postgresql , Oracle , SQLite ; MS SQL Server 2008带有内置的空间扩展。
* 基于对象的空间数据库
*具有空间支持的No-SQL数据库(例如CassandraDB, CouchDB , MongoDB , Neo4j等)

优化了空间数据库或地理数据库,以存储和查询表示在几何空间中定义的对象的数据。 大多数空间数据库都允许根据OpenGIS规范表示简单的几何对象,例如点,线和面以及空间索引。 但是,您无需具有GeoSpatial数据库即可构建GIS应用程序,但是使用它会有好处。

3.2模型

您如何访问数据库以检索要用于Java应用程序的数据? 以下是您可以使用的可能技术的列表:

  • SQL查询数据库,即Java Database Connectivity或JDBC 。 这是传统方式(但是我们在2016年!)。 您需要“讲” SQL来查询数据库并在ResultSets检索数据,当您的应用程序遵循面向对象模型时(除非您的数据库也是面向对象或对象关系的)也不太方便。
  • 对象关系映射,例如Java Persistence API(JPA) 。 这是将数据库表映射到Java对象的现代方法。 NetBeans为您提供了一个不错的JPA映射向导。
  • 功能映射。 如果您是Java 8专家,并且喜欢lambda,那么为什么不使用λ表达式和Stream API而不是SQL查询或JPA? Speedment是一个Java库,使这个梦想成为现实。 这是SQL和Stream API之间的比较,以便查询数据。

3.3控制器

最后一个问题是如何将视图连接到模型? 这里的关键问题是各个组件之间的松耦合。 松散耦合使您可以使用另一种技术替换应用程序的任何层,而又不影响其他层(或进行有限的更改)。 有许多解决方案,例如:

  • Java 6 ServiceLoader
  • NetBeans查找API
  • Dukescript(MVVM) 。 将DukeScript用于客户端-服务器应用程序的好处之一是代码重用。 您可以在客户端和服务器上使用相同的模型类。 这是映射JPA和Dukescript的教程 。

4.构建我们的应用程序

我不会在这里探索所有这些技术。 请随意查看本文结尾处的参考。

在本文中,我们将看到如何使用模型的JPA和控制器的NetBeans Lookup API来构建MVC GIS应用程序。 在以后的文章中,我们将看到替代技术,例如用Speedment替换JPA和用Dukescript用MVVM替换MVC。

4.1我们的观点

在之前的文章中,我们已经创建了一个OpenMap应用程序。 让我们回顾一下并重构它。

我们的OpenMap应用程序包含以下文件层次结构:

  • openmap

    • DMSCoordInfoFormatter
    • DemoLayer
    • MyDrawingTool
    • OpenMap
  • openmap.properties

让我们这样重构它:

  • openmap

    • OpenMap.java
    • openmap.controller
    • openmap.model
    • openmap.view
      • DMSCoordInfoFormatter.java
      • DemoLayer.java
      • MyDrawingTool.java
  • openmap.properties

也不要忘记更新openmap.properties的路径。 上面的包结构描述了Model-View-Controller(MVC)设计模式。

在NetBeans中(而且在其他IDE),你可以很容易地应用重构(如移动一个文件或文件夹到另一个文件夹或重命名文件/文件夹)上的文件/文件夹,右键单击并选择子菜单重构下一个重构。

添加一个城市图层(来自OpenMap的原始openmap.properties ):

清单1 – openmap.properties –城市层

# These layers are turned on when the map is first started.  Order
# does not matter here...
openmap.startUpLayers=demo cities graticule shapePolitical# Layers listed here appear on the Map in the order of their names.
openmap.layers=demo cities graticule shapePolitical
...
###
# LocationLayer that holds cities.  The palette for this layer lets
# you turn on the names and declutter matrix, if you want.  The
# declutter matrix can get expensive at small scales.
cities.class=com.bbn.openmap.layer.location.LocationLayer
cities.prettyName=World Cities
cities.locationHandlers=csvcities
cities.useDeclutter=false
cities.declutterMatrix=com.bbn.openmap.layer.DeclutterMatrixcsvcities.class=com.bbn.openmap.layer.location.csv.CSVLocationHandler
csvcities.prettyName=World Cities
csvcities.locationFile=resources/map/cities.csv
csvcities.csvFileHasHeader=true
csvcities.locationColor=FF0000
csvcities.nameColor=008C54
csvcities.showNames=false
csvcities.showLocations=true
csvcities.nameIndex=0
csvcities.latIndex=5
csvcities.lonIndex=4
csvcities.csvFileHasHeader=true

并且不要忘记将cities.csv复制到resources/map

再次运行该应用程序以查看新层。

4.2我们的数据库架构

我们的数据库架构显示在下面的清单中。 它主要由一个Supplier表组成。 我们想在地图上将我们的供应商显示为GeoPoint

以下是在NetBeans中创建SQLite数据库的步骤(您可以选择任何喜欢的DBMS):

  1. 右键单击Libraries
  2. 从弹出菜单中选择添加JAR /文件夹...
  3. 导航到您从中下载SQLite的文件夹,然后选择sqlite-jdbc-xxx.jar
  4. 选择复制到库文件夹 ,然后单击打开 。 驱动程序应显示在“ 库”下。
  5. 单击窗口→服务菜单以显示服务选项卡。
  6. 展开数据库节点
  7. 右键单击“ 驱动程序”节点,然后选择“ 新驱动程序”
  8. 点击添加
  9. 导航到从SQLite网站下载sqlite-jdbc-xxxx.jar文件的位置 ; 驱动程序类应为org.sqlite.JDBCNameSQLite
  10. 单击确定SQLite应该列在Drivers
  11. 右键单击数据库,然后选择New Connection…
  12. 选择SQLite驱动程序,然后单击下一步
  13. 提供一个JDBC URL,例如jdbc:sqlite:C:\db\suppliers.sqlite-3并单击Finish 。 您的连接应显示在“ 数据库”下。
  14. 右键单击它,然后选择Connect…
  15. 右键单击表,然后选择Execute Command…
  16. 输入以下SQL语句,然后单击“ Run SQL按钮:

清单2 –供应商表

CREATE TABLE supplier (
SID             INTEGER           PRIMARY KEY,
NAME               VARCHAR2 (30)     NOT NULL,
CITY               VARCHAR2 (30)     NOT NULL,
TYPE            VARCHAR2 (10)     NOT NULL
CONSTRAINT TYPE CHECK (TYPE IN ('GROSS','RETAIL')),
LATITUDE        NUMBER (12,10)    NOT NULL
CONSTRAINT LATITUDE CHECK (LATITUDE BETWEEN -90.0000000000 AND 90.0000000000),
LONGITUDE           NUMBER (13,10)    NOT NULL
CONSTRAINT LONGITUDE CHECK (LONGITUDE BETWEEN -180.0000000000 AND 180.0000000000),
CONSTRAINT UID UNIQUE (SID, NAME, LATITUDE, LONGITUDE)
)

验证新表是否已创建并列在“ 表”下。 您可以将相同的样本数据添加到表中:

清单3 –样本数据

INSERT INTO supplier (NAME, CITY, TYPE, LATITUDE, LONGITUDE)
VALUES ('HP', 'ATHENS', 'GROSS', 38.1216011, 23.65486336);
INSERT INTO supplier (NAME, CITY, TYPE, LATITUDE, LONGITUDE)
VALUES ('DELL', 'BRUSSELS', 'RETAIL', 50.83704758, 4.367612362);
INSERT INTO supplier (NAME, CITY, TYPE, LATITUDE, LONGITUDE)
VALUES ('APPLE', 'LONDON', 'RETAIL', 51.48791122, -0.177998126);
INSERT INTO supplier (NAME, CITY, TYPE, LATITUDE, LONGITUDE)
VALUES ('TOSHIBA', 'PARIS', 'GROSS', 48.88155365, 2.432832718);

在继续操作之前,请不要忘记断开与数据库的连接。 由于SQLite是独立数据库,因此它主要是文件系统中的文件。 一次只能有一个应用程序可以访问它。 如果从“ 服务”选项卡连接到它,并尝试同时从OpenMap应用程序访问它,则会出现数据库被锁定的异常。 对于诸如PostgresqlMS SQL Server之类的“实际” DBMS,情况并非如此,它们可以并发访问。

4.3建立模型

让我们根据以上模式构建一个JPA模型。 NetBeans提供了很好的JPA支持:

  1. 右键单击openmap.model
  2. New → Other → Persistence → Entity Classes from Database选择New → Other → Persistence → Entity Classes from Database然后单击下一步。
  3. 选择您的数据库连接suppliers.sqlite-3
  4. 从“ 可用表”中选择suppliers ,然后单击“ 添加”以将其移动到“ 选定表”中
  5. 点击下一步
  6. 在第3步中,仅选中“ 为持久字段生成命名查询注释” ,然后单击“ 下一步”
  7. 在第4步中,取消选中所有复选框,然后单击Finish

向导在openmap.model下创建了一个新类Suppliers和一个不必要的SupplierPK 。 它还创建了文件META-INF/persistence.xml ,其中包含有关数据库的连接信息:

清单4 – persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"><persistence-unit name="OpenMapPU" transaction-type="RESOURCE_LOCAL"><provider>org.eclipse.persistence.jpa.PersistenceProvider</provider><class>openmap.model.Supplier</class><properties><property name="javax.persistence.jdbc.url" value="jdbc:sqlite:C:\db\suppliers.sqlite3"/><property name="javax.persistence.jdbc.user" value=""/><property name="javax.persistence.jdbc.driver" value="org.sqlite.JDBC"/><property name="javax.persistence.jdbc.password" value=""/></properties></persistence-unit>
</persistence>

由于包含主键定义的架构(请参见清单2),向导将为主键生成SupplierPK类。 这不是必需的,因此删除此类并从Supplier类中删除此字段及其引用。 修改您的Supplier类,使其类似于以下清单:

清单5 – Supplier.java

package openmap.model;import java.io.Serializable;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;/**** @author ikost*/
@Entity
@Table(name = "supplier")
@NamedQueries({@NamedQuery(name = "Supplier.findAll", query = "SELECT s FROM Supplier s"),@NamedQuery(name = "Supplier.findBySid", query = "SELECT s FROM Supplier s WHERE s.sid = :sid"),@NamedQuery(name = "Supplier.findByName", query = "SELECT s FROM Supplier s WHERE s.name = :name"),@NamedQuery(name = "Supplier.findByCity", query = "SELECT s FROM Supplier s WHERE s.city = :city"),@NamedQuery(name = "Supplier.findByType", query = "SELECT s FROM Supplier s WHERE s.type = :type"),@NamedQuery(name = "Supplier.findByLatitude", query = "SELECT s FROM Supplier s WHERE s.latitude = :latitude"),@NamedQuery(name = "Supplier.findByLongitude", query = "SELECT s FROM Supplier s WHERE s.longitude = :longitude")})
public class Supplier implements Serializable {private static final long serialVersionUID = 1L;@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Basic(optional = false)@Column(name = "SID")private int sid;@Basic(optional = false)@Column(name = "NAME")private String name;@Basic(optional = false)@Column(name = "CITY")private String city;@Basic(optional = false)@Column(name = "TYPE")@Enumerated(EnumType.STRING)private String type;@Basic(optional = false)@Column(name = "LATITUDE")private double latitude;@Basic(optional = false)@Column(name = "LONGITUDE")private double longitude;public enum TYPE {GROSS, RETAIL};public Supplier() {}public Supplier(int id) {this.sid = id;}public Supplier(int id, String name, String city,TYPE type, double latitude, double longitude) {this.sid = id;this.name = name;this.city = city;this.type = type;this.latitude = latitude;this.longitude = longitude;}public int getSid() {return sid;}public void setSid(int sid) {this.sid = sid;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getCity() {return city;}public void setCity(String city) {this.city = city;}public TYPE getType() {return type;}public void setType(TYPE type) {this.type = type;}public double getLatitude() {return latitude;}public void setLatitude(double latitude) {this.latitude = latitude;}public double getLongitude() {return longitude;}public void setLongitude(double longitude) {this.longitude = longitude;}@Overridepublic int hashCode() {return sid;}@Overridepublic boolean equals(Object object) {if (!(object instanceof Supplier)) {return false;}Supplier other = (Supplier) object;if (this.sid != other.sid) {return false;}return true;}@Overridepublic String toString() {return "openmap.model.Supplier[ sid =" + sid + " ]";}}

JPA 2.1为枚举提供了映射支持(请参见上面清单中的type字段)。

4.4建立您的控制器

NetBeans还可轻松为模型生成控制器。

  1. 右键单击openmap.controller
  2. New → Other → Persistence → JPA Controller Classes from Entity Classes选择New → Other → Persistence → JPA Controller Classes from Entity Classes ,然后单击下一步
  3. 从“ 可用实体类 ”列表中选择“ Supplier ”,然后单击“ 添加”将其移动到“ 选定实体类 ”列表中。
  4. 点击下一步
  5. 在第3步中,将包固定为openmap.controller
  6. 点击完成

该向导创建了SupplierJpaController以及3个异常文件。 现在,视图可以访问此控制器以便对模型执行操作。

清单6 – SupplierJpaController.java

package openmap.controller;import java.io.Serializable;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import javax.persistence.EntityNotFoundException;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import openmap.controller.exceptions.NonexistentEntityException;
import openmap.model.Supplier;/**** @author ikost*/
public class SupplierJpaController implements Serializable {public SupplierJpaController(EntityManagerFactory emf) {this.emf = emf;}private EntityManagerFactory emf = null;public EntityManager getEntityManager() {return emf.createEntityManager();}public void create(Supplier supplier) {EntityManager em = null;try {em = getEntityManager();em.getTransaction().begin();em.persist(supplier);em.getTransaction().commit();} finally {if (em != null) {em.close();}}}public void edit(Supplier supplier) throws NonexistentEntityException, Exception {EntityManager em = null;try {em = getEntityManager();em.getTransaction().begin();supplier = em.merge(supplier);em.getTransaction().commit();} catch (Exception ex) {String msg = ex.getLocalizedMessage();if (msg == null || msg.length() == 0) {int id = supplier.getSid();if (findSupplier(id) == null) {throw new NonexistentEntityException("The supplier with id " + id + " no longer exists.");}}throw ex;} finally {if (em != null) {em.close();}}}public void destroy(int id) throws NonexistentEntityException {EntityManager em = null;try {em = getEntityManager();em.getTransaction().begin();Supplier supplier;try {supplier = em.getReference(Supplier.class, id);supplier.getSid();} catch (EntityNotFoundException enfe) {throw new NonexistentEntityException("The supplier with id " + id + " no longer exists.", enfe);}em.remove(supplier);em.getTransaction().commit();} finally {if (em != null) {em.close();}}}public List<Supplier> findSupplierEntities() {return findSupplierEntities(true, -1, -1);}public List<Supplier> findSupplierEntities(int maxResults, int firstResult) {return findSupplierEntities(false, maxResults, firstResult);}private List<Supplier> findSupplierEntities(boolean all, int maxResults, int firstResult) {EntityManager em = getEntityManager();try {CriteriaQuery cq = em.getCriteriaBuilder().createQuery();cq.select(cq.from(Supplier.class));Query q = em.createQuery(cq);if (!all) {q.setMaxResults(maxResults);q.setFirstResult(firstResult);}return q.getResultList();} finally {em.close();}}public Supplier findSupplier(int id) {EntityManager em = getEntityManager();try {return em.find(Supplier.class, id);} finally {em.close();}}public int getSupplierCount() {EntityManager em = getEntityManager();try {CriteriaQuery cq = em.getCriteriaBuilder().createQuery();Root<Supplier> rt = cq.from(Supplier.class);cq.select(em.getCriteriaBuilder().count(rt));Query q = em.createQuery(cq);return ((Long) q.getSingleResult()).intValue();} finally {em.close();}}
}

在此示例中,我们只有一个域对象Supplier但在实际应用中,您将有许多域对象。 一个常见的解决方案是创建一个Facade ,该Facade仅将视图所需的方法公开,而将其余的隐藏。 在下面的内容中,我们展示如何使用NetBeans Lookup API创建松散耦合的Facade IDBManager (请参阅参考资料)。

清单7 – IDBManager.java

package openmap.controller;import java.util.List;
import openmap.model.Supplier;/*** A facade of our controllers.** @author ikost*/
public interface IDBManager {List getSuppliers();
}

单击IDBManager左侧的blob,然后选择“ 实现接口” 。 如下表所示修改DBManager实现,以将其转换为服务提供者:

清单8 – DBManager.java

package openmap.controller;import java.util.List;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import openmap.model.Supplier;
import org.openide.util.lookup.ServiceProvider;@ServiceProvider(service = IDBManager.class)
public class DBManager implements IDBManager {private final EntityManagerFactory emf;private final SupplierJpaController suppliers;public DBManager() {emf = Persistence.createEntityManagerFactory("OpenMapPU");suppliers = new SupplierJpaController(emf);}@Overridepublic List getSuppliers() {return suppliers.findSupplierEntities();}
}

@ServiceProvider(service = IDBManager.class)可以解决所有问题。 此行将DBManager添加到默认查找中。 在下一部分中,我们将从视图中了解如何访问DBManager 。 为了使其工作,如果NetBeans没有自动添加依赖项,则需要向org-openide-util-lookup.jar添加一个依赖项。

  1. 右键单击Libraries
  2. 从弹出菜单中选择添加JAR /文件夹...
  3. 导航到并选择<NetBeans_Installation>/platform/lib/org-openide-util-lookup.jar
  4. 选择复制到库文件夹 ,然后单击打开
  5. 清理并构建您的项目,以使更改生效。

但是什么是查找? 查找是将类对象作为键并将这些类对象的实例集作为值的映射,即

Lookup = Map<Class, Set<Class>> ,例如Map<String, Set<String>>Map<Provider, Set<Provider>> 。 NetBeans提供了许多访问默认查找的方法:

Provider provider = Lookup.getDefault().lookup(Provider.class);
provider.aMethod();

或如果您具有Provider多个实现:

Collection providers = Lookup.getDefault().lookupAll(Provider.class);
for (Provider provider : providers) { ... }

从上面的代码示例中可以看到,客户端不知道客户端使用哪种实现。 它只知道接口。 松耦合!

上面的代码将服务添加到默认查找中。 客户端在接口的默认查找中查找。 默认查找是一个评估META-INF/services文件夹中的服务声明的查找。 可通过Lookup.getDefault()方法调用它。 如果您对更多细节感兴趣,则Netbeans在build/classes/META-INF/services/文件夹内创建一个文本文件package.Provider ,其中包含实现类的完全限定名称。 通过以这种方式请求服务接口,您会收到在META-INF/services文件夹中注册的实现类的实例。

当然,在NetBeans胖客户端平台中还有其他查找,而不是默认查找,但是这些不在本文讨论范围之内。

4.5建立您的视图

最后,我们需要创建一个新图层,该图层将在地图上显示我们的供应商,并在openmap.properties中声明它,以便将其添加到地图中。 如果您遵循以前的教程,那么现在应该很容易做到。 让我们逐步构建SupplierLayer.java

清单9 – SupplierLayer.java

package openmap.controller;
public class SupplierLayer extends OMGraphicHandlerLayer {private static final String LOOKUP_OBJECT = "Lookup Object";public SupplierLayer() {// This is how to set the ProjectionChangePolicy, which// dictates how the layer behaves when a new projection is// received.setProjectionChangePolicy(new StandardPCPolicy(this, true));setRenderPolicy(new BufferedImageRenderPolicy());// Making the setting so this layer receives events from the// SelectMouseMode, which has a modeID of "Gestures". Other// IDs can be added as needed.setMouseModeIDsForEvents(new String[]{"Gestures"});}/*** Called from the prepare() method if the layer discovers that its* OMGraphicList is {@code null}.** @return new {@code OMGraphicList} with {@code OMGraphics{ that you always* want to display and reproject as necessary.*/public OMGraphicList init() {final IDBManager dbManager = Lookup.getDefault().lookup(IDBManager.class);final List suppliers = dbManager.getSuppliers();// This layer keeps a pointer to an OMGraphicList that it uses// for painting. It's initially set to null, which is used as// a flag in prepare() to signal that the OMGraphcs need to be// created. The list returned from prepare() gets set in the// layer.// This layer uses the StandardPCPolicy for new// projections, which keeps the list intact and simply calls// generate() on it with the new projection, and repaint()// which calls paint().OMGraphicList omList = new OMGraphicList();// Add suppliers as OMPoints.for (Supplier supplier : suppliers) {OMPoint omSupplier = new OMPoint(supplier.getLatitude(),supplier.getLongitude(), 3);  // radiusomSupplier.putAttribute(OMGraphicConstants.LABEL,new OMTextLabeler(supplier.getName(), OMText.JUSTIFY_LEFT));omSupplier.putAttribute(LOOKUP_OBJECT, supplier);omSupplier.setLinePaint(Color.BLUE);omSupplier.setSelectPaint(Color.ORANGE);omSupplier.setOval(true);omList.add(omSupplier);}return omList;}/*** This is an important Layer method to override. The prepare method gets* called when the layer is added to the map, or when the map projection* changes. We need to make sure the OMGraphicList returned from this method* is what we want painted on the map. The OMGraphics need to be generated* with the current projection. We test for a null OMGraphicList in the* layer to see if we need to create the OMGraphics. This layer doesn't* change it's OMGraphics for different projections, if your layer does, you* need to clear out the OMGraphicList and add the OMGraphics you want for* the current projection.** @return*/@Overridepublic synchronized OMGraphicList prepare() {OMGraphicList list = getList();// Here's a test to see if it's the first time that the layer has been// added to the map. This list object will be whatever was returned from// this method the last time prepare() was called. In this// example, we always return an OMGraphicList object, so if it's null,// prepare() must not have been called yet.if (list == null) {list = init();}/** This call to the list is critical! OMGraphics need to be told where* to paint themselves, and they figure that out when they are given the* current Projection in the generate(Projection) call. If an* OMGraphic's location is changed, it will need to be regenerated* before it is rendered, otherwise it won't draw itself. You generally* know you have a generate problem when OMGraphics show up with the* projection changes (zooms and pans), but not at any other time after* something about the OMGraphic changes.** If you want to be more efficient, you can replace this call to the* list as an else clause to the (list == null) check above, and call* generate(Projection) on all the OMGraphics in the init() method below* as you create them. This will prevent the* OMGraphicList.generate(Projection) call from making an additional* loop through all of the OMGraphics before they are returned.*/list.generate(getProjection());return list;}/*** Query that an OMGraphic can be highlighted when the mouse moves over it.* If the answer is true, then highlight with this OMGraphics will be* called.** @param omg* @return*/@Overridepublic boolean isHighlightable(OMGraphic omg) {return true;}/*** Query that an OMGraphic is selectable. Examples of handing selection are* in the EditingLayer. The default OMGraphicHandlerLayer behavior is to add* the OMGraphic to an OMGraphicList called selectedList. If you aren't* going to be doing anything in particular with the selection, then return* false here to reduce the workload of the layer.** @param omg* @return* @see com.bbn.openmap.layer.OMGraphicHandlerLayer#select* @see com.bbn.openmap.layer.OMGraphicHandlerLayer#deselect*/@Overridepublic boolean isSelectable(OMGraphic omg) {return true;}/*** Query for what tooltip to display for an OMGraphic* the mouse is over.** @param omg* @return*/@Overridepublic String getToolTipTextFor(OMGraphic omg) {String ttText = null;if (omg instanceof OMPoint) {OMPoint point = ((OMPoint) omg);Object attribute = point.getAttribute(OMGraphicConstants.LABEL);if (attribute != null && attribute instanceof OMTextLabeler) {OMTextLabeler labeler = (OMTextLabeler) attribute;ttText = labeler.getData();}}return ttText;}@Overridepublic Component getGUI() {JPanel panel = PaletteHelper.createPaletteJPanel("Suppliers Layer");JCheckBox chkShowLabels = new JCheckBox("Show/Hide Labels", true);chkShowLabels.addItemListener((ItemEvent e) -> {OMGraphicList omSuppliers = getList();for (OMGraphic omSupplier : omSuppliers) {if (chkShowLabels.isSelected()) {omSupplier.putAttribute(OMGraphicConstants.LABEL,new OMTextLabeler(((Supplier) omSupplier.getAttribute(LOOKUP_OBJECT)).getName(),OMText.JUSTIFY_LEFT));} else {omSupplier.removeAttribute(OMGraphicConstants.LABEL);}}repaint();});panel.add(chkShowLabels);return panel;}
}

init()方法中,我们使用默认的 LookupDBManager检索Supplier的列表。 我们遍历所有Supplier并从其中的每一个中创建一个OMPoint 。 通过设置属性OMGraphicConstants.LABEL创建点的标签。 通过使用键"Lookup Object"将支持的Supplier添加到OMPoint的属性映射中,我们可以实现一个技巧。 我们稍后将需要它。 (不要将其与NetBeans的Lookup混淆;我们只是以类似的方式命名它,以表明它类似于NetBeans的Lookup但与它无关;您可以将其命名为其他名称)。 最后,将每个点添加到返回的OMGraphicList

单击“ 工具”按钮时,“ 图层”对话框将调用getGUI()方法(请参见下图):

图2 –供应商层

该方法创建一个带有复选框的新面板,以显示/隐藏该图层的标签。 Supplier的标签是从OMPoint属性图检索的Supplier名称,以便为其设置OMGraphicConstants.LABEL属性。 选中复选框后,标签可见,否则属性被删除。 图层被repaint()编辑。 不幸的是, repaint()不能100%起作用。 您需要缩放地图或调整地图大小,以便再次显示标签。

接下来,当我们右键单击Supplier以显示其属性时,我们想显示一个弹出菜单。 从上一教程中,您知道我们需要重写getItemsForOMGraphicMenu()方法。 如果要在右键单击图层上的任何位置时显示弹出菜单,请重写以下方法getItemsForMapMenu()

清单10 – SupplierLayer.java(续)

@Override
public List getItemsForOMGraphicMenu(OMGraphic omg) {final OMGraphic chosen = omg;List menuItems = new ArrayList<>();JMenuItem mnuProperties = new JMenuItem("Properties")mnuProperties.addActionListener((ActionEvent ae) -> {//...});menuItems.add(mnuProperties);return menuItems;
}/*** This method is called mnuCreate a right mouse click is detected over the map* and not over an OMGraphic. You can provide a List of components to be* displayed in a popup menu. You have to do the wiring for making the list* components do something, though.** @param me* @return*/
@Override
public List getItemsForMapMenu(MapMouseEvent me) {List l = new ArrayList<>();JMenuItem mnuCreate = new JMenuItem("Create New Supplier");mnuCreate.addActionListener((ActionEvent ae) -> {fireRequestMessage("Create New Supplier");});l.add(mnuCreate);return l;
}

我们缺少显示数据的表格。 我们将在此处进行快速介绍,以向您展示另一个NetBeans向导,但是您可以使用Matisse或您所知道的自由构建自己的对话框。

  1. 创建一个新的包openmap.view.properties
  2. 右键单击它,然后选择新建→其他→Swing GUI表单→主/详细样本表单 ,然后单击下一步。
  3. 将其命名为SuppliersPropertiesDialogBox ,然后单击“ 下一步”。
  4. 选择您的数据库连接供应商表,然后从要包括列中排除SID字段。
  5. 点击完成

该向导已创建一个主/明细表格,但是我们只需要明细部分。 如下图所示对其进行自定义。

图3 –供应商属性对话框

选择每个文本字段和“ 删除”按钮,单击“ 绑定” (“ 属性”区域),然后从已enabled属性和text属性中删除对主表的任何引用。

图4 –删除绑定

类型文本字段更改为组合框,因为type仅限制为enum'GROSS''RETAIL'

  1. 右键单击组合框,然后选择“ Customize Code…
  2. 单击第二个默认代码组合框,然后将其更改为custom属性
  3. 将代码更改为以下代码,然后单击“ 确定”
cmbType.setModel(new javax.swing.DefaultComboBoxModel<>(Supplier.TYPE.values()));

  1. 再次单击组合框,然后在“ 属性”中单击“ 代码”
  2. 类型参数设置为<Supplier.TYPE>

在底部添加标签( lblStatus )。 使它不透明。 与数据库的事务处理成功后,将显示为绿色,否则显示为红色。 这对用户是一个很好的反馈,以确保他/她的修改得以保留。

该对话框与实体管理器耦合以检索要显示的数据,但这通常是一个不好的设计。 删除对实体管理器和主表的所有引用,并将其转换为JDialog 。 为了避免java.lang.IllegalArgumentException: GroupLayout can only be used with one Container at a time ,请将所有组件添加到JPanel

  1. 在“ 设计”视图中调整对话框的大小
  2. 将一个面板从面板拖到顶部(旋转容器)
  3. 将面板的变量名称更改为panel
  4. 将其布局设置为Free Design
  5. 导航器中选择所有小部件,然后将它们拖动到新面板中; 它们的布局应保持不变。
  6. 导航器中选择JDialog并将其大小属性设置为[400, 230] 。 取消选中“ 可调整大小”属性。

源代码应如下所示:

清单11 – SuppliersPropertiesDialogBox.java

public class SuppliersPropertiesDialogBox extends JDialog {private final Supplier supplier;private final IDBManager dbManager;public SuppliersPropertiesDialogBox(Supplier s) {dbManager = Lookup.getDefault().lookup(IDBManager.class);initComponents();supplier = s;setData(supplier);}@SuppressWarnings("unchecked")private void btnCloseActionPerformed(java.awt.event.ActionEvent evt) {this.setVisible(false);}private void btnDeleteActionPerformed(java.awt.event.ActionEvent evt) {try {dbManager.delete(supplier);lblStatus.setBackground(Color.green);} catch (Exception ex) {Logger.getLogger(SuppliersPropertiesDialogBox.class.getName()).log(Level.SEVERE, null, ex);lblStatus.setBackground(Color.red);}}private void btnSaveActionPerformed(java.awt.event.ActionEvent evt) {try {dbManager.save(getData());lblStatus.setBackground(Color.green);} catch (Exception ex) {Logger.getLogger(SuppliersPropertiesDialogBox.class.getName()).log(Level.SEVERE, null, ex);lblStatus.setBackground(Color.red);}}public void setData(Supplier supplier) {txtName.setText(supplier.getName());txtCity.setText(supplier.getCity());txtLatitude.setText(String.valueOf(supplier.getLatitude()));txtLongitude.setText(String.valueOf(supplier.getLongitude()));cmbType.setSelectedItem(supplier.getType());}public Supplier getData() {supplier.setName(txtName.getText());supplier.setCity(txtCity.getText());supplier.setLatitude(Double.valueOf(txtLatitude.getText()));supplier.setLongitude(Double.valueOf(txtLongitude.getText()));supplier.setType(Supplier.TYPE.valueOf(cmbType.getSelectedItem().toString()));return supplier;}// initComponents() generated method omitted ...
}

如您所见,我们引用DBManager来处理数据。 我们需要向其中添加以下新方法:

清单12 – IDBManager.java

public interface IDBManager {List getSuppliers();void delete(Supplier supplier) throws Exception;void save(Supplier supplier) throws Exception;
}

及其实现:
清单13 – DBManager.java

@Overridepublic void delete(Supplier supplier)  throws Exception {try {suppliers.destroy(supplier.getSid());} catch (NonexistentEntityException ex) {Logger.getLogger(DBManager.class.getName()).log(Level.SEVERE, null, ex);throw ex;}}@Overridepublic void save(Supplier supplier)  throws Exception {Supplier s = suppliers.findSupplier(supplier.getSid());if (s == null) {suppliers.create(supplier);} else {try {suppliers.edit(supplier);} catch (Exception ex) {Logger.getLogger(DBManager.class.getName()).log(Level.SEVERE, null, ex);throw ex;}}}

现在可以将SupplierLayer修改为:

清单14 – SupplierLayer.java

@Overridepublic List getItemsForOMGraphicMenu(OMGraphic omg) {List menuItems = new ArrayList<>();JMenuItem mnuProperties = new JMenuItem("Properties");mnuProperties.addActionListener((ActionEvent ae) -> {SuppliersPropertiesDialogBox dlgProperties =new SuppliersPropertiesDialogBox((Supplier)omg.getAttribute(LOOKUP_OBJECT));dlgProperties.setVisible(true);});menuItems.add(mnuProperties);return menuItems;}

最后,

  1. 清理并构建您的应用程序,然后运行它
  2. 单击“ 图层控件”按钮,使“ 世界城市”图层不可见; 这样,您的鼠标点击地图就不会选择城市,而是供应商
  3. 右键单击地图上的供应商,然后从弹出菜单中选择“ 属性 ”; 下图对话框将显示。 当您单击“ 保存”时,您获得绿色反馈,表明您的更改已成功保存。

图5 –供应商属性对话框

做得好! 您已经构建了大多数功能,并且您的设计允许您进行修改而无需更改所有层。

这是您可以尝试的TODO列表:

  • 将上述对话框中的纬度/经度文本字段设置为易于阅读的格式,即xxºyy'zzz"N|Sxxxºyy'zzz"E|W

    • 提示 :使用我们在上一篇文章中显示的DMSCoordInfoFormatter格式化纬度/经度双DMSCoordInfoFormatter值;
    • 您可以为纬度和经度的小时/分钟/秒使用单独的文本字段,以便用户可以轻松键入新值而不会弄乱特殊字符;
    • 保存更改后,确保OMPoint显示在其新位置; 你需要添加PropertyChangeListenerSupplierLayer监听在改变Supplier

    清单15 – SupplierLayer.java(续)

private final PropertyChangeListener listener = (PropertyChangeEvent evt) -> {if (evt.getPropertyName().equals("latitude") || evt.getPropertyName().equals("longitude")) {Supplier supplier = (Supplier) evt.getSource();OMGraphicList list = getList();for (OMGraphic omPoint : list) {if (omPoint.getAttribute(LOOKUP_OBJECT).equals(supplier)) {((OMPoint) omPoint).set(supplier.getLatitude(), supplier.getLongitude());break;}}repaint();}};public OMGraphicList init() {// ...// Add suppliers as OMPoints.for (Supplier supplier : suppliers) {// ...supplier.addPropertyChangeListener(listener);omList.add(omSupplier);}// ...}

为使以上各项起作用,您需要将Supplier转变为可观察的:

清单16 – Supplier.java(续)

public class Supplier implements Serializable {@Transientprivate final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);// ...public void setSid(int sid) {int oldSid = this.sid;this.sid = sid;changeSupport.firePropertyChange("sid", oldSid, sid);}// ...public void setName(String name) {String oldName = this.name;this.name = name;changeSupport.firePropertyChange("name", oldName, name);}// ...public void setCity(String city) {String oldCity = this.city;this.city = city;changeSupport.firePropertyChange("city", oldCity, city);}// ...public void setType(TYPE type) {TYPE oldType = this.type;this.type = type;changeSupport.firePropertyChange("type", oldType, type);}// ...public void setLatitude(double latitude) {double oldLatitude = this.latitude;this.latitude = latitude;changeSupport.firePropertyChange("latitude", oldLatitude, latitude);}// ...public void setLongitude(double longitude) {double oldLongitude = this.longitude;this.longitude = longitude;changeSupport.firePropertyChange("longitude", oldLongitude, longitude);}// ...public void addPropertyChangeListener(PropertyChangeListener listener) {changeSupport.addPropertyChangeListener(listener);}public void removePropertyChangeListener(PropertyChangeListener listener) {changeSupport.removePropertyChangeListener(listener);}
  • 添加拖动功能,即用户应该能够将地图上的供应商拖动到新位置

    • 使用上一篇文章中的提示;
    • 实现DrawingToolRequestor接口
    • findAndInit()定义并初始化DrawingTool的实例
    • 重写select()drawingComplete()方法
  • 添加创建新的供应商的功能(方法getItemsForMapMenu()SupplierLayer )。 应该显示SuppliersPropertiesDialogBox ,其中已经填充了用户在地图上单击的坐标的纬度/经度字段; 然后用户应填写其他字段,并将新的供应商添加到数据库中
  • 单击“ Drawing Tool Launcher按钮时,您可以在图层上添加许多类型的图形,而这可能不是您想要的。 由于我们希望我们的Supplier层仅显示OMPointompointloader像上一篇文章中所做的那样,修改openmap.componentsomdrawingtoolompointloader
  • 您可能会遇到的另一个问题是,当右键单击OMPoint ,将显示与通过getItemsForOMGraphicMenu()创建的弹出菜单不同的弹出菜单。 com.bbn.openmap.tools.drawing.OMDrawingTool包含dt.setBehaviorMask(OMDrawingTool.QUICK_CHANGE_BEHAVIOR_MASK)OMDrawingTool定义了许多行为掩码,如上一教程中所述。 作为解决方法,我们创建了自己的OMDrawingTool

结论

在本教程中,我们创建了一个三层独立应用程序,该应用程序使用JPA从关系数据库中检索数据,并将其显示为OpenMap的层。 我们看到了如何使用NetBeans Lookup API将视图与控制器松散耦合。

您应该已经对如何开发此类应用程序有所了解,但是请不要在实际的特别是关键的应用程序中使用此代码。 代码是错误的,既不高效也不是线程安全的(例如,有关如何从不同线程中的数据库检索数据的信息,请参见com.bbn.openmap.layer.location.LayerLocationLayer )。

您还可以使用其他技术来替换各个层,例如:

  • 用纯Java 8 lambda框架替换JPA来访问数据库( Speedment )
  • 使用DukeScript将JPA粘合到您的视图

例如,由于您的视图依赖于IDBManager而不是特定的实现(例如JPA的EntityManager ),因此它不受模型的任何更改的影响(只要SupplierIDBManager的方法不变)。 然后,您可以将JPA替换为Speedment,而无需更改视图。

如果时间和空间允许,我们可能会在以后的文章中进行调查。

参考资料

  1. OpenMap开发人员指南
  2. OpenMap开发人员提示
  3. 鲍尔等 等 (2016),《 Java Persistence with Hibernate》 ,第二版,Manning。
  4. Coehlo H.,Kiourtzoglou B., Java持久性API迷你书 , JavaCodeGeeks 。
  5. Epple T.(2009),“ NetBeans查找说明!”, DZone
  6. Epple T.(2016),“ JPA和Dukescript ”
  7. Goncalves A.(2013年),《 Java EE 7入门》,Apress。
  8. Keith M.和Schincariol M.(2013), Pro JPA 2 –精通Java™Persistence API ,第二版,APress。
  9. Kostaras I. 博客 ,“松耦合”

翻译自: https://www.javacodegeeks.com/2016/06/openmap-tutorial-5-3-tier-gis-application.html

pcl_openmap

pcl_openmap_OpenMap教程5 – 3层GIS应用程序相关推荐

  1. OpenMap教程5 – 3层GIS应用程序

    1.简介 欢迎使用OpenMap系列教程的第5个教程. OpenMap是一个免费的开源Java GIS库. 这是以前的教程列表: 在第一个教程中,我们创建了一个基本的OpenMap GIS应用程序,该 ...

  2. pcl_openmap_OpenMap教程第2部分–使用MapHandler构建基本地图应用程序–第1部分

    pcl_openmap 1.简介 在第一个教程中,我们创建了一个基本的OpenMap GIS应用程序,该应用程序在JFrame中显示一个从文件系统加载的具有一个形状图层的地图. 该教程基于com.bb ...

  3. 《ArcGIS Engine+C#实例开发教程》第一讲桌面GIS应用程序框架的建立

    原文:<ArcGIS Engine+C#实例开发教程>第一讲桌面GIS应用程序框架的建立 摘要:本讲主要是使用MapControl.PageLayoutControl.ToolbarCon ...

  4. pcl_openmap_OpenMap教程–第1部分

    pcl_openmap 介绍 本系列教程将向您展示如何使用OpenMap GIS Java Swing库构建Java应用程序. OpenMap的开发人员指南是非常有用的文档,描述了OpenMap的体系 ...

  5. ArcGIS水文分析实战教程(1)GIS与水文学

    ArcGIS水文分析实战教程(1)GIS与水文学 对于大部分GIS从业人员或者利用GIS作为研究方向的人员来说,水文学过于专业,更偏重于理论化,很难从GIS的角度来模拟和分析水文的过程.这其实是个普遍 ...

  6. Directx11 教程(2) 基本的windows应用程序框架(2)

    Directx11 教程(2) 基本的windows应用程序框架(2) 原文:Directx11 教程(2) 基本的windows应用程序框架(2) 在本教程中,我们把前面一个教程的代码,进行封装.把 ...

  7. 如何通过域服务器推送安装文件,域环境推送exe程序并自动安装教程域环境推送exe程序并自动安装教程.pdf...

    域环境推送exe程序并自动安装教程域环境推送exe程序并自动安装教程 域环境推送exe 程序并自动安装教程 一.思路 微软可以通过组策略对域中的计算机实现对 MSI 格式的软件程序实现派发后自动运行, ...

  8. GIS开源程序收集(转载)

    分类包括:GIS基础函数库.GIS控件.GIS桌面程序.GIS数据引擎.WEBGIS浏览器端程序.WEBGIS服务器程序.GPS相关程序,其它分类 派系:"NET"派系," ...

  9. python基本原理概论_优学院《2020年马克思主义基本原理概论题库(高职类)》查题教程2020高校邦《Python程序设计基础【实境编程】》答案完整...

    优学院<2020年马克思主义基本原理概论题库(高职类)>查题教程2020高校邦<Python程序设计基础[实境编程]>答案完整 更多相关问题 专业职业是相对以下哪点而言的(). ...

最新文章

  1. 通信系统之信道(一)
  2. 【组合数学】递推方程 ( 非齐次部分是 指数函数 且 底是特征根 | 求特解示例 )
  3. Oracle 添加主键和索引
  4. Java文件能编译成lib吗_Makefile用于将一些.cpp和.h编译成lib
  5. 编程应该用 Mac ,还是 PC ?
  6. tomcat 之 tomcat实例配置
  7. 重点关注之自定义序列化方式(Protobuf和Msgpack)
  8. 机器人收集 + Keypose 算法:准确估计透明物体的 3D 姿态
  9. sharepoint SSP(shared services providers ) 配置
  10. MacOS Big Sur 11.2.1 (20D75) 纯净恢复版黑苹果镜像下载
  11. 超图神经网络 Hypergraph Neural Networks
  12. Windows安装redis教程
  13. kali桌面的安装与切换
  14. 美国计算机专业大学排名前30,美国计算机专业研究生大学排名TOP30
  15. 计算机桌面背景在哪里调整,电脑中怎么设置桌面背景
  16. MySQL字符集是什么
  17. 一个“编码十几年”的『老程序员』分享的四点心得
  18. 微信二维码图片长按没有出现“识别图中的二维码”
  19. 农村电商是什么 农产品电商平台如何运营?
  20. 大数据生态:Hadoop生态圈

热门文章

  1. P7518-[省选联考2021A/B卷]宝石【主席树,二分】
  2. hdu5709-Claris Loves Painting【线段树合并】
  3. AT4352-[ARC101C] Ribbons on Tree【dp,容斥】
  4. jzoj3512-游戏节目【树状数组,双向dfs】
  5. 主席树 - 可持久化线段树
  6. codeforces1440 E. Greedy Shopping
  7. 【prim】【最小生成树】最优布线问题(ssl 1612)
  8. Spark Streaming之统计socket单词数
  9. JavaFX UI控件教程(二十三)之Menu
  10. Spring思维导图,让Spring不再难懂(ioc篇)