|
| Tue, May 13th | home | browse | articles | contact | chat | submit | faq | newsletter | about | stats | scoop | 11:31 PDT |
|
login « register « recover password « |
| [Article] | add comment | [Article] |
This article will show you how easy it is to set up interactive maps for your Web page, including multiple layers and interactivity between your view of your Web page and your backend business logic. Copyright notice: All reader-contributed material on freshmeat.net is the property and responsibility of its author; for reprint rights, please contact the author directly. When you reach the point that it would be nice to have a map on a 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. Let's look at some common implementations and their ups and downs. Common problems when creating a GIS application
PresentationOf course you would like to implement your maps in the best way possible. Using rich UI clients like applets, Flash, or 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 browsers. This could be inconvenient for those people who don't have permission to install extra components on their computers or don't want to install them. I believe JavaScript maps are a more reasonable choice. Once a user goes to your Web page, he/she sees maps immediately, and no other actions or installation are required. I think Google Maps has proved this approach. Initializing parametersWhen you want to render a JavaScript map, you are supposed to provide some initialization parameters that set up 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 you tend 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 does not reuse existing components as a good application should. What we need is to use a map template and reuse this template across different pages of the application. The map template should be between the Web page rendering and the JavaScript. Using template technologies such as Apache Velocity helps to solve the problem of reusing JavaScript components. CommunicationThere is only one answer for JavaScript: Ajax. Speed and ReliabilityMaps consist of a base layer and extra layers. The base layers are 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). Let's start implementing maps. Making Maps with OpenLayersI 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 subscribe to events. I'm very excited about what the people at OpenLayers have done because of its simplicity and convenience. First, we need to know what the GIS WMS Server is. WMS (Web Map Service) is an open standard for rendering maps. Most map servers support WMS. In particular, WMS defines:
OpenLayers has the WMS Layer as a primary layer for maps. You can find a lot of free WMS servers on the Internet. Here is an example from the OpenLayers examples directory:
<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>
Example: http://shpuroff.com/maps/wms.html In this example, we use the 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. 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 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 from 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 in different zoom levels to speed up the map. Here is the code of a basic 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>
Example:
http://shpuroff.com/maps/arcGis.html
As you 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"
});
Example:
http://shpuroff.com/maps/arcGis1.html
There we added some new control to the map 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 the page and get values from them. If the maps exist in different Web pages, we can use the Spring Framework to send the proper bean there. But what 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)); We need to use a template approach like JSP tags or Apache Velocity. Velocity is better, since everything will be in the same template, while JSP tags would be in a JSP page. Let's have a look: #foreach( $p in $shape.points ) points.push(new OpenLayers.Geometry.Point($p.lat,$p.lon)); #end Here is an example of velocity templates for maps: http://shpuroff.com/maps/mapVector.vm Extra layersThe map could contain additional layers. We have the OGS WMS specification for this, 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 a WMS service simultaneously, and this causes performance issues. Here, I have added a new OpenLayers 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");
},
Example:
http://shpuroff.com/maps/arcGis2.html
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 an additional selection layer. For example: http://shpuroff.com/maps/Selection.html In this example, we inherit our base map to reuse all the basic properties, then define a new event called [sateselected]. In the method addControls, we create a new vector layer (the layer which 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 the 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. OpenLayers can add a marker to the map and show some HTML in the message window, as Google does. You can find samples in the examples directory. Vector selection looks fast, but 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 additional 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, then 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 serverThe OpenLayers framework has controls which allow you to draw different kinds of vector shapes. For example: http://shpuroff.com/maps/Vector.html Here, 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 a 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 the DOM model of HTML forms. Basically, it adds some parameters, and these parameters will be sent to the server upon submission.
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"
});
You can the read them from the request, for example:
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;
}
}
You will find the completed class in the war file. Maps for Enterprise applicationIt'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 the maximum and default extents, zoom levels, and tiles or WMS server locations. Let's use the Spring framework for that, and define extents for a USA map and some parameters for tile 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>
Next, we need to specify a bean which will render maps, and we need to provide velocity configuration: <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> Finally, we will use all of the above: <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 getters and setters for properties. The bean mapRender has two POJO properties: velocityEngine and baseMap. Most importantly it renders the map script and 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);
}
This is the foundation of our maps. Now we will create a controller:
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);
}
}
Note that we implement the MapRenderExtra interface because we are going to customize our map a little bit:
public void setExtraVelocityParameters(VelocityContext vc) {
vc.put("vectorShapeData", shapes);
}
And here we notify the MapRender class that the previous method should
be visited:
VectorMap bean = (VectorMap) springContext.getBean("vectorMap");
String templateName = bean.getMapTemplate();
String scriptString = null;
scriptString = bean.getMapRender().getRenderedMap(servlet,
templateName, this);
We are sending " Then our 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 a very basic map class which will be the same for all of the application and 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 us the foundation for all the maps in our application. 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>
Note that we are using our basic map
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 the "
<form id="mapForm" method="post"
onsubmit="myMap.mapFormPrepare('mapForm');"><input type="submit"
name="Submit to Server"></form>
ConclusionIn 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 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. Author's bio: Alex Shpurov offers his great thanks to software developer Nathan Thomas, who helped in writing this article. T-Shirts and Fame! We're eager to find people interested in writing articles on software-related topics. We're flexible on length, style, and topic, so long as you know what you're talking about and back up your opinions with facts. Anyone who writes an article gets a t-shirt from ThinkGeek in addition to 15 minutes of fame. If you think you'd like to try your hand at it, let jeff.covey@freshmeat.net know what you'd like to write about.
|