Introduction
The goal of this project is to make maps development for your project as simple as possible. The implementation is based on the JS framework OpenLayers. Client’s look and feel is closed to Google maps. Also the clients don’t need to install any extra components for their web browsers. I even was able to test it on Apple IPhone and it worked correctly.
Every feature of the map is a component. Component consists of a java bean and a velocity template. Map rendering is based on velocity templates. Every template produces separate java script class at the web page during the page rendering time. Initialization parameters as long as current available templates are given through Spring framework.
Interaction of JS classes is preformed through event manager and there is no direct reference form one JS class to the other. This approach gives you a possibility to have only those components at the page you currently need.
Velocity – based javascripts
Map java scripts are individual components (classes) and they are not coupled. Every component must inherit java script class:
MapControl = OpenLayers.Class.create();
MapControl.prototype= {
map: null,
events: null,
init: function(map, events){
this.map = map;
this.events = events;
},
CLASS_NAME: "MapControl"
}
As you can see it has only one method: init where there are two parameters: map and events. Map is a façade to OpenLayers map and events are delivered by event manager. Event manager is an object which notifies other components about some activities for the current control (Observer pattern). By subscribing to events you can intercept messages thrown by other components. Basically it performs interoperability inside the framework and solves the problems of creating decoupled components.
Notification example:
this.events.fireEvent('changeWMSLayer', {id:layer.id, name:layer.name, visible: layer.checked} );
Subscribing example:
this.events.attachEventHandler( 'changeWMSLayer', this.changeWMSLayer.bind(this));
When the whole java script is loaded the function init will be called for every single component and components should perform initialization.
You are required to provide some data for components form you java back end. And its done by using the elegance of Spring framework. Let’s have a look at some example where we going to use Zoom To component:
First we need to create the java bean:
public class ZoomToControl extends MapAbstractControl {
private Extent zoomToExtent;
@VelocityParam(name = "zoomToExtent")
public Extent getZoomToExtent() {
return zoomToExtent;
}
public void setZoomToExtent(Extent zoomToExtent) {
this.zoomToExtent = zoomToExtent;
}
}
We are extending basic control MapAbstractControl which has the common properties.
Then note that we use annotation VelocityParam(name = "zoomToExtent"). In the velocity template the variable with the name zoomToExtent corresponds the getter of the property zoomToExtent.
Then we need to create velocity template where our current JS component will be generated from when the page loads:
${javaScriptClassName} = OpenLayers.Class.create();
${javaScriptClassName}.prototype = OpenLayers.Class.inherit ( MapControl, {
// from map
init: function(map, events){
MapControl.prototype.init.apply(this, arguments);
#if (${zoomToExtent})
map.zoomToExtent(new OpenLayers.Bounds($zoomToExtent.minX,$zoomToExtent.minY,$zoomToExtent.maxX,$zoomToExtent.maxY));
#end
#if (${zoomToPoint})
## to do
#end
},
CLASS_NAME: "$javaScriptClassName"
});
You see that we using some velocity parameters which will substituted by constants during rendering time. Also some primitive velocity logic is used.
The reason I use velocity is that you have a feeling that you are working with JS itself, while it is still a template.
Also all the java variables are defined in velocity by annotations as mentioned above.
And the last step we need to create a Spring configuration:
Extent POJO:
<bean id="extentWMS" class="org.emaps.Extent">
<property name="maxY" value="88.112" />
<property name="minY" value="10.14911608" />
<property name="maxX" value="-49.1132" />
<property name="minX" value="-205.075" />
</bean>
ZoomTo bean:
<bean id="zoomToControl" class="org.emaps.control.ZoomToControl">
<property name="javaScriptClassName" value="ZoomToControl" />
<property name="tempateName"
value="/WEB-INF/map-templates/zoomto.vm" />
<property name="zoomToExtent" ref="extentWMS" />
</bean>
Here are the following properties:
javaScriptClassName – class name as it appears on the html page
tempateName - velocity template
zoomToExtent - our zoom property
Then
We need to register it in the main controller in order it appears on the page its method init is called.
<bean id="baseWMSMap" class="org.emaps.GisMap">
<property name="javaScriptClassName" value="GisMap" />
<property name="extent" ref="extentWMS" />
<property name="tileSize" ref="tileSizeWMS" />
<property name="maxResolution" value="0.15228550153247" />
<property name="units" value="degree" />
<property name="mapDiv" value="mapDiv" />
<property name="numZoomLevels" value="12" />
<property name="childernControls">
<list>
<ref bean="waitControl" />
<ref bean="wmsLayer" />
<ref bean="ajaxDispatcherControl" />
<ref bean="navigationControl" />
<ref bean="vectorFeaturesControl" />
<ref bean="layerSwitcherControl" />
<ref bean="zoomToControl" />
</list>
</property>
<property name="tempateName"
value="/WEB-INF/map-templates/gismap.vm" />
</bean>
As you can see we are registering the components we are going to use only. The main controller will generate java script at the page for every single control and it will take care about brining java beans values to the page through velocity. After page is loaded the main controller initialized the event manager and Open layers map and then call method init for all the registered controls. At this step controls should initialize themselves and register to the events they are going to listen.
Java script event model
As you can see the control doesn’t know about the existence the others. This is done in order to use only those controls you need and simplify you map development.
But they definitely should communicate and event manager solves this issue.
Let’s have a look at the change layer mechanism.
In order to change a layer on the map we need to have at least two controls:
- layers switcher (checkbox)
- layers itself (map image)
So when the user toggles to box we notify the event manager by firing an event:
this.events.fireEvent('changeWMSLayer', {id:layer.id,
name:layer.name, visible: layer.checked} );
None, one or many other controls may subscribe to this message. One of the subscriber is layer control:
this.events.attachEventHandler( 'changeWMSLayer', this.changeWMSLayer.bind(this));
After message has been thrown is going to be delivered here the java script method changeWMSLayer of the MultiTileLayer control will be executed:
changeWMSLayer : function(layer) {
for (var i=0;i<this.layerArray.length;i++) {
var l =this.layerArray[i];
if (layer.id == l.id) {
l.visible = layer.visible;
this.wmsLayer.params.LAYERS=this.getWmsServerUrl();
this.wmsLayer.redraw();
break;
}
}
},
As
you can see from above everything are controls and it doesn’t matter if they
perform visual activity or showing something or just working like mediators or
controllers. One of these is
I tried to simplify java script communication to java and made a solution where you send java script object and you have java object at the back end and vise versa.
First
you need to create java bean where you
public class AjaxVectorGeometry {
private AjaxGeometryXY[] xy;
private String shapeName;
@AjaxParam(name = "xy")
public AjaxGeometryXY[] getXy() {
return xy;
}
@AjaxParam(name = "xy")
public void setXy(AjaxGeometryXY[] xy) {
this.xy = xy;
}
@AjaxParam (name ="geometry")
public String getShapeName() {
return shapeName;
}
@AjaxParam (name ="geometry")
public void setShapeName(String shapeName) {
this.shapeName = shapeName;
}
}
The annotations mach java script variable names.
Then from any place of our page or from any other component you need to fire the event:
myMap.events.fireEvent('ajaxVectorFeatures',{vectorFeatures:shapes});
shapes is plain java script object and it consists of the flowing fields:
- shapes.geometry - String
- shapes.xy - Array of XY
As you can see java script has these properties by annotations, e.g. @AjaxParam (name ="geometry")
If the request is synchronous event manager will notify a subscriber by thrown the event:
this.myMap.events.attachEventHandler( 'ajaxVectorFeaturesOk', function(d) {
this.myMap.events.fireEvent('hideWaitIcon');
var table = new Element('table',{border:'2px solid black'});
var tbody = new Element('tbody');
table.appendChild(tbody);
var tr = new Element('tr');
tbody.appendChild(tr);
var td = new Element('td');
td.innerText = d.name;
tr.appendChild(td);
td = new Element('td');
td.innerText = d.name1;
tr.appendChild(td);
$('vectorResilts').appendChild(table);
}.bind(this));
this.myMap.events.attachEventHandler( 'ajaxVectorFeaturesErr', function(d) {
this.myMap.events.fireEvent('hideWaitIcon');
alert(Object.toJSON(d));
}.bind(this));
Then let’s have a look we are defining these events names as along as class names. Of course its Spring again:
<bean id="ajaxVectorFeatures"
class="org.emaps.control.AjaxDispatcherEvent">
<property name="eventName" value="ajaxVectorFeatures" />
<property name="className"
value="org.emaps.ajax.AjaxVectorFeatures" />
<property name="methodName" value="ajaxCall" />
<property name="ajaxSuccEvent" value="ajaxVectorFeaturesOk" />
<property name="ajaxErrEvent" value="ajaxVectorFeaturesErr" />
</bean>
So here you can see that event name we fired is: ajaxVectorFeatures , also after that when you have some data back from the java side by notifying either ajaxVectorFeaturesOk or ajaxVectorFeaturesErr event.
Also
Also
please note that we don’t need to serialize
When all the properties are set up the method ajaxCall will be called and you may return java object and at the page it appears and java script object automatically.
You
may have as many beans as you want but you need to register them in the
<bean id="ajaxDispatcherControl"
class="org.emaps.control.AjaxDispatcherControl">
<property name="javaScriptClassName"
value="AjaxDispatcherControl" />
<property name="dispatcherUrl" value="/mapAjaxDispatcher" />
<property name="tempateName"
value="/WEB-INF/map-templates/mapajaxdispatcher.vm" />
<property name="events">
<list>
<ref bean="testEvent" />
<ref bean="ajaxVectorFeatures" />
</list>
</property>
</bean>
As you can see all the framework is event driven. This will allow the developer to create pages having some complicated java scripts and their back – end without worrying about that complexity. The application is messaging to the dispatcher to process the data. Also all the JSON mapping is annotation based and it will increase maintainability.
To get the war file go to the
http://sourceforge.net/projects/enterprisemaps/