package demo.controller; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Vector; import org.zkoss.gmaps.Gmaps; import org.zkoss.gmaps.Gmarker; import org.zkoss.gmaps.Gpolyline; import org.zkoss.gmaps.MapsModelList; import org.zkoss.gmaps.event.MapDropEvent; import org.zkoss.gmaps.event.MapMouseEvent; import org.zkoss.gmaps.event.MapMoveEvent; import org.zkoss.gmaps.event.MarkerDropEvent; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.ForwardEvent; import org.zkoss.zk.ui.event.GenericEventListener; import org.zkoss.zk.ui.util.GenericAutowireComposer; import org.zkoss.zul.Image; import org.zkoss.zul.Textbox; import edu.emory.mathcs.backport.java.util.LinkedList; public class MapViewControl extends GenericAutowireComposer { /* * Here we hold a few local references to a variety of structrus. * Their purpose was to make sure proper control is achieved. * @author Robert Lee * @email robertlee@zkoss.org * */ protected Gmaps theMap; protected static MapsModelList myMapModel; protected static int counter; protected Textbox nodeName; protected LinkedList linkBuffer; // embedded cheap queue for linkage. protected boolean[][] conTable; protected static int gmarkCounter; // universal gmarker counter protected HashMap indexMap; private final int LINK_DIMENSION = 1000; /* For initialisation. */ public void doAfterCompose(Component comp) throws Exception { super.doAfterCompose(comp); counter = 0; linkBuffer = new LinkedList(); conTable = new boolean[LINK_DIMENSION][LINK_DIMENSION];// should be enough gmarkCounter = 0; indexMap = new HashMap(); } /* custom event listener for onMarkerDrop event, we put it here for demo purpose. */ public class MyEventListener extends GenericEventListener { public void onMarkerDrop(Event evt) { MarkerDropEvent mde = (MarkerDropEvent) evt; linkBuffer.clear(); Gmarker theDropped = (Gmarker) mde.getTarget(); redrawFromMap(theDropped); } public final MapViewControl mvc; public MyEventListener() { super(); mvc = MapViewControl.this; } } /* * @param ForwardEvent forwarded from zul file mapping(forward="..."). * The reason we are using Gmarker/Gpolyline directly without any object models * is because this was done for a fast prototype. If we are going to configure * specific object structures or differentiating different type of nodes we shall * use abstract classes extending Gmarker, and later extend the different node types * to the abstract class, achieving the flexibility of java programming. * purpose: Handles events triggered by droppings on map. */ public void onMapDrop(ForwardEvent e) { //Get the original event from the forwarded event. MapDropEvent mde = (MapDropEvent) e.getOrigin(); //get the model as a List, as in this design we decided it should be a list. if (theMap.getModel() != null) myMapModel = (MapsModelList) theMap.getModel(); else myMapModel = new MapsModelList(); theMap.setModel(myMapModel); //If the component dropped were an image, create a gmarker. if (mde.getDragged() instanceof Image) { Image draggedImage = (Image) mde.getDragged(); // Do chores for gmarker. String imageName = draggedImage.getSrc(); Gmarker gmark = new Gmarker(imageName); gmark.setId(gmarkCounter++ + ""); // we can later differentiate the entityNames with imageName. gmark.setContent(nodeName.getValue()); gmark.setTooltiptext(nodeName.getValue()); gmark.setIconImage(imageName); gmark.setDraggingEnabled(true); // Display information from this gmarker when its firstly created. //gmark.setOpen(true); gmark.setLng(mde.getLng()); gmark.setLat(mde.getLat()); String original = nodeName.getValue(); if (original.endsWith(counter + "")) { original = original.replace(counter + "", ++counter + ""); } nodeName.setValue(original); // registers gmark to event listener, so it can be notified by renderer. (new MyEventListener()).bindComponent(gmark); myMapModel.add(gmark); } } /* Handles map move event - currently do nothing cause we can't think of any features...*/ public void onMapMove(ForwardEvent e) { MapMoveEvent mme = (MapMoveEvent) e.getOrigin(); } /* @purpose: process map mouse event, handles possibilities * for linkage creation or termination. * @precondition: There are at least one Gmarks in theMap. * We are using a linkedList to monitor the selected gmarkers, * when one is selected we leave it in the list * when the second gmarker enters the list we build a node from * start(first) to stop(candidate). * Here we also employed a 2D boolean array to register links * which have been connected. This way we will can be safe to check * the existance of the link and avoid creating duplicated links between * two Gmarkers. * */ public void onMapClick(ForwardEvent e) { MapMouseEvent mme = (MapMouseEvent) e.getOrigin(); if (mme.getGmarker() != null) { Gmarker candidate = mme.getGmarker(); // draw a Gpolyline if there are two Gmarkers in the queue. linkBuffer.add(candidate); if (linkBuffer.size() < 2) { //do nothing } else if (linkBuffer.size() == 2) { if (linkBuffer.get(0) != linkBuffer.get(1)) { Gmarker first = (Gmarker) linkBuffer.getFirst(); // check for repetition. Currently we only allow one if (!linkExists(first, candidate)) { linkRegister(first, candidate); /** * create a link, a gployline, * link registered on conTable, * we declare the style as constant for now, * leave it to be implemented as dynamic later. */ Gpolyline gpl = new Gpolyline(); gpl.setColor("#44FF55"); gpl.setOpacity(50); gpl.setWeight(7); gpl.addPoint(first.getLat(), first.getLng(), 3); gpl.addPoint(candidate.getLat(), candidate.getLng(), 3); linkBuffer.removeFirst(); /** * all we have to do is to create the gPolyline and throw it * to the model, as it will handle all the rendering needs. * If we were still using the 2.0_8 version we would need to worry about * the structure tree of theMap ourselves. Glory to ZK!!! */ myMapModel.add(gpl); /* * To make sure the link is properly registered with the nodes, * (so we can know which link to redraw/remove later on) * we needed to bind them from both perspective. * */ bind(first, gpl); bind(candidate, gpl); } else { // move the canditate to first in buffer linkBuffer.removeFirst(); } } else { // if clicked on same one, removeFirst; linkBuffer.removeFirst(); } } else { // shouldnt occur. linkBuffer.clear(); } } else { // if clicked on non gmaker, restart linkage. linkBuffer.clear(); } } /** * Utility methods registering links existance between nodes, * by using the 2D boolean table we can cut search time to O(n), * yet sacrificing a little heap space. * @param g1 * @param g2 */ public void linkRegister(Gmarker g1, Gmarker g2) { int first = new Integer(g1.getId()).intValue(); int second = new Integer(g2.getId()).intValue(); // currently prohibit two way linkage. conTable[first][second] = true; conTable[second][first] = true; } /*** * Utility method making sure the link exists between two nodes. * @param g1 * @param g2 * @return if link between two nodes exists. */ public boolean linkExists(Gmarker g1, Gmarker g2) { // check from both sides. int first = new Integer(g1.getId()).intValue(); int second = new Integer(g2.getId()).intValue(); return (conTable[first][second] || conTable[second][first]); } /** * * @param g1 * @param g2 * @return a unique name for anyLinks created. */ public String getLinkName(Gmarker g1, Gmarker g2) { int first = new Integer(g1.getId()).intValue(); int second = new Integer(g2.getId()).intValue(); // from first to second return first + ":" + second; } /** * Register a Gmarker with a GPolyline, we use HashSet to hold * the data, saving memory and search time. * @param g1 * @param gplIndex */ public void bind(Gmarker g1, Gpolyline gplIndex) { HashSet gpSet = (HashSet) indexMap.get(g1); if (gpSet != null) { // add to Set and reinsert. gpSet.add(gplIndex); indexMap.put(g1, gpSet); } else { gpSet = new HashSet(); gpSet.add(gplIndex); indexMap.put(g1, gpSet); } } //Remove the registered link of this Gmarker. public void removeFromMap(Gmarker g1) { HashSet gpSet = (HashSet) indexMap.get(g1); if (gpSet != null) { myMapModel.removeAll(gpSet); } } //Remove and redraw. Using the hardcoded style. We can dynamicallise this later. public void redrawFromMap(Gmarker g1) { HashSet gpSet = (HashSet) indexMap.get(g1); Vector matchedGmarker = retrieve(g1); if (gpSet != null) { myMapModel.removeAll(gpSet); } /* redraw them here */ for (Gmarker g : matchedGmarker) { Gpolyline gpl = new Gpolyline(); // gpl has an index equivalent to myMapModel.size() // these three are hardcoded for now, we can add var later gpl.setColor("#44FF55"); gpl.setOpacity(50); gpl.setWeight(7); gpl.addPoint(g1.getLat(), g1.getLng(), 3); gpl.addPoint(g.getLat(), g.getLng(), 3); myMapModel.add(gpl); bind(g,gpl); bind(g1,gpl); } } // we firstly check the connection table for linkage index, // then search the MapsModel (as a list) for gmarkers' reference // in order to remove/redraw the links associated. // heavy O(n^2) compare. BECAREFUL! public Vector retrieve(Gmarker key) { // get id of key, lookup conTable, get list of integer, then..get from model? int targetId = new Integer(key.getId()).intValue(); Vector results = new Vector(); Vector gmarkerResult = new Vector(); // search the 'from' dimension for (int i = 0; i < conTable[targetId].length; i++) { if (i == targetId) continue; // if connection exists. if (conTable[targetId][i] == true) { results.add(new Integer(i)); } } //In our list, each gmarker are represented by integer. //This is another good thing of using model of gmap, because everything //are viewed as a list instead of trees. List innerList = myMapModel.getInnerList(); for (int i = 0; i < innerList.size(); i++) { if (innerList.get(i) instanceof Gmarker) { Gmarker gTemp = (Gmarker) innerList.get(i); for (Integer integer : results) { Integer intTemp = new Integer(gTemp.getId()); if (intTemp.intValue() == integer.intValue()) { gmarkerResult.add(gTemp); } } } } return gmarkerResult; } // @TODO remove selected link if required. // @TODO load and save custom info structure to database - will need to use Abstract class for nodes. }