Great thanks to Nathan Thomas, Software Developer who helped me in
writing this article.This article will show you how easy it is to setup interactive maps for your web page; including multiple layers and interactivity between your view for your web page and your backend business logic.
When you reach a point that it would be nice to have a map at web page you always have a choice of what to use and what to expect from the API. Also people may have a need for backend logic for their maps. So let's look at some common implementations and their ups and downs.
Common problems when creating GIS application:1. Presentation:
Of course you would like to implement your maps in the best way
possible. Using rich UI clients like applets, flash, activex will give
you pretty much all you need, but once you implement your maps in this
way, people will have to install these components as add-ons to their
web browser. This could be inconvenient for those people who don't have
permissions to install extra components to their computers or don't want
to install them. Thus I'm trying to say that I believe javascript maps
is the very reasonable choice. Basically once the user goes to your web
page he/she sees maps immediately and no other activities or
installation required. I think Google maps have proved this approach.
2. Initializing parameters:
When you want to render the javascript map you are supposed to provide
some initialization parameters that setup the basic look and feel of the
map. One way is to have some beans and get the values from them when the
page is being rendered. But if your tending to use maps in different
pages you need to copy and paste some extra javascript and provide some
beans for the new page. This approach however does not reuse existing
components as a good application should.
So what we need to have is a map template and reuse this template across
different pages of the application. The map template should be
in-between the web page rendering and the javascript, using template
technologies such as Apache Velocity project helps a lot to solve the
problem of reusing you javascript components.
3. Communication:
There is only one answer for javascript – Ajax
4. Fast and Reliable
As usual the maps consist of a base layer and extra layers. The base
layer is supposed to be static and it's a good practice to have them
pre-rendered and saved as a set of tiles somewhere. The extra layers
usually have some specific information, e.g. school boundaries.
I have chosen the OpenLayers framework since its easy and MVC
based. Everything in OpenLayers is based on events and all you need to
do is just subscribe to events.
I'm very excited about what the people at OpenLayers have done because
of its simplicity and convenience.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
#map {
width: 100%;
height: 100%;
border: 1px solid black;
}
</style>
<script src="OpenLayers.js"></script>
<script type="text/javascript">
<!--
var lon = 5;
var lat = 40;
var zoom = 5;
var map, layer;
function init(){
map = new OpenLayers.Map( 'map' );
layer = new OpenLayers.Layer.WMS.Untiled( "OpenLayers WMS",
"http://labs.metacarta.com/wms/vmap0", {layers: 'basic'} );
map.addLayer(layer);
map.setCenter(new OpenLayers.LonLat(lon, lat), zoom);
}
// -->
</script>
</head>
<body onload="init()">
<div id="map"></div>
</body>
</html>
In this example we using http://labs.metacarta.com/wms/vmap0
WMS Server.
The URL http://labs.metacarta.com/wms/vmap0?REQUEST=GetCapabilities&Service=WMS
will tell us all the information about this map including maximum extent
and layers.
Also it is difficult to reuse this script since we have the functions
only and map logic is not encapsulated in a class.
However since WMS does map rendering on request, it could be slow,
especially when you have a complicated map. Moreover you need to have a
WMS server.
One option to avoid of using a WMS server is to have a pre-rendered set
of images of the map, called tiles. These tiles could be rendered for
one or multiple layers and they are just pictures using standard formats
like GIF, PNG, or JPEG. Since no server API is involved in map rendering
you will achieve maximum performance on your maps.
I have prepared some tiles for the base-layer using the ArcGis cache.
Disk space is now cheap and you can pre-render huge maps on different
zoom levels to speed up the map.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<style type="text/css">
</style>
<script src="OpenLayers.js"></script>
<script src="AGS.js"></script>
<script type="text/javascript">
<!--
// create a class for future reuse
MyGisRender = OpenLayers.Class.create();
MyGisRender.prototype= {
lon: -107.0612,
lat: 38.9435,
zoom : 0,
map : null,
baseLayer: null,
initialize: function() {
},
init: function(){
this.map = this.createMap();
this.baseLayer = this.createBaseLayer();
this.map.addLayer(this.baseLayer);
this.addControls();
},
createMap: function () {
return new OpenLayers.Map( 'map', {
maxExtent: new OpenLayers.Bounds(-205.075,10.14911608,-49.1132 , 88.112),
maxResolution: 0.15228550153247,
resolutions: new Array(
0.15228550153247,
7.61427507662349E-02,
3.80713753831174E-02,
1.90356876915587E-02,
9.51784384577936E-03
), tileSize : new OpenLayers.Size(256,256),
tileOrigin: new OpenLayers.LonLat(-400,400),
units: 'degree', controls: []} );
},
createBaseLayer: function () {
return new OpenLayers.Layer.AGS( "AGS",
"http://arcgisserver/arcgiscache/phis/Layers", {layername: '_alllayers', type:'png',
tileOrigin: new OpenLayers.LonLat(-400,400)
} );
},
addControls: function () {
this.map.addControl(new OpenLayers.Control.PanZoomBar());
this.map.addControl(new OpenLayers.Control.Navigation());
this.map.addControl(new OpenLayers.Control.MousePosition());
this.map.setCenter (new OpenLayers.LonLat(this.lon, this.lat), this.zoom);
},
CLASS_NAME: "MyGisRender"
}
// global init
function init() {
var myMap = new MyGisRender();
myMap.init();
}
// -->
</script>
</head>
<body onload="init()">
An example showing OpenLayers using the MapCache of ArcGIS server 9.2
<div id="map" style="width:600px;height:500px;border: 1px solid black;"></div>
</body>
</html>
As you may see I have a class here which controls the map behavior. Using the class instead of set of functions will allow us to reuse it, especially when we need to change its behavior. For example:
MyGisRender1 = OpenLayers.Class.create();
MyGisRender1.prototype =
OpenLayers.Class.inherit(MyGisRender, {
addControls: function () {
// call inherited method
MyGisRender.prototype.addControls.apply(this);
// do our stuff
this.map.addControl(new OpenLayers.Control.MouseToolbar());
},
CLASS_NAME: "MyGisRender1"
});
There we added some new control on the map
So as you may see the big disadvantage of these examples is hard
coded parameters, like zoom, extent, etc. As I mentioned before we can
use java beans exposed to page and get values out of them. If the maps
exist in different web pages we can use the Spring Framework to send the
proper bean there. But what about if you need some simple logic when
setting up beans? For example we need to add some points to the map:
points.push(new OpenLayers.Geometry.Point(-72,42)); points.push(new OpenLayers.Geometry.Point(-71,42)); points.push(new OpenLayers.Geometry.Point(-73,42)); points.push(new OpenLayers.Geometry.Point(-70,42)); points.push(new OpenLayers.Geometry.Point(-71,41));
Extra layers
The map could contain some additional layers. We have OGS WMS
specification and if you need something standard such as a satellite
layer you can plug it in very easy. But as I said before most of the
internet WMS services are slow. OpenLayers supports WMS but only in
tile–based mode. Usually about 20-40 tiles are being requesting from WMS
service simultaneously and this is causing some performance issues. Here
I have added a new Oplenlayers WMS layer class which has one image at a
time.
createMap: function () {
return new OpenLayers.Map( 'map', {
maxExtent: new OpenLayers.Bounds(-205.075,10.14911608,-49.1132 , 88.112),
maxResolution: 0.15228550153247,
units: 'degree', controls: []} );
},
createBaseLayer: function () {
return new CRISatalliteImage( "si1",
"http://terraserver-usa.com/ogcmap6.ashx?version=1.1.1&request=GetMap&Layers=DOQ&Styles=&SRS=EPSG:4326&format=image/jpeg&Exceptions=se_xml");
},
This layer has a URL to hit the server. Depending on user interaction this URL can be changed in order to have the image you need according to WMS request specifications. The user can use a URL and point it anywhere in order to have a custom layer.
Map Features SelectionSelection of some feature on the map can be done by using OpenLayers vector drawing primitives or by creating additional selection layer.
Example: http://shpuroff.com/maps/Selection.htmlIn this example we inherit our base map to reuse all the basic properties. Then we define a new event called [sateselected]. In the method addControls we create a new vector layer, the layer will allow us to handle vector primitives like rectangles, circles, and polygons then we subscribe to the event mouse click [click]. After the mouse is pressed on the map the method selectState will be called. In this method I use the constant value STATES_ENV. This constant has the rectangle extent for the states and their basic information. When a point is selected within a state a new rectangle is created and it is added to the map. Also we trigger event [sateselected]. Then the user can register that event somewhere and handle it.
Let's have a look at the vector selection first.
MyGisRenderSelection = OpenLayers.Class.create();
MyGisRenderSelection.prototype =
OpenLayers.Class.inherit(MyGisRender, {
// define vector layer
vlayer: null,
// add our event types
EVENT_TYPES: [ 'sateselected' ],
events: null,
// constructor
initialize: function() {
// call MyGisRender constructor
MyGisRender.prototype.initialize.apply(this);
// init our events
this.events = new OpenLayers.Events(this, this.div, this.EVENT_TYPES);
},
addControls: function () {
// call inherited method
MyGisRender.prototype.addControls.apply(this);
// do our stuff
this.map.addControl(new OpenLayers.Control.MouseToolbar());
// create vector layer and add it to the map
this.vlayer = new OpenLayers.Layer.Vector("Editable");
this.map.addLayer(this.vlayer);
this.map.events.register("click", this, this.selectState);
},
selectState: function(evt) {
var latlon = this.map.getLonLatFromViewPortPx(evt.xy);
for (var i=0;i<STATES_ENV.length;i++) {
if (STATES_ENV[i].extent.containsLonLat(latlon,true)) {
var env = STATES_ENV[i].extent;
// create geometry (rectangle)
var points=[
new OpenLayers.Geometry.Point(env.left,env.top),
new OpenLayers.Geometry.Point(env.left,env.bottom),
new OpenLayers.Geometry.Point(env.right,env.bottom),
new OpenLayers.Geometry.Point(env.right,env.top),
new OpenLayers.Geometry.Point(env.left,env.top)
];
//add it to the vector layer
var ring = new OpenLayers.Geometry.LinearRing(points);
this.vlayer.addFeatures(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([ring])));
// trigger our event
this.events.triggerEvent("sateselected",STATES_ENV[i]);
break;
}
}
},
CLASS_NAME: "MyGisRenderSelection"
});
// global init
function init() {
var myMap = new MyGisRenderSelection();
myMap.init();
myMap.events.register("sateselected", this, function (selection) {
alert ('You have selected '+ selection.state);
});
}
I used a constant to define all the states, but in the real application it should be an Ajax call. A rectangle was used for this demonstration but a polygon should be used to conform with the states shape better.
Furthermore OpenLayers can add a marker to the map and show some HTML in the message window like Google does. You can find samples in the examples directory.
Vector selection looks nice and fast, however, if you have complex shapes to be selected it will be slow, because a lot of points will have to be displayed by your web browser. In that case I would recommend using server side image rendering and displaying the selection as an addition image layer.
The image selection layer has to have a transparent background. For this purpose I have implemented the class CRIOneTileLayer which you can see in the war file. It has a URL as an input where your selected features should be coded and it adds the BOX parameter in order to get the proper extent, and then it sends the request to the server. Server side implementation in this case should be done by a developer.
Vector map drawing and sending shapes back to the server
OpenLayers framework has the control allows you to draw different kind of vector shapes. Let's have a look at the example
Example: http://shpuroff.com/maps/Vector.htmlHere we are creating a vector layer as in the previous example and adding the new control EditingToolbar. This control allows us to draw shapes on the map. But then we have submit button and we'd like to see the data on the server side. I wrote the function mapFormPrepare and it allows you to modify DOM model of HTML Form. Basically it adds some parameters and these parameters will be sent to the server upon submit.
MyGisRenderVector = OpenLayers.Class.create();
MyGisRenderVector.prototype =
OpenLayers.Class.inherit(MyGisRender, {
// define vector layer
vlayer: null,
addControls: function () {
// call inherited method
MyGisRender.prototype.addControls.apply(this);
// do our stuff
this.map.addControl(new OpenLayers.Control.MouseToolbar());
// add vector layer
this.vlayer = new OpenLayers.Layer.Vector("Editable");
this.map.addLayer(this.vlayer);
// add editing toolbar
this.map.addControl(new OpenLayers.Control.EditingToolbar(this.vlayer));
},
mapFormPrepare: function (mapFormName) {
var features = this.vlayer.features;
var form = document.forms[mapFormName];
var i = 0;
if (features != null && features.length>0) {
for (i=0;i<features.length;i++) {
var geometry = features[i].geometry;
var str = null;
if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
str = "POINT:"+geometry.x+","+geometry.y
}
if (geometry.CLASS_NAME == "OpenLayers.Geometry.Circle") {
str ="CIRCLE:"+geometry.x+","+geometry.y+","+geometry.radius;
}
if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
str ="POLYGON:"+this.componentGeometry2String (geometry.components[0].components)
}
if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
str ="LINE:"+this.componentGeometry2String (geometry.components)
}
if (str != null ) {
var input = document.createElement("input");
input.id = "MAP_FEATURE"+i;
input.name = "MAP_FEATURE"+i;
input.value=str;
input.type = "hidden"; //Type of field - can be any valid input type like text,file,checkbox etc.
form.appendChild(input);
}
}
}
var latlon = this.map.getCenter();
var zoom = this.map.getZoom();
var inputForm = document.createElement("input");
inputForm.id = "MAP_FEATURE"+i;
inputForm.name = "MAP_FEATURE"+i;
inputForm.value= "MAP:"+latlon.lat+","+latlon.lon+","+zoom;
inputForm.type = "hidden";
form.appendChild(inputForm);
inputForm = document.createElement("input");
inputForm.id = "MAP_FEATURE_POST";
inputForm.name = "MAP_FEATURE_POST";
inputForm.value = "true";
inputForm.type = "hidden";
form.appendChild(inputForm);
},
CLASS_NAME: "MyGisRenderVector"
});
public class MapDataManager {
private static String MAP_FEATURE = "MAP_FEATURE";
public static List<GeometryObject> parseFromRequest(javax.servlet.ServletRequest request) {
int featureId =0;
String value = null;
List<GeometryObject> list = new ArrayList<GeometryObject>();
while ( (value = request.getParameter(MAP_FEATURE+featureId))!= null ) {
GeometryObject obj = ParsingFactory.createGeometryObject(value);
if (obj != null ) {
list.add(obj);
}
featureId++;
}
return list;
}
}
Maps for Enterprise application
Ok, It's time to have a look at how we can plug OpenLayers into our Web application.
First, we need to define all the configuration parameters like maximum and default extent, zoom levels, tiles or WMS server locations. Let's use the Spring framework for that and define extents for USA map and some parameters for tiles handling:
<bean id="extent" class="org.maps.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>
<bean id="defpoint" class="org.maps.Point">
<property name="lat" value="38.9435" />
<property name="lon" value="-107.0612" />
</bean>
<bean id="origin" class="org.maps.Point">
<property name="lat" value="400" />
<property name="lon" value="-400" />
</bean>
<bean id="tileSize" class="org.maps.Size">
<property name="w" value="256" />
<property name="h" value="256" />
</bean>
<bean id="map" class="org.maps.BaseMap">
<property name="extent" ref="extent" />
<property name="defpoint" ref="defpoint" />
<property name="origin" ref="origin" />
<property name="tileSize" ref="tileSize" />
<property name="baseLayerName" value="data" />
<property name="baseLayerUrl" value="maps" />
<property name="maxResolution" value="0.15228550153247" />
<property name="resolutionList">
<list>
<value>0.15228550153247</value>
<value>7.61427507662349E-02</value>
<value>3.80713753831174E-02</value>
<value>1.90356876915587E-02</value>
<value>9.51784384577936E-03</value>
</list>
</property>
</bean>
<bean id="mapRender" class="org.maps.MapRender"> <property name="baseMap" ref="map" /> <property name="velocityEngine" ref="velocityEngine" /> </bean> <bean id="velocityEngine" class="org.springframework.ui.velocity.VelocityEngineFactoryBean"> <property name="resourceLoaderPath"> <value>/</value> </property> </bean>
<bean id="vectorMap" class="org.maps.VectorMap"> <property name="mapRender" ref="mapRender" /> <property name="mapTemplate" value="/WEB-INF/map-templates/mapVector.vm" /> </bean>
Basic configuration parameters like extent, zoom, or map are just POJO beans with getter's and setter's for properties. The bean mapRender has two POJO properties: velocityEngine and baseMapp. Most importantly it renders the map script and it binds spring beans to velocity beans by calling the setGisMapping method:
public String getRenderedMap(HttpServlet servlet, String templateName,
MapRenderExtra mapRenderExtra) throws Exception {
String response = "";
VelocityContext vc = new VelocityContext();
Template t = getVelocityEngine().getTemplate(templateName);
StringWriter sw = new StringWriter();
setGisMapping(vc, servlet);
if (mapRenderExtra != null) {
mapRenderExtra.setExtraVelocityParameters(vc);
}
t.merge(vc, sw);
response = sw.toString();
return response;
}
protected void setGisMapping(VelocityContext vc, HttpServlet servlet) {
vc.put("ctxPath", servlet.getServletContext().getContextPath());
vc.put("map", getBaseMap());
}
As you see it binds the baseMap bean and ctxPath to the velocity context in order to use them as variables in velocity templates. Note, that it has the parameter mapRenderExtra, this is just an interface allowing classes calling this method to provide additional implementation where you may specify extra velocity bindings. This will allow us to use a velocity template as a base template and then add as many templates as you want. The interface has only one method:
public interface MapRenderExtra {
void setExtraVelocityParameters(VelocityContext vc);
}
public class VectorMapController implements MapRenderExtra {
private HttpServlet servlet;
List<GeometryObject> shapes;
private static String MAP_FEATURE = "MAP_FEATURE";
public String getMapScript(HttpServlet servlet) {
this.servlet = servlet;
ApplicationContext springContext = (ApplicationContext) servlet
.getServletContext()
.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
VectorMap bean = (VectorMap) springContext.getBean("vectorMap");
String templateName = bean.getMapTemplate();
String scriptString = null;
try {
scriptString = bean.getMapRender().getRenderedMap(servlet,
templateName, this);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return scriptString;
}
public void parseFromRequest(javax.servlet.ServletRequest request) {
int featureId = 0;
String value = null;
List<GeometryObject> list = new ArrayList<GeometryObject>();
while ((value = request.getParameter(MAP_FEATURE + featureId)) != null) {
GeometryObject obj = ParsingFactory.createGeometryObject(value);
if (obj != null) {
list.add(obj);
}
featureId++;
}
shapes = list;
}
@Override
public void setExtraVelocityParameters(VelocityContext vc) {
vc.put("vectorShapeData", shapes);
}
}
public void setExtraVelocityParameters(VelocityContext vc) {
vc.put("vectorShapeData", shapes);
}
VectorMap bean = (VectorMap) springContext.getBean("vectorMap");
String templateName = bean.getMapTemplate();
String scriptString = null;
scriptString = bean.getMapRender().getRenderedMap(servlet,
templateName, this);
We are sending ‘this' since we have implemented MapRenderExtra in
this controller. We need to add an extra velocity context variable since
we are trying to send Geometry objects to the map. This geometry object
is restored from the Http Request by calling the parseFromRequest
method.
Then out JSP page will look like:
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1"%>
<%@ page import="org.maps.VectorMapController"%>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<%
VectorMapController mapController = new VectorMapController();
mapController.parseFromRequest(request);
String mapScript = mapController.getMapScript(this);
out.write(mapScript);
%>
<script type="text/javascript">
<!--
// global init
var myMap = null;
function init() {
myMap = new MyGisRenderVector();
myMap.init();
}
// -->
</script>
</head>
<body onload="init()">
An example showing OpenLayers using String
<div id="map"
style="width: 600px; height: 500px; border: 1px solid black;"></div>
<form id="mapForm" method="post"
onsubmit="myMap.mapFormPrepare('mapForm');"><input type="submit"
name="Submit to Server"></form>
<br>
<a href="Map.jsp">Remove all</a>
</body>
</html>
As you see, we create the MapController class and then try to restore its state by calling the parseFromRequest method, then we render the javascript.
When the user presses submit we call the JS mapFormPrepare method for adding extra request parameters to send geometry content back in order to restore them by parseFromRequest.
Then we look at velocity templates. First we are using very basic map class which will be the same for all the application and it has basic init parameters like server URLs, default extent, etc. Here is the default velocity template:
<script src="OpenLayers.js"></script>
<script src="AGS.js"></script>
<script type="text/javascript">
<!--
// create a class for future reuse
MyGisRender = OpenLayers.Class.create();
MyGisRender.prototype= {
lon : $map.defpoint.lon,
lat : $map.defpoint.lat,
zoom : 1,
map : null,
baseLayer: null,
initialize: function() {
},
init: function(){
this.map = this.createMap();
this.baseLayer = this.createBaseLayer();
this.map.addLayer(this.baseLayer);
this.addControls();
},
createMap: function () {
var resArray = new Array();
#foreach($r in $map.resolutionList)
resArray.push($r);
#end
return new OpenLayers.Map( 'map', {
maxExtent: new OpenLayers.Bounds($map.extent.minX,$map.extent.minY,
$map.extent.maxX, $map.extent.maxY),
maxResolution: $map.maxResolution,
resolutions: resArray,
tileSize : new OpenLayers.Size($map.tileSize.w,$map.tileSize.h),
tileOrigin: new OpenLayers.LonLat($map.origin.lon,$map.origin.lat),
units: 'degree', controls: []} );
},
createBaseLayer: function () {
return new OpenLayers.Layer.AGS( "AGS",
"$map.baseLayerUrl", {layername: '$map.baseLayerName', type:'png',
tileOrigin: new OpenLayers.LonLat($map.origin.lon,$map.origin.lat)
} );
},
addControls: function () {
this.map.addControl(new OpenLayers.Control.PanZoomBar());
this.map.addControl(new OpenLayers.Control.Navigation());
this.map.addControl(new OpenLayers.Control.MousePosition());
this.map.setCenter (new OpenLayers.LonLat(this.lon, this.lat), this.zoom);
},
CLASS_NAME: "MyGisRender"
}
// -->
</script>
This template will give use the foundation for all the maps in our application. And then let's have a look at the template we have our VectorMapController for.
#parse("/WEB-INF/map-templates/map.vm")
<script type="text/javascript">
<!--
MyGisRenderVector = OpenLayers.Class.create();
MyGisRenderVector.prototype = OpenLayers.Class.inherit ( MyGisRender, {
vlayer: null,
addControls: function () {
// call inherited method
MyGisRender.prototype.addControls.apply(this);
// do our stuff
this.map.addControl(new OpenLayers.Control.MouseToolbar());
// add vector layer
this.vlayer = new OpenLayers.Layer.Vector("Editable");
this.map.addLayer(this.vlayer);
// add editing toolbar
this.map.addControl(new OpenLayers.Control.EditingToolbar(this.vlayer));
this.addVectorLayer();
},
addVectorLayer: function() {
var vlayer = this.vlayer;
var bounds = new OpenLayers.Bounds();
var lat, lon, r, circleBounds,ring,points,waypoint,lineString;
var features = [] ;
#foreach( $shape in $vectorShapeData)
#if ($shape.objectType == "POINT") //point
#foreach( $p in $shape.points )
lat = $p.lat;
lon = $p.lon;
features.push(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lat,lon)));
r = 0.01;
circleBounds = new OpenLayers.Bounds(lat-r,lon-r,lat+r,lon+r);
bounds.extend( circleBounds );
#end
#end
#if ($shape.objectType == "POLYGON") //polygon
points = [] ;
#foreach( $p in $shape.points )
points.push(new OpenLayers.Geometry.Point($p.lat,$p.lon));
#end
ring = new OpenLayers.Geometry.LinearRing(points);
features.push(new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon([ring])));
ring.calculateBounds();
bounds.extend(ring.bounds);
#end
#if ($shape.objectType == "LINE") //waypoint
waypoint = [] ;
#foreach( $p in $shape.points )
waypoint.push(new OpenLayers.Geometry.Point($p.lat,$p.lon));
#end
lineString = new OpenLayers.Geometry.LineString(waypoint) ;
features.push(new OpenLayers.Feature.Vector(lineString));
lineString.calculateBounds();
bounds.extend(lineString.bounds);
#end
#end
this.vlayer.addFeatures(features);
},
componentGeometry2String: function (poly) {
var str="";
var i;
for (i=0;i<poly.length;i++) {
var a = poly[i];
str+=a.x+"," + a.y;
if (i != poly.length - 1 ) {
str+=";";
}
}
return str;
},
mapFormPrepare: function (mapFormName) {
var features = this.vlayer.features;
var form = document.forms[mapFormName];
var i = 0;
if (features != null && features.length>0) {
for (i=0;i<features.length;i++) {
var geometry = features[i].geometry;
var str = null;
if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
str = "POINT:"+geometry.x+","+geometry.y
}
if (geometry.CLASS_NAME == "OpenLayers.Geometry.Circle") {
str ="CIRCLE:"+geometry.x+","+geometry.y+","+geometry.radius;
}
if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
str ="POLYGON:"+this.componentGeometry2String (geometry.components[0].components)
}
if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
str ="LINE:"+this.componentGeometry2String (geometry.components)
}
if (str != null ) {
var input = document.createElement("input");
input.id = "MAP_FEATURE"+i;
input.name = "MAP_FEATURE"+i;
input.value=str;
input.type = "hidden"; //Type of field - can be any valid input type like text,file,checkbox etc.
form.appendChild(input);
}
}
}
var latlon = this.map.getCenter();
var zoom = this.map.getZoom();
var inputForm = document.createElement("input");
inputForm.id = "MAP_FEATURE"+i;
inputForm.name = "MAP_FEATURE"+i;
inputForm.value= "MAP:"+latlon.lat+","+latlon.lon+","+zoom;
inputForm.type = "hidden";
form.appendChild(inputForm);
inputForm = document.createElement("input");
inputForm.id = "MAP_FEATURE_POST";
inputForm.name = "MAP_FEATURE_POST";
inputForm.value = "true";
inputForm.type = "hidden";
form.appendChild(inputForm);
},
CLASS_NAME: "MyGisRenderVector"
});
// -->
</script>
First, note that we using our basic map [#parse("/WEB-INF/map-templates/map.vm")] and then we are extending this class.
You remember we put in the velocity context: vc.put("vectorShapeData", shapes). The Template method addVectorLayer will be readjusted by adding new lines to restore the geometry object from shapes back–end bean. The method mapFormPrepare will send all the objects back to the server when the user presses the submit button:
<form id="mapForm" method="post"
onsubmit="myMap.mapFormPrepare('mapForm');"><input type="submit"
name="Submit to Server"></form>
In this article I have tried to demonstrate how easy it is to use maps with javascript and OpenLayers. Also I have tried to show a method for reusing most of your base code for use when you have multiple pages with maps. The examples and war file are in java but you can do the same in ASP.NET. Finally, I would recommend using a UI framework like STRUTS or JSF, since they are powerful and easy to use with the Spring framework.
Download war file