pcl_openmap_OpenMap教程5 – 3层GIS应用程序
pcl_openmap
1.簡介
歡迎使用OpenMap系列教程的第5個教程。 OpenMap是一個免費(fèi)的開源Java GIS庫。
這是以前的教程列表:
- 在第一個教程中,我們創(chuàng)建了一個基本的OpenMap GIS應(yīng)用程序,該應(yīng)用程序在JFrame中顯示一個從文件系統(tǒng)加載的具有一個形狀圖層的地圖。 該教程基于com.bbn.openmap.app.example.SimpleMap 。
- 在第二個教程中,我們擴(kuò)展了基本應(yīng)用程序以使用MapHandler 。
- 在第三個教程中,我們看到了如何利用BeanContext技術(shù)在openmap.properties文件中聲明我們的類并以聲明方式構(gòu)建整個應(yīng)用程序。
- 第四個教程介紹了地圖圖層。
在本教程中,我們將討論如何基于OpenMap構(gòu)建3層GIS應(yīng)用程序。 我們將在探索新的OpenMap功能方面稍作休息,并將主要回顧我們在先前教程中學(xué)到的內(nèi)容。
2.需求和架構(gòu)概述
您的老板或客戶提出了一些要求。 在第一個沖刺(例如Scrum)中,應(yīng)用程序應(yīng)該能夠:
- 從數(shù)據(jù)庫讀取數(shù)據(jù)/向數(shù)據(jù)庫寫入數(shù)據(jù)
- 在GIS地圖上顯示數(shù)據(jù)
- 與數(shù)據(jù)交互并顯示其屬性
- 將地理數(shù)據(jù)移動到其他位置并將其保存回數(shù)據(jù)庫
- 創(chuàng)建/更新/刪除地圖數(shù)據(jù)
您可能會說得很簡單,然后繼續(xù)草繪應(yīng)用程序的3層架構(gòu)草案:
圖1: 三層架構(gòu)
3層架構(gòu)遵循“ 模型-視圖-控制器(MVC)”架構(gòu)模式。 從數(shù)據(jù)庫(后端)創(chuàng)建模型 。 我們的View是我們在之前的教程中構(gòu)建的OpenMap GIS應(yīng)用程序,能夠?qū)?shù)據(jù)顯示為點(diǎn),線,多邊形等。并且Controller將所有內(nèi)容連接起來。
類似的架構(gòu)是Model-View-ViewModel(MVVM) ,我們還將對其進(jìn)行簡要討論。
3.技術(shù)
3.1后端
后端主要是數(shù)據(jù)庫,或更準(zhǔn)確地說,是數(shù)據(jù)庫管理系統(tǒng)(DBMS) 。 在這里,您可以選擇:
*具有或不具有地理空間擴(kuò)展的關(guān)系數(shù)據(jù)庫 (Oracle,MySQL,Postgresql,MS SQL Server,Sqlite,Hsqldb,JavaDB等)。 地理空間擴(kuò)展存在于MySQL , Postgresql , Oracle , SQLite ; MS SQL Server 2008帶有內(nèi)置的空間擴(kuò)展。
* 基于對象的空間數(shù)據(jù)庫
*具有空間支持的No-SQL數(shù)據(jù)庫(例如CassandraDB, CouchDB , MongoDB , Neo4j等)
優(yōu)化了空間數(shù)據(jù)庫或地理數(shù)據(jù)庫,以存儲和查詢表示在幾何空間中定義的對象的數(shù)據(jù)。 大多數(shù)空間數(shù)據(jù)庫都允許根據(jù)OpenGIS規(guī)范表示簡單的幾何對象,例如點(diǎn),線和面以及空間索引。 但是,您無需具有GeoSpatial數(shù)據(jù)庫即可構(gòu)建GIS應(yīng)用程序,但是使用它會有好處。
3.2模型
您如何訪問數(shù)據(jù)庫以檢索要用于Java應(yīng)用程序的數(shù)據(jù)? 以下是您可以使用的可能技術(shù)的列表:
- SQL查詢數(shù)據(jù)庫,即Java Database Connectivity或JDBC 。 這是傳統(tǒng)方式(但是我們在2016年!)。 您需要“講” SQL來查詢數(shù)據(jù)庫并在ResultSets檢索數(shù)據(jù),當(dāng)您的應(yīng)用程序遵循面向?qū)ο竽P蜁r(除非您的數(shù)據(jù)庫也是面向?qū)ο蠡驅(qū)ο箨P(guān)系的)也不太方便。
- 對象關(guān)系映射,例如Java Persistence API(JPA) 。 這是將數(shù)據(jù)庫表映射到Java對象的現(xiàn)代方法。 NetBeans為您提供了一個不錯的JPA映射向?qū)А?
- 功能映射。 如果您是Java 8專家,并且喜歡lambda,那么為什么不使用λ表達(dá)式和Stream API而不是SQL查詢或JPA? Speedment是一個Java庫,使這個夢想成為現(xiàn)實(shí)。 這是SQL和Stream API之間的比較,以便查詢數(shù)據(jù)。
3.3控制器
最后一個問題是如何將視圖連接到模型? 這里的關(guān)鍵問題是各個組件之間的松耦合。 松散耦合使您可以使用另一種技術(shù)替換應(yīng)用程序的任何層,而又不影響其他層(或進(jìn)行有限的更改)。 有許多解決方案,例如:
- Java 6 ServiceLoader
- NetBeans查找API
- Dukescript(MVVM) 。 將DukeScript用于客戶端-服務(wù)器應(yīng)用程序的好處之一是代碼重用。 您可以在客戶端和服務(wù)器上使用相同的模型類。 這是映射JPA和Dukescript的教程 。
4.構(gòu)建我們的應(yīng)用程序
我不會在這里探索所有這些技術(shù)。 請隨意查看本文結(jié)尾處的參考。
在本文中,我們將看到如何使用模型的JPA和控制器的NetBeans Lookup API來構(gòu)建MVC GIS應(yīng)用程序。 在以后的文章中,我們將看到替代技術(shù),例如用Speedment替換JPA和用Dukescript用MVVM替換MVC。
4.1我們的觀點(diǎn)
在之前的文章中,我們已經(jīng)創(chuàng)建了一個OpenMap應(yīng)用程序。 讓我們回顧一下并重構(gòu)它。
我們的OpenMap應(yīng)用程序包含以下文件層次結(jié)構(gòu):
- openmap
- DMSCoordInfoFormatter
- DemoLayer
- MyDrawingTool
- OpenMap
- openmap.properties
讓我們這樣重構(gòu)它:
- openmap
- OpenMap.java
- openmap.controller
- openmap.model
- openmap.view
- DMSCoordInfoFormatter.java
- DemoLayer.java
- MyDrawingTool.java
- openmap.properties
也不要忘記更新openmap.properties的路徑。 上面的包結(jié)構(gòu)描述了Model-View-Controller(MVC)設(shè)計模式。
在NetBeans中(而且在其他IDE),你可以很容易地應(yīng)用重構(gòu)(如移動一個文件或文件夾到另一個文件夾或重命名文件/文件夾)上的文件/文件夾,右鍵單擊并選擇子菜單重構(gòu)下一個重構(gòu)。
添加一個城市圖層(來自O(shè)penMap的原始o(jì)penmap.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復(fù)制到resources/map 。
再次運(yùn)行該應(yīng)用程序以查看新層。
4.2我們的數(shù)據(jù)庫架構(gòu)
我們的數(shù)據(jù)庫架構(gòu)顯示在下面的清單中。 它主要由一個Supplier表組成。 我們想在地圖上將我們的供應(yīng)商顯示為GeoPoint 。
以下是在NetBeans中創(chuàng)建SQLite數(shù)據(jù)庫的步驟(您可以選擇任何喜歡的DBMS):
清單2 –供應(yīng)商表
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) )驗證新表是否已創(chuàng)建并列在“ 表”下。 您可以將相同的樣本數(shù)據(jù)添加到表中:
清單3 –樣本數(shù)據(jù)
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);在繼續(xù)操作之前,請不要忘記斷開與數(shù)據(jù)庫的連接。 由于SQLite是獨(dú)立數(shù)據(jù)庫,因此它主要是文件系統(tǒng)中的文件。 一次只能有一個應(yīng)用程序可以訪問它。 如果從“ 服務(wù)”選項卡連接到它,并嘗試同時從OpenMap應(yīng)用程序訪問它,則會出現(xiàn)數(shù)據(jù)庫被鎖定的異常。 對于諸如Postgresql或MS SQL Server之類的“實(shí)際” DBMS,情況并非如此,它們可以并發(fā)訪問。
4.3建立模型
讓我們根據(jù)以上模式構(gòu)建一個JPA模型。 NetBeans提供了很好的JPA支持:
向?qū)г趏penmap.model下創(chuàng)建了一個新類Suppliers和一個不必要的SupplierPK 。 它還創(chuàng)建了文件META-INF/persistence.xml ,其中包含有關(guān)數(shù)據(jù)庫的連接信息:
清單4 – persistence.xml
由于包含主鍵定義的架構(gòu)(請參見清單2),向?qū)橹麈I生成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還可輕松為模型生成控制器。
該向?qū)?chuàng)建了SupplierJpaController以及3個異常文件。 現(xiàn)在,視圖可以訪問此控制器以便對模型執(zhí)行操作。
清單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();}} }在此示例中,我們只有一個域?qū)ο骃upplier但在實(shí)際應(yīng)用中,您將有許多域?qū)ο蟆?一個常見的解決方案是創(chuàng)建一個Facade ,該Facade僅將視圖所需的方法公開,而將其余的隱藏。 在下面的內(nèi)容中,我們展示如何使用NetBeans Lookup API創(chuàng)建松散耦合的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左側(cè)的blob,然后選擇“ 實(shí)現(xiàn)接口” 。 如下表所示修改DBManager實(shí)現(xiàn),以將其轉(zhuǎn)換為服務(wù)提供者:
清單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添加到默認(rèn)查找中。 在下一部分中,我們將從視圖中了解如何訪問DBManager 。 為了使其工作,如果NetBeans沒有自動添加依賴項,則需要向org-openide-util-lookup.jar添加一個依賴項。
但是什么是查找? 查找是將類對象作為鍵并將這些類對象的實(shí)例集作為值的映射,即
Lookup = Map<Class, Set<Class>> ,例如Map<String, Set<String>>或Map<Provider, Set<Provider>> 。 NetBeans提供了許多訪問默認(rèn)查找的方法:
Provider provider = Lookup.getDefault().lookup(Provider.class); provider.aMethod();或如果您具有Provider多個實(shí)現(xiàn):
Collection providers = Lookup.getDefault().lookupAll(Provider.class); for (Provider provider : providers) { ... }從上面的代碼示例中可以看到,客戶端不知道客戶端使用哪種實(shí)現(xiàn)。 它只知道接口。 松耦合!
上面的代碼將服務(wù)添加到默認(rèn)查找中。 客戶端在接口的默認(rèn)查找中查找。 默認(rèn)查找是一個評估META-INF/services文件夾中的服務(wù)聲明的查找。 可通過Lookup.getDefault()方法調(diào)用它。 如果您對更多細(xì)節(jié)感興趣,則Netbeans在build/classes/META-INF/services/文件夾內(nèi)創(chuàng)建一個文本文件package.Provider ,其中包含實(shí)現(xiàn)類的完全限定名稱。 通過以這種方式請求服務(wù)接口,您會收到在META-INF/services文件夾中注冊的實(shí)現(xiàn)類的實(shí)例。
當(dāng)然,在NetBeans胖客戶端平臺中還有其他查找,而不是默認(rèn)查找,但是這些不在本文討論范圍之內(nèi)。
4.5建立您的視圖
最后,我們需要創(chuàng)建一個新圖層,該圖層將在地圖上顯示我們的供應(yīng)商,并在openmap.properties中聲明它,以便將其添加到地圖中。 如果您遵循以前的教程,那么現(xiàn)在應(yīng)該很容易做到。 讓我們逐步構(gòu)建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()方法中,我們使用默認(rèn)的 Lookup從DBManager檢索Supplier的列表。 我們遍歷所有Supplier并從其中的每一個中創(chuàng)建一個OMPoint 。 通過設(shè)置屬性O(shè)MGraphicConstants.LABEL創(chuàng)建點(diǎn)的標(biāo)簽。 通過使用鍵"Lookup Object"將支持的Supplier添加到OMPoint的屬性映射中,我們可以實(shí)現(xiàn)一個技巧。 我們稍后將需要它。 (不要將其與NetBeans的Lookup混淆;我們只是以類似的方式命名它,以表明它類似于NetBeans的Lookup但與它無關(guān);您可以將其命名為其他名稱)。 最后,將每個點(diǎn)添加到返回的OMGraphicList 。
單擊“ 工具”按鈕時,“ 圖層”對話框?qū)⒄{(diào)用getGUI()方法(請參見下圖):
圖2 –供應(yīng)商層
該方法創(chuàng)建一個帶有復(fù)選框的新面板,以顯示/隱藏該圖層的標(biāo)簽。 Supplier的標(biāo)簽是從OMPoint屬性圖檢索的Supplier名稱,以便為其設(shè)置OMGraphicConstants.LABEL屬性。 選中復(fù)選框后,標(biāo)簽可見,否則屬性被刪除。 圖層被repaint()編輯。 不幸的是, repaint()不能100%起作用。 您需要縮放地圖或調(diào)整地圖大小,以便再次顯示標(biāo)簽。
接下來,當(dāng)我們右鍵單擊Supplier以顯示其屬性時,我們想顯示一個彈出菜單。 從上一教程中,您知道我們需要重寫getItemsForOMGraphicMenu()方法。 如果要在右鍵單擊圖層上的任何位置時顯示彈出菜單,請重寫以下方法getItemsForMapMenu() :
清單10 – SupplierLayer.java(續(xù))
@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; }我們?nèi)鄙亠@示數(shù)據(jù)的表格。 我們將在此處進(jìn)行快速介紹,以向您展示另一個NetBeans向?qū)?#xff0c;但是您可以使用Matisse或您所知道的自由構(gòu)建自己的對話框。
該向?qū)б褎?chuàng)建一個主/明細(xì)表格,但是我們只需要明細(xì)部分。 如下圖所示對其進(jìn)行自定義。
圖3 –供應(yīng)商屬性對話框
選擇每個文本字段和“ 刪除”按鈕,單擊“ 綁定” (“ 屬性”區(qū)域),然后從已enabled屬性和text屬性中刪除對主表的任何引用。
圖4 –刪除綁定
將類型文本字段更改為組合框,因為type僅限制為enum值'GROSS'和'RETAIL' 。
在底部添加標(biāo)簽( lblStatus )。 使它不透明。 與數(shù)據(jù)庫的事務(wù)處理成功后,將顯示為綠色,否則顯示為紅色。 這對用戶是一個很好的反饋,以確保他/她的修改得以保留。
該對話框與實(shí)體管理器耦合以檢索要顯示的數(shù)據(jù),但這通常是一個不好的設(shè)計。 刪除對實(shí)體管理器和主表的所有引用,并將其轉(zhuǎn)換為JDialog 。 為了避免java.lang.IllegalArgumentException: GroupLayout can only be used with one Container at a time ,請將所有組件添加到JPanel
源代碼應(yīng)如下所示:
清單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來處理數(shù)據(jù)。 我們需要向其中添加以下新方法:
清單12 – IDBManager.java
public interface IDBManager {List getSuppliers();void delete(Supplier supplier) throws Exception;void save(Supplier supplier) throws Exception; } 及其實(shí)現(xiàn):
清單13 – DBManager.java
現(xiàn)在可以將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;}最后,
圖5 –供應(yīng)商屬性對話框
做得好! 您已經(jīng)構(gòu)建了大多數(shù)功能,并且您的設(shè)計允許您進(jìn)行修改而無需更改所有層。
這是您可以嘗試的TODO列表:
- 將上述對話框中的緯度/經(jīng)度文本字段設(shè)置為易于閱讀的格式,即xxoyy'zzz"N|S , xxxoyy'zzz"E|W
- 提示 :使用我們在上一篇文章中顯示的DMSCoordInfoFormatter格式化緯度/經(jīng)度雙DMSCoordInfoFormatter值;
- 您可以為緯度和經(jīng)度的小時/分鐘/秒使用單獨(dú)的文本字段,以便用戶可以輕松鍵入新值而不會弄亂特殊字符;
- 保存更改后,確保OMPoint顯示在其新位置; 你需要添加PropertyChangeListener在SupplierLayer監(jiān)聽在改變Supplier :
清單15 – SupplierLayer.java(續(xù))
為使以上各項起作用,您需要將Supplier轉(zhuǎn)變?yōu)榭捎^察的:
清單16 – Supplier.java(續(xù))
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);}- 添加拖動功能,即用戶應(yīng)該能夠?qū)⒌貓D上的供應(yīng)商拖動到新位置
- 使用上一篇文章中的提示;
- 實(shí)現(xiàn)DrawingToolRequestor接口
- 在findAndInit()定義并初始化DrawingTool的實(shí)例
- 重寫select()和drawingComplete()方法
- 添加創(chuàng)建新的供應(yīng)商的功能(方法getItemsForMapMenu()在SupplierLayer )。 應(yīng)該顯示SuppliersPropertiesDialogBox ,其中已經(jīng)填充了用戶在地圖上單擊的坐標(biāo)的緯度/經(jīng)度字段; 然后用戶應(yīng)填寫其他字段,并將新的供應(yīng)商添加到數(shù)據(jù)庫中
- 單擊“ Drawing Tool Launcher按鈕時,您可以在圖層上添加許多類型的圖形,而這可能不是您想要的。 由于我們希望我們的Supplier層僅顯示OMPoint , ompointloader像上一篇文章中所做的那樣,修改openmap.components僅omdrawingtool和ompointloader
- 您可能會遇到的另一個問題是,當(dāng)右鍵單擊OMPoint ,將顯示與通過getItemsForOMGraphicMenu()創(chuàng)建的彈出菜單不同的彈出菜單。 com.bbn.openmap.tools.drawing.OMDrawingTool包含dt.setBehaviorMask(OMDrawingTool.QUICK_CHANGE_BEHAVIOR_MASK) 。 OMDrawingTool定義了許多行為掩碼,如上一教程中所述。 作為解決方法,我們創(chuàng)建了自己的OMDrawingTool 。
結(jié)論
在本教程中,我們創(chuàng)建了一個三層獨(dú)立應(yīng)用程序,該應(yīng)用程序使用JPA從關(guān)系數(shù)據(jù)庫中檢索數(shù)據(jù),并將其顯示為OpenMap的層。 我們看到了如何使用NetBeans Lookup API將視圖與控制器松散耦合。
您應(yīng)該已經(jīng)對如何開發(fā)此類應(yīng)用程序有所了解,但是請不要在實(shí)際的特別是關(guān)鍵的應(yīng)用程序中使用此代碼。 代碼是錯誤的,既不高效也不是線程安全的(例如,有關(guān)如何從不同線程中的數(shù)據(jù)庫檢索數(shù)據(jù)的信息,請參見com.bbn.openmap.layer.location.LayerLocationLayer )。
您還可以使用其他技術(shù)來替換各個層,例如:
- 用純Java 8 lambda框架替換JPA來訪問數(shù)據(jù)庫( Speedment )
- 使用DukeScript將JPA粘合到您的視圖
例如,由于您的視圖依賴于IDBManager而不是特定的實(shí)現(xiàn)(例如JPA的EntityManager ),因此它不受模型的任何更改的影響(只要Supplier和IDBManager的方法不變)。 然后,您可以將JPA替換為Speedment,而無需更改視圖。
如果時間和空間允許,我們可能會在以后的文章中進(jìn)行調(diào)查。
參考資料
翻譯自: https://www.javacodegeeks.com/2016/06/openmap-tutorial-5-3-tier-gis-application.html
pcl_openmap
總結(jié)
以上是生活随笔為你收集整理的pcl_openmap_OpenMap教程5 – 3层GIS应用程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (linux导出mysql)
- 下一篇: jdk170不支持注释_JDK 9 @不