pcl_openmap_OpenMap教程5 – 3层GIS应用程序
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):
清單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應用程序訪問它,則會出現數據庫被鎖定的異常。 對于諸如Postgresql或MS SQL Server之類的“實際” DBMS,情況并非如此,它們可以并發訪問。
4.3建立模型
讓我們根據以上模式構建一個JPA模型。 NetBeans提供了很好的JPA支持:
向導在openmap.model下創建了一個新類Suppliers和一個不必要的SupplierPK 。 它還創建了文件META-INF/persistence.xml ,其中包含有關數據庫的連接信息:
清單4 – persistence.xml
由于包含主鍵定義的架構(請參見清單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還可輕松為模型生成控制器。
該向導創建了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添加一個依賴項。
但是什么是查找? 查找是將類對象作為鍵并將這些類對象的實例集作為值的映射,即
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()方法中,我們使用默認的 Lookup從DBManager檢索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或您所知道的自由構建自己的對話框。
該向導已創建一個主/明細表格,但是我們只需要明細部分。 如下圖所示對其進行自定義。
圖3 –供應商屬性對話框
選擇每個文本字段和“ 刪除”按鈕,單擊“ 綁定” (“ 屬性”區域),然后從已enabled屬性和text屬性中刪除對主表的任何引用。
圖4 –刪除綁定
將類型文本字段更改為組合框,因為type僅限制為enum值'GROSS'和'RETAIL' 。
在底部添加標簽( lblStatus )。 使它不透明。 與數據庫的事務處理成功后,將顯示為綠色,否則顯示為紅色。 這對用戶是一個很好的反饋,以確保他/她的修改得以保留。
該對話框與實體管理器耦合以檢索要顯示的數據,但這通常是一個不好的設計。 刪除對實體管理器和主表的所有引用,并將其轉換為JDialog 。 為了避免java.lang.IllegalArgumentException: GroupLayout can only be used with one Container at a time ,請將所有組件添加到JPanel
源代碼應如下所示:
清單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
現在可以將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 –供應商屬性對話框
做得好! 您已經構建了大多數功能,并且您的設計允許您進行修改而無需更改所有層。
這是您可以嘗試的TODO列表:
- 將上述對話框中的緯度/經度文本字段設置為易于閱讀的格式,即xxoyy'zzz"N|S , xxxoyy'zzz"E|W
- 提示 :使用我們在上一篇文章中顯示的DMSCoordInfoFormatter格式化緯度/經度雙DMSCoordInfoFormatter值;
- 您可以為緯度和經度的小時/分鐘/秒使用單獨的文本字段,以便用戶可以輕松鍵入新值而不會弄亂特殊字符;
- 保存更改后,確保OMPoint顯示在其新位置; 你需要添加PropertyChangeListener在SupplierLayer監聽在改變Supplier :
清單15 – SupplierLayer.java(續)
為使以上各項起作用,您需要將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層僅顯示OMPoint , ompointloader像上一篇文章中所做的那樣,修改openmap.components僅omdrawingtool和ompointloader
- 您可能會遇到的另一個問題是,當右鍵單擊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 ),因此它不受模型的任何更改的影響(只要Supplier和IDBManager的方法不變)。 然后,您可以將JPA替換為Speedment,而無需更改視圖。
如果時間和空間允許,我們可能會在以后的文章中進行調查。
參考資料
翻譯自: https://www.javacodegeeks.com/2016/06/openmap-tutorial-5-3-tier-gis-application.html
pcl_openmap
總結
以上是生活随笔為你收集整理的pcl_openmap_OpenMap教程5 – 3层GIS应用程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: (linux导出mysql)
- 下一篇: 个人网络会被ddos攻击吗(个人网络会被