2009年4月30日星期四

ArcGIS Server Java ADF 案例教程 23

三 Task的渲染

Task的功能类中还有一个属性,IDE默认生成的名字是taskInfo,它对应这个另外一个Java类(如上面QueryTaskTaskInfo类)的对象。这个类主要控制Task的渲染,我们可以回想一下,如果光在Task的功能类中定义了Command是一个void类型的query方法,Task面板怎么会生成一个标签为“查询”的按钮出来的呢?答案就在taskInfo中。

打开上面生成的“QueryTaskTaskInfo.java”文件,我们可以看到以下的内容:
public class QueryTaskTaskInfo extends SimpleTaskInfo
{
public TaskDescriptor getTaskDescriptor()
{
TaskDescriptor descriptor = new TaskDescriptor(QueryTask.class, "queryTask", "查询任务");
return descriptor;
}

public TaskParamDescriptorModel[] getParamDescriptors()
{
TaskParamDescriptor[] descriptors = new TaskParamDescriptor[1];
descriptors[0] = new TaskParamDescriptor(QueryTask.class, "keyWord", "关键词", "getKeyWord", "setKeyWord");
return descriptors;
}

public TaskActionDescriptorModel[] getActionDescriptors()
{
TaskActionDescriptor[] descriptors = new TaskActionDescriptor[1];
descriptors[0] = new TaskActionDescriptor(QueryTask.class, "query", "查询");
return descriptors;
}

public TaskToolDescriptorModel[] getToolDescriptors()
{
TaskToolDescriptor[] descriptors = new TaskToolDescriptor[1];
descriptors[0] = new TaskToolDescriptor(QueryTask.class, "select", "选择", "EsriMapPolygon");
return descriptors;
}
}


简单的说,TaskInfo类中包含4个主要的属性:TaskDescriptor对象、TaskParamDescriptor[]数组、TaskActionDescriptor[]数组、TaskToolDescriptor[]数组。很好理解,TaskDescriptor对应整个Task的外观,比如Task的标题等;TaskParamDescriptor[]描述所有Parameter的外观;TaskActionDescriptor[]描述所有Command的外观;TaskToolDescriptor[]描述所有Tool的外观。一个Task中肯定包含不定数目的Parameter、Command或Tool,因此他们的描述需要数组来存放。

让我们通过TaskToolDescriptor来更详细地看一下,上面的代码设置“select”这个TaskTool表现为一个“选择”按钮,并在点击时通知Map执行“EsriMapPolygon”操作。现在我想让它渲染成一个自定义的图片按钮,并随鼠标移上或点击切换不同的图片,用下面的代码可以实现这样的功能:
descriptors[0].setRendererType(TaskToolDescriptor.IMAGE_RENDERER_TYPE);
descriptors[0].setDefaultImage("images/tasks/maptools/polygon.gif");
descriptors[0].setHoverImage("images/tasks/maptools/polygonU.gif");
descriptors[0].setSelectedImage("images/tasks/maptools/polygonD.gif");
descriptors[0].setDisabledImage("images/tasks/maptools/polygonX.gif");


现在我还想调整一下这些按钮、文本框的布局,怎么办?TaskInfo还可以指定Task各个子元素的布局,让我们在刚刚的QueryTaskTaskInfo类中再添加以下的代码:
public TaskLayout[] getTaskLayout()
{
TabularLayout[] descriptors = new TabularLayout[1];
descriptors[0] = new TabularLayout();
descriptors[0].addComponent(getParamDescriptors()[0], new TabularPosition(0, 0, 0, 0));
descriptors[0].addComponent(getActionDescriptors()[0], new TabularPosition(1, 0, 0, 0));
descriptors[0].addComponent(getToolDescriptors()[0], new TabularPosition(2, 0, 0, 0));
return descriptors;
}


经过上面的两次修改,我们会发现Task面板的Tool变成了使用图片渲染,并且布局方式更加我们的定义进行了变化,效果如图 17所示。
图 17 更改Tool渲染方式和布局后的Task面板

顺便回头再看看页面中Task的定义,其中有taskInfo属性,它对应的就是这个Task的TaskInfo对象:
taskInfo="#{queryTask.taskInfo}"

最后稍微总结一下吧,Task的外观主要通过TaskInfo来定义,ADF会根据Task所对应的TaskInfo,通过XSL样式文件渲染成DHTML代码,并输出到浏览器。到现在为止,一个Task面板包含什么内容、对应后台什么对象、怎么控制它们的表现,你应该都清楚了吧?

ArcGIS Server Java ADF 案例教程 22

二 Task的执行

Task的执行主要在对应的功能类(比如上面的QueryTask类)中实现,上面已经详述了Task中的Parameter、Command、Tool分别对应的功能类中的元素,下面就让我们看看怎么去使用它们。

在上面定义的那个QueryTask中,我们设计点击“查询”按钮就能根据文本框中输入的关键词去查找相应的要素。下面是实现这样功能的一段代码:
public void query(TaskEvent event)
{
WebContext webContext = event.getWebContext();

WebQuery webQuery = (WebQuery) webContext.getAttribute("query");
TextCriteria textCriteria = new TextCriteria();
textCriteria.setMaxRecordCount(100);
textCriteria.setSearchText(this.keyWord);

List
listQueryResult = webQuery.query(textCriteria, webQuery.getQueryLayers());

webContext.getWebGraphics().clearGraphics();
for (int i = 0; i <>
{
QueryResult queryResult = listQueryResult.get(i);
queryResult.highlight();
}

webContext.refresh();
}

我们可以看到,从TaskEvent中可以直接获得我们的WebContext对象;从WebContext开始可以执行一系列的操作,其中用到了keyWord这个属性,keyWord对应的就是用户在Task面板的文本框中输入的文字;最后查询到结果在地图上通过Graphic高亮出来,通过WebContext的refresh方法输出到浏览器。

这里涉及到了Parameter和Command的使用;至于Tool,唯一的区别就是WebContext是从MapEvent中获得,因此这里就不详细写了。另外,这段代码中还涉及到如何进行查询、如何使用Graphic等,这些内容我们在后面会详细讲。

2009年4月29日星期三

ArcGIS Server Java ADF 案例教程 21

这一章我们主要研究一下Task同志的成份和出身。ADF中本身包含一些现成的Task,不过相比对开发人员来说没有很大的意义,这一章我们的重点还是在如何自定义Task。

Task是实现业务逻辑的重要部分。简单来说,Task可以让输入一些参数(在页面上以文本框、组合框等形式出现)、通过Command和Tool的组合,帮助你完成特定的“任务”并返回结果,这就是所谓的Task。

一 Task的请求

虽说这一节的标题是Task的请求,但在这里我们对ADF的Task怎么向服务器做请求并不作深入的探讨,这里我们主要是去了解一个Task在服务器端执行的时候需要客户端提供的信息有哪些。
图 13 一个Task的请求面板

如图 12是一个简单的Task请求面板,在这个面板中会有一些Parameter(一般以文本框、组合框等形式出现)和一些Command或Tool(一般以按钮形式出现)。下面我们先来定义一个Task,其中包含一个Parameter、一个Command和一个Tool。

在IDE中新建一个Task最简单的方法就是使用菜单【File】-【New】-【Task】,然后给你的Task起个名字,如图 14。
图 14 新建一个Task

这个时候,IDE会弹出另外一个对话框供你对这个Task进行一些设置,比如Task的名称、里面包含的Parameter、Command(在这里叫Action)、Tool等。这里就根据你的需要进行填写了,我们先各添加一个,如图 15所示。
图 15 给Task添加一些元素

这时你会发现,在Java源文件目录下多了2个文件,一个是“QueryTask.java”,另一个是“QueryTaskTaskInfo.java”,同时在faces-config.xml文件中也多了一个名为queryTask的ManagedBean。

打开“QueryTask.java”文件,我们可以找到和刚才定义的那些内容相对应的代码,下面的表简述了这样的一种对应关系:

类型 名称
Parameter String keyWord
Command Void方法 query(TaskEvent event)
Tool Void方法 select(MapEvent event)

在“QueryTask.java”中我们还可以找到QueryTaskTaskInfo对象,关于QueryTaskTaskInfo这个类的定义及其作用我们在后面再详细说。下面让我们先把这个Task添加到页面上去:

这里的taskInfo属性就是这个Task的TaskInfo对象,我们可以先不用管,唯一一个以前没有出现过的属性就是windowingSupport,这个属性定义的是这个Task面板是否可以在页面上浮动并拖拽。让我们运行下这个页面看一下效果:
图 16 上面定义的Task面板的效果

稍微总结一下,Task中主要包含3个功能元素:Parameter、Command、Tool;Task对应的Java类有2个,其中在实现功能的Task类(比如上面的QueryTask类)中,Parameter对应类中的属性字段、Command对应类中的带TaskEvent 参数的void方法、Tool对应类中的带MapEvent 参数的void方法。这样,前台的Task请求就和后台具体的功能联系起来了。

2009年4月28日星期二

ArcGIS Server Java ADF 案例教程 20

七 自定义Button实现前面的Command和Tool【案例】

在这一章前面的小节中,我们实现了两个案例分别演示Command和Tool的功能;在这个案例里,让我们用Button来完成相同的工作。
首先,我们需要在一个我们自己定义的ManagedBean 来实现这些功能。注意,在这个ManagedBean中我们会使用WebContext对象(否则怎么调用地图功能),因此,我们这个类定义需要实现WebContextInitialize接口。当然,在IDE中有更简单的方法,比如在Eclipse中选择菜单【New】-【Context Attribute】就可以新建一个ManagedBean并添加到WebContext对象的attributes中去。
好了,现在我们已经有了自己的ManagedBean类“MapEditor”;接下来我们先实现地图等比放大的功能。在这个类中,我们添加一个不带任何参数的方法zoomIn,在zoomIn实现地图放大一倍的功能。源代码如下:
package wuyf;

import com.esri.adf.web.data.WebContext;
import com.esri.adf.web.data.WebContextInitialize;

public class MapEditor implements WebContextInitialize
{
private WebContext webContext;

public void destroy() {}

public void init(WebContext context)
{
this.webContext = context;
}

public void zoomIn()
{
webContext.getWebMap().getCurrentExtent().expand(0.5);
webContext.refresh();
}
}

在页面中用如下的标签进行定义:

这个时候,你在页面上点击这个Button的时候,地图也会放大一倍了。
下面我们接着去实现点击地图居中,和上面类似,只不过实现这个功能的方法需要带一个MapEvent参数:
public void centerAt(MapEvent event)
{
WebPoint webPoint = (WebPoint)event.getWebGeometry().toMapGeometry(webContext.getWebMap());
webContext.getWebMap().centerAt(webPoint, 1.0);
webContext.refresh();
}

在页面中定义的标签则如下:

好了,试着点击这个按钮,再在地图上点击,是不是地图以点击处居中了?

ArcGIS Server Java ADF 案例教程 19

六 Button

Button不属于Toolbar的子元素,而是和Toolbar有类似的地位;但同时,它的功能又集合了Command和Tool。Button的出现主要是为了方便用户,当用户不希望出现一个工具条来摆放与地图相关的功能,那么Button可以在页面的任意地方摆放一个与地图相关的按钮。

在ADF工程里,让我们打开ADF自带的模板页面“edit.jsp”,在里面有很多Button。以下是定义了一个在客户端绘制一个矩形,服务器端执行“选择要素”操作的Button:

我们可以看到,Button和Command以及Tool都很相像,有clientAction也有serverAction,所不同的是,Button需要指定一个mapId属性,因为Command和Tool都是存放在Toolbar中,Toolbar会有个统一的mapId来表示和哪个Map绑定;而Button则由于是独立的按钮,需要自己指定与之绑定的Map。

clientAction属性是可选的,如果你不填clientAction,那么这个Button的行为就类似于Command;如果你设置了clientAction,那么这个Button的行为就类似与Tool。另外,这里的serverAction需要指向一个服务器端对象的方法,如果没有clientAction,那么这个serverAction指向的方法应该是一个无参数的方法;如果有clientAction,那么这个serverAction指向的方法应该是一个带MapEvent 参数的方法。

ArcGIS Server Java ADF 案例教程 18

五 自定义Tool实现点击地图居中【案例】

在这一小节中,我们通过自定义一个Tool来激活这样的地图功能:每次点击地图,都以这个点击点为中心使地图居中。

如下,我们新建了这样一个类CenterAtToolAction:

import com.esri.adf.web.data.WebContext;
import com.esri.adf.web.data.geometry.WebPoint;
import com.esri.adf.web.faces.event.MapEvent;
import com.esri.adf.web.faces.event.MapToolAction;

public class CenterAtToolAction implements MapToolAction
{
public void execute(MapEvent event) throws Exception
{
WebContext webContext = event.getWebContext();
WebPoint webPoint = (WebPoint)event.getWebGeometry().toMapGeometry(webContext.getWebMap());
webContext.getWebMap().centerAt(webPoint, 0.5);
webContext.refresh();
}
}


这个类的关键是实现了MapToolAction的execute方法,当一个Tool被激活,并在地图上进行了操作以后,一个请求就会被发送到服务器,对应的MapToolAction的execute方法就会被执行。

这里,我们通过MapEvent的获得WebContext和WebGeometry 对象。比如我们在浏览器中点击了一下,那么WebGeometry就是一个点对象,当然,这个点坐标是基于屏幕坐标的,我们需要调用它的toMapGeometry方法转化成地理坐标。

当这些准备工作完成以后,我们就可以使用WebMap的centerAt方法使地图居中,随后刷新WebContext输出响应到浏览器。

如图 11是点击居中前后的地图。

ArcGIS Server Java ADF 案例教程 17

四 Tool

Tool是另外一种按钮,虽然它在外观上和Command可能没什么区别,但是当Tool被点击以后,地图状态就进行了改变。比如我当前默认对地图的操作是漫游,通过点击一个Tool,接下来在地图上的点击可能就变成了放大操作——与地图交互,这就是Tool的作用。

以下代码定义了一个“使地图处于漫游状态”的Tool:

Tool定义时两个最重要的属性就是clientAction和serverAction。clientAction定义的是在浏览器端执行的操作,比如这里的“EsriMapContinuousPan”代表浏览器端执行的是连续漫游操作——当然,比如你需要在地图里画个多边形之类的操作也可以使用其它的定义,可用的操作大概如表 1所列:
EsriEditingLine 编辑直线 EsriMapLine 画直线
EsriEditingPoint 编辑点 EsriMapMouseWheel 滚轮滚动
EsriEditingPolygon 编辑多边形 EsriMapOval 画椭圆
EsriEditingPolyline 编辑多线 EsriMapPan 漫游
EsriMapCircle 画圆 EsriMapPoint 画点
EsriMapContinuousPan 连续漫游 EsriMapPolygon 画多边形
EsriMapImage 添加图片 EsriMapPolyline 画多线
EsriMapKeyNavigation 键盘导航 EsriMapRectangle 画矩形
表 1 浏览器端支持的操作列表

当用户在浏览器执行了操作以后,ADF会把这个操作相关的信息发送到服务器,然后Tool的serverAction属性中定义的这个类就起作用了。比如上面的这个Tool,服务器接到请求以后会通过PanToolAction的定义对请求过来的参数进行处理(比如向右漫游100个像素),经过一系列计算以后它更新服务器端的WebMap等对象的状态,所有工作完成以后再调用WebContext的refresh方法进行刷新,随后输出响应到浏览器。

serverAction指向的类主要需要实现MapToolAction 接口,在下面的案例中,我们会实现一个自己的ToolAction类。

2009年4月24日星期五

PostgreSQL+PostGIS的使用 5

五、 PostGIS示例

下面我们通过一个简单的Flex应用示例来看一下PostGIS的用法:

假想现在发生了恐怖袭击,导致在一些城市有污染物出现,现在我们要根据污染物和当地风力、风向情况,计算污染扩散范围,针对这些区域及时进行警报和疏散。

首先我们希望获得所有发生污染的城市的当前风速、风向等信息,在我们的PostGIS数据库中有一个空间表保存着这些信息,我们构造这样的SQL语句进行查询:
select *,ST_AsGeoJson(shape) from sde.wind

这里会获取所有风相关的信息,并且附加了以JSON格式返回的几何信息,这有助于我们在Flex中进行解析。如下图是关于风的查询结果:

下面我们希望PostGIS帮助我们实现一些空间分析。我们以污染发生的城市为起点,当地风向为主方向,构造一个30度开角的范围;这个范围将是污染扩散的主要方向,扩散的范围主要和风的强度有关;在构造这个区域以后,为了保险起见,我们在对其进行一定范围的缓冲,最后得到每个污染源可能扩散的范围。我们构造的SQL语句如下:
select *,ST_AsGeoJson( ST_Buffer( ST_PolygonFromText( 'POLYGON((' ||ST_X(shape)||' '||ST_Y(shape)||',' ||ST_X(shape)+velocity*cos((direction+15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction+15)*PI()/180)/20||',' ||ST_X(shape)+velocity*cos((direction-15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction-15)*PI()/180)/20||',' ||ST_X(shape)||' '||ST_Y(shape)||'))' ) , velocity/50 ) ) from sde.wind

下面是PostGIS进行运算后返回的结果:

在这里,Flex应用与服务器的交互通过BlazeDS进行,下面是本示例在服务器端的Java代码:

package wuyf;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;

public class Wind
{
private Connection conn = null;

public Connection getConn()
{
if (conn==null)
{
try
{
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://localhost:5432/sde" ;
conn = DriverManager.getConnection(url, "sde" , "pwd" );
conn.setAutoCommit(false);
}
catch(Exception e)
{
System.err.print(e);
}
}

return conn;
}

public ArrayList
> getWinds()
{
ArrayList
> result = new ArrayList>();

if ( this.getConn()==null )
return result;

try
{
String sql = "select *,ST_AsGeoJson(shape) from sde.wind";

Statement st = this.getConn().createStatement();
st.setFetchSize(0);
ResultSet rs = st.executeQuery(sql);
while (rs.next())
{
HashMap
map = new HashMap();
map.put("shape", rs.getString("ST_AsGeoJson"));
map.put("velocity", rs.getString("velocity"));
map.put("direction", rs.getString("direction"));
result.add(map);
}
rs.close();
st.close();
}
catch(Exception e)
{
System.err.print(e);
}

return result;
}


public ArrayList
> getEffectZones()
{
ArrayList
> result = new ArrayList>();

if ( this.getConn()==null )
return result;

try
{
String sql = "select *,ST_AsGeoJson(";
sql+= "ST_Buffer(";
sql+= "ST_PolygonFromText(";
sql+= "'POLYGON(('";
sql+= "||ST_X(shape)||' '||ST_Y(shape)||','";
sql+= "||ST_X(shape)+velocity*cos((direction+15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction+15)*PI()/180)/20||','";
sql+= "||ST_X(shape)+velocity*cos((direction-15)*PI()/180)/20||' '||ST_Y(shape)+velocity*sin((direction-15)*PI()/180)/20||','";
sql+= "||ST_X(shape)||' '||ST_Y(shape)||'))'";
sql+= ")";
sql+= ", velocity/50";
sql+= ")";
sql+= ") ";
sql+="from sde.wind";

Statement st = this.getConn().createStatement();
st.setFetchSize(0);
ResultSet rs = st.executeQuery(sql);
while (rs.next())
{
HashMap
map = new HashMap();
map.put("shape", rs.getString("ST_AsGeoJson"));
map.put("velocity", rs.getString("velocity"));
map.put("direction", rs.getString("direction"));
result.add(map);
}
rs.close();
st.close();
}
catch(Exception e)
{
System.err.print(e);
}

return result;
}

}

PostgreSQL+PostGIS的使用 4

2. PostGIS扩展函数

管理函数:

删除一个空间表(包括geometry_columns中的记录) DropGeometryTable([], )
更新空间表的空间参考 UpdateGeometrySRID([], , , )
更新空间表的统计信息 update_geometry_stats([, ])

参考语义:
Geos:GEOS库
Jts:JTS库
Proj:PROJ4库 postgis_version()

postgis_lib_version()
postgis_lib_build_date()
postgis_script_build_date()
postgis_scripts_installed()
postgis_scripts_released()
postgis_geos_version()
postgis_jts_version()
postgis_proj_version()
postgis_uses_stats()
postgis_full_version()

几何操作符:

A范围=B范围 A = B
A范围覆盖B范围或A范围在B范围左侧 A &<> B
A范围在B范围左侧 A <<>> B
A范围覆盖B范围或A范围在B范围下方 A &<| B A范围覆盖B范围或A范围在B范围上方 A |&> B
A范围在B范围下方 A <<| B A范围在B范围上方 A |>> B
A=B A ~= B
A范围被B范围包含 A @ B
A范围包含B范围 A ~ B
A范围覆盖B范围 A && B

几何量测函数:

量测面积 ST_Area(geometry)
根据经纬度点计算在地球曲面上的距离,单位米,地球半径取值6370986米 ST_distance_sphere(point, point)
类似上,使用指定的地球椭球参数 ST_distance_spheroid(point, point, spheroid)
量测2D对象长度 ST_length2d(geometry)
量测3D对象长度 ST_length3d(geometry)
根据经纬度对象计算在地球曲面上的长度 ST_length_spheroid(geometry,spheroid)
ST_length3d_spheroid(geometry,spheroid)
量测两个对象间距离 ST_distance(geometry, geometry)
量测两条线之间的最大距离 ST_max_distance(linestring,linestring)
量测2D对象的周长 ST_perimeter(geometry)
ST_perimeter2d(geometry)
量测3D对象的周长 ST_perimeter3d(geometry)
量测两点构成的方位角,单位弧度 ST_azimuth(geometry, geometry)

几何对象输出:
参考语义:
NDR:Little Endian
XDR:big-endian
HEXEWKB:Canonical
SVG:SVG 格式
GML:GML 格式
KML:KML 格式
GeoJson:GeoJson 格式

ST_AsBinary(geometry,{'NDR'|'XDR'})
ST_AsEWKT(geometry)
ST_AsEWKB(geometry, {'NDR'|'XDR'})
ST_AsHEXEWKB(geometry, {'NDR'|'XDR'})
ST_AsSVG(geometry, [rel], [precision])
ST_AsGML([version], geometry, [precision])
ST_AsKML([version], geometry, [precision])
ST_AsGeoJson([version], geometry, [precision], [options])

几何对象创建:

参考语义:
Dump:转储 ST_GeomFromEWKT(text)

ST_GeomFromEWKB(bytea)
ST_MakePoint(, , [], [])
ST_MakePointM(, , )
ST_MakeBox2D(, )
ST_MakeBox3D(, )
ST_MakeLine(geometry set)
ST_MakeLine(geometry, geometry)
ST_LineFromMultiPoint(multipoint)
ST_MakePolygon(linestring, [linestring[]])
ST_BuildArea(geometry)
ST_Polygonize(geometry set)
ST_Collect(geometry set)
ST_Collect(geometry, geometry)
ST_Dump(geometry)
ST_DumpRings(geometry)

几何对象编辑:

给几何对象添加一个边界,会使查询速度加快 ST_AddBBOX(geometry)
删除几何对象的边界 ST_DropBBOX(geometry)
添加、删除、设置点 ST_AddPoint(linestring, point, [])
ST_RemovePoint(linestring, offset)
ST_SetPoint(linestring, N, point)
几何对象类型转换 ST_Force_collection(geometry)
ST_Force_2d(geometry)
ST_Force_3dz(geometry), ST_Force_3d(geometry),
ST_Force_3dm(geometry)
ST_Force_4d(geometry)
ST_Multi(geometry)
将几何对象转化到指定空间参考 ST_Transform(geometry,integer)
对3D几何对象作仿射变化 ST_Affine(geometry, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8, float8)
对2D几何对象作仿射变化 ST_Affine(geometry, float8, float8, float8, float8, float8, float8)
对几何对象作偏移 ST_Translate(geometry, float8, float8, float8)
对几何对象作缩放 ST_Scale(geometry, float8, float8, float8)
对3D几何对象作旋转 ST_RotateZ(geometry, float8)
ST_RotateX(geometry, float8)
ST_RotateY(geometry, float8)
对2D对象作偏移和缩放 ST_TransScale(geometry, float8, float8, float8, float8)
反转 ST_Reverse(geometry)
转化到右手定则 ST_ForceRHR(geometry)
参考IsSimple函数
使用Douglas-Peuker算法 ST_Simplify(geometry, tolerance)
ST_SimplifyPreserveTopology(geometry, tolerance)
讲几何对象顶点捕捉到网格 ST_SnapToGrid(geometry, originX, originY, sizeX, sizeY)
ST_SnapToGrid(geometry, sizeX, sizeY), ST_SnapToGrid(geometry, size)
第二个参数为点,指定原点坐标 ST_SnapToGrid(geometry, geometry, sizeX, sizeY, sizeZ, sizeM)
分段 ST_Segmentize(geometry, maxlength)
合并为线 ST_LineMerge(geometry)

线性参考:

根据location(0-1)获得该位置的点 ST_line_interpolate_point(linestring, location)
获取一段线 ST_line_substring(linestring, start, end)
根据点获取location(0-1) ST_line_locate_point(LineString, Point)
根据量测值获得几何对象 ST_locate_along_measure(geometry, float8)
根据量测值区间获得几何对象集合 ST_locate_between_measures(geometry, float8, float8)

杂项功能函数:
几何对象的摘要 ST_Summary(geometry)
几何对象的边界 ST_box2d(geometry)
ST_box3d(geometry)
多个几何对象的边界 ST_extent(geometry set)
0=2d, 1=3dm, 2=3dz, 3=4d ST_zmflag(geometry)
是否包含Bounding Box ST_HasBBOX(geometry)
几何对象的维数:2、3、4 ST_ndims(geometry)
子对象的个数 ST_nrings(geometry)
ST_npoints(geometry)
对象是否验证成功 ST_isvalid(geometry)
扩大几何对象 ST_expand(geometry, float)
计算一个空间表的边界范围 ST_estimated_extent([schema], table, geocolumn)
获得空间参考 ST_find_srid(, , )
几何对象使用的内存大小,单位byte ST_mem_size(geometry)
点是否在圆上 ST_point_inside_circle(,,,)
获取边界的X、Y、Z ST_XMin(box3d)
ST_YMin(box3d)
ST_ZMin(box3d)
ST_XMax(box3d)
ST_YMax(box3d)
ST_ZMax(box3d)
构造一个几何对象的数组 ST_Accum(geometry set)

长事务支持:
启用/关闭长事务支持,重复调用无副作用 EnableLongTransactions()
DisableLongTransactions()
检查对行的update和delete操作是否已授权 CheckAuth([],
, )
锁定行 LockRow([],
, , , [])
解锁行 UnlockRows()
在当前事务中添加授权ID AddAuth()

其它还有SQL-MM和ArcSDE样式的函数支持,可以参考http://postgis.refractions.net/documentation/manual-1.3/ch06.html#id2750611,这里就不详细列了。

PostgreSQL+PostGIS的使用 3

四、 PostGIS中的常用函数

以下内容包括比较多的尖括号,发布到blogger的时候会显示不正常,内容太多我也无暇一个个手动改代码,因此如有问题就去参考PostGIS官方文档。

首先需要说明一下,这里许多函数是以ST_[X]yyy形式命名的,事实上很多函数也可以通过xyyy的形式访问,在PostGIS的函数库中我们可以看到这两种函数定义完全一样。

1. OGC标准函数

管理函数:
添加几何字段 AddGeometryColumn(, , , , , )
删除几何字段 DropGeometryColumn(, , )
检查数据库几何字段并在geometry_columns中归档 Probe_Geometry_Columns()
给几何对象设置空间参考(在通过一个范围做空间查询时常用) ST_SetSRID(geometry, integer)

几何对象关系函数
获取两个几何对象间的距离 ST_Distance(geometry, geometry)
如果两个几何对象间距离在给定值范围内,则返回TRUE ST_DWithin(geometry, geometry, float)
判断两个几何对象是否相等
(比如LINESTRING(0 0, 2 2)和LINESTRING(0 0, 1 1, 2 2)是相同的几何对象) ST_Equals(geometry, geometry)
判断两个几何对象是否分离 ST_Disjoint(geometry, geometry)
判断两个几何对象是否相交 ST_Intersects(geometry, geometry)
判断两个几何对象的边缘是否接触 ST_Touches(geometry, geometry)
判断两个几何对象是否互相穿过 ST_Crosses(geometry, geometry)
判断A是否被B包含 ST_Within(geometry A, geometry B)
判断两个几何对象是否是重叠 ST_Overlaps(geometry, geometry)
判断A是否包含B ST_Contains(geometry A, geometry B)
判断A是否覆盖 B ST_Covers(geometry A, geometry B)
判断A是否被B所覆盖 ST_CoveredBy(geometry A, geometry B)
通过DE-9IM 矩阵判断两个几何对象的关系是否成立 ST_Relate(geometry, geometry, intersectionPatternMatrix)
获得两个几何对象的关系(DE-9IM矩阵) ST_Relate(geometry, geometry)

几何对象处理函数:
获取几何对象的中心 ST_Centroid(geometry)
面积量测 ST_Area(geometry)
长度量测 ST_Length(geometry)
返回曲面上的一个点 ST_PointOnSurface(geometry)
获取边界 ST_Boundary(geometry)
获取缓冲后的几何对象 ST_Buffer(geometry, double, [integer])
获取多几何对象的外接对象 ST_ConvexHull(geometry)
获取两个几何对象相交的部分 ST_Intersection(geometry, geometry)
将经度小于0的值加360使所有经度值在0-360间 ST_Shift_Longitude(geometry)
获取两个几何对象不相交的部分(A、B可互换) ST_SymDifference(geometry A, geometry B)
从A去除和B相交的部分后返回 ST_Difference(geometry A, geometry B)
返回两个几何对象的合并结果 ST_Union(geometry, geometry)
返回一系列几何对象的合并结果 ST_Union(geometry set)
用较少的内存和较长的时间完成合并操作,结果和ST_Union相同 ST_MemUnion(geometry set)

几何对象存取函数:
获取几何对象的WKT描述 ST_AsText(geometry)
获取几何对象的WKB描述 ST_AsBinary(geometry)
获取几何对象的空间参考ID ST_SRID(geometry)
获取几何对象的维数 ST_Dimension(geometry)
获取几何对象的边界范围 ST_Envelope(geometry)
判断几何对象是否为空 ST_IsEmpty(geometry)
判断几何对象是否不包含特殊点(比如自相交) ST_IsSimple(geometry)
判断几何对象是否闭合 ST_IsClosed(geometry)
判断曲线是否闭合并且不包含特殊点 ST_IsRing(geometry)
获取多几何对象中的对象个数 ST_NumGeometries(geometry)
获取多几何对象中第N个对象 ST_GeometryN(geometry,int)
获取几何对象中的点个数 ST_NumPoints(geometry)
获取几何对象的第N个点 ST_PointN(geometry,integer)
获取多边形的外边缘 ST_ExteriorRing(geometry)
获取多边形内边界个数 ST_NumInteriorRings(geometry)
同上 ST_NumInteriorRing(geometry)
获取多边形的第N个内边界 ST_InteriorRingN(geometry,integer)
获取线的终点 ST_EndPoint(geometry)
获取线的起始点 ST_StartPoint(geometry)
获取几何对象的类型 GeometryType(geometry)
类似上,但是不检查M值,即POINTM对象会被判断为point ST_GeometryType(geometry)
获取点的X坐标 ST_X(geometry)
获取点的Y坐标 ST_Y(geometry)
获取点的Z坐标 ST_Z(geometry)
获取点的M值 ST_M(geometry)

几何对象构造函数
参考语义:
Text:WKT
WKB:WKB
Geom:Geometry
M:Multi
Bd:BuildArea
Coll:Collection ST_GeomFromText(text,[])

ST_PointFromText(text,[])
ST_LineFromText(text,[])
ST_LinestringFromText(text,[])
ST_PolyFromText(text,[])
ST_PolygonFromText(text,[])
ST_MPointFromText(text,[])
ST_MLineFromText(text,[])
ST_MPolyFromText(text,[])
ST_GeomCollFromText(text,[])
ST_GeomFromWKB(bytea,[])
ST_GeometryFromWKB(bytea,[])
ST_PointFromWKB(bytea,[])
ST_LineFromWKB(bytea,[])
ST_LinestringFromWKB(bytea,[])
ST_PolyFromWKB(bytea,[])
ST_PolygonFromWKB(bytea,[])
ST_MPointFromWKB(bytea,[])
ST_MLineFromWKB(bytea,[])
ST_MPolyFromWKB(bytea,[])
ST_GeomCollFromWKB(bytea,[])
ST_BdPolyFromText(text WKT, integer SRID)
ST_BdMPolyFromText(text WKT, integer SRID)

PostgreSQL+PostGIS的使用 2

三、 PostGIS中空间信息处理的实现

1. spatial_ref_sys表

在基于PostGIS模板创建的数据库的public模式下,有一个spatial_ref_sys表,它存放的是OGC规范的空间参考。我们取我们最熟悉的4326参考看一下:

它的srid存放的就是空间参考的Well-Known ID,对这个空间参考的定义主要包括两个字段,srtext存放的是以字符串描述的空间参考,proj4text存放的则是以字符串描述的PROJ.4 投影定义(PostGIS使用PROJ.4实现投影)。

4326空间参考的srtext内容:
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],TOWGS84[0,0,0,0,0,0,0],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.01745329251994328,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

4326空间参考的proj4text内容:
+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs

2. geometry_columns表

geometry_columns表存放了当前数据库中所有几何字段的信息,比如我当前的库里面有两个空间表,在geometry_columns表中就可以找到这两个空间表中几何字段的定义:

其中f_table_schema字段表示的是空间表所在的模式,f_table_name字段表示的是空间表的表名,f_geometry_column字段表示的是该空间表中几何字段的名称,srid字段表示的是该空间表的空间参考。

3. 在PostGIS中创建一个空间表

在PostGIS中创建一个包含几何字段的空间表分为2步:第一步创建一个一般表,第二步给这个表添加几何字段。

以下先在test模式下创建一个名为cities的一般表:
create table test.cities (id int4, name varchar(20))

再给cities添加一个名为shape的几何字段(二维点):
select AddGeometryColumn('test', 'cities', 'shape', 4326, 'POINT', 2)

4. PostGIS对几何信息的检查

PostGIS可以检查几何信息的正确性,这主要是通过IsValid函数实现的。
以下语句分辨检查了2个几何对象的正确性,显然,(0, 0)点和(1,1)点可以构成一条线,但是(0, 0)点和(0, 0)点则不能构成,这个语句执行以后的得出的结果是TRUE,FALSE。

select IsValid('LINESTRING(0 0, 1 1)'), IsValid('LINESTRING(0 0,0 0)')
默认PostGIS并不会使用IsValid函数检查用户插入的新数据,因为这会消耗较多的CPU资源(特别是复杂的几何对象)。当你需要使用这个功能的时候,你可以使用以下语句为表新建一个约束:
ALTER TABLE cities
ADD CONSTRAINT geometry_valid
CHECK (IsValid(shape))

这时当我们往这个表试图插入一个错误的空间对象的时候,会得到一个错误:
INSERT INTO test.cities ( shape, name )
VALUES ( GeomFromText('LINESTRING(0 0,0 0)', 4326), '北京');

ERROR: new row for relation "cities" violates check constraint "geometry_valid"
SQL 状态: 23514

5. PostGIS中的空间索引

数据库对多维数据的存取有两种索引方案,R-Tree和GiST(Generalized Search Tree),在PostgreSQL中的GiST比R-Tree的健壮性更好,因此PostGIS对空间数据的索引一般采用GiST实现。

以下的语句给sde模式中的cities表添加了一个空间索引shape_index_cities,在pgAdmin中也可以通过图形界面完成相同的功能。
CREATE INDEX shape_index_cities
ON sde.cities
USING gist
(shape);

另外要注意的是,空间索引只有在进行基于边界范围的查询时才起作用,比如“&&”操作。

PostgreSQL+PostGIS的使用 1

二、 PostGIS中的几何类型

PostGIS支持所有OGC规范的“Simple Features”类型,同时在此基础上扩展了对3DZ、3DM、4D坐标的支持。

1. OGC的WKB和WKT格式

OGC定义了两种描述几何对象的格式,分别是WKB(Well-Known Binary)和WKT(Well-Known Text)。

在SQL语句中,用以下的方式可以使用WKT格式定义几何对象:
POINT(0 0) ——点
LINESTRING(0 0,1 1,1 2) ——线
POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1)) ——面
MULTIPOINT(0 0,1 2) ——多点
MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4)) ——多线
MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1))) ——多面
GEOMETRYCOLLECTION(POINT(2 3),LINESTRING((2 3,3 4))) ——几何集合

以下语句可以使用WKT格式插入一个点要素到一个表中,其中用到的GeomFromText等函数在后面会有详细介绍:
INSERT INTO table ( SHAPE, NAME )
VALUES ( GeomFromText('POINT(116.39 39.9)', 4326), '北京');

2. EWKT、EWKB和Canonical格式

EWKT和EWKB相比OGC WKT和WKB格式主要的扩展有3DZ、3DM、4D坐标和内嵌空间参考支持。

以下以EWKT语句定义了一些几何对象:
POINT(0 0 0) ——3D点
SRID=32632;POINT(0 0) ——内嵌空间参考的点
POINTM(0 0 0) ——带M值的点
POINT(0 0 0 0) ——带M值的3D点
SRID=4326;MULTIPOINTM(0 0 0,1 2 1) ——内嵌空间参考的带M值的多点

以下语句可以使用EWKT格式插入一个点要素到一个表中:
INSERT INTO table ( SHAPE, NAME )
VALUES ( GeomFromEWKT('SRID=4326;POINTM(116.39 39.9 10)'), '北京' )

Canonical格式是16进制编码的几何对象,直接用SQL语句查询出来的就是这种格式。

3. SQL-MM格式

SQL-MM格式定义了一些插值曲线,这些插值曲线和EWKT有点类似,也支持3DZ、3DM、4D坐标,但是不支持嵌入空间参考。

以下以SQL-MM语句定义了一些插值几何对象:
CIRCULARSTRING(0 0, 1 1, 1 0) ——插值圆弧
COMPOUNDCURVE(CIRCULARSTRING(0 0, 1 1, 1 0),(1 0, 0 1)) ——插值复合曲线
CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)) ——曲线多边形
MULTICURVE((0 0, 5 5),CIRCULARSTRING(4 0, 4 4, 8 4)) ——多曲线
MULTISURFACE(CURVEPOLYGON(CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),(1 1, 3 3, 3 1, 1 1)),((10 10, 14 12, 11 10, 10 10),(11 11, 11.5 11, 11 11.5, 11 11))) ——多曲面

PostgreSQL+PostGIS的使用 0

一、 PostgreSQL与PostGIS的关系

PostgreSQL是世界上技术最先进的开源数据库,其前身是1977年一个源于Berkeley名为Ingres的非关系型数据库,其项目领导人为Michael Stonebraker教授。1982年该教授商业化了Ingres;1985年,Michael Stonebraker教授回到Berkeley,开始对新的数据库设计进行研究,并于次年在美国防务高级研究项目局(DARPA)、陆军研究办公室(ARO)、国家科学基金(NSF)以及ESL, Inc等机构的赞助下启动了Postgres(Post-Ingres)项目。

Postgres在1987年形成第一个Demo,1989年发布第一个版本,直到1993年的4.2版本,由于外部用户过多,做技术支持和维护源代码的时间影响到了对数据库的研究,因此Berkeley中止了该项目。在此期间,Postgres项目就已经被使用在了一些GIS系统中。

Postgres项目并未就此消亡,在1994年两个Berkeley的研究生向Postgres中加入了SQL语言解释器,将之改名为Postgre95并发布到了互联网上。经过一些黑客的修改,1996年Postgres95再次更名为PostgreSQL,并采用BSD许可证发布了第一个开源版本。经过多年发展,PostgreSQL已经发展成为一个技术非常先进的开源数据库,其支持特性之多性能之强可与诸多高级商业数据库比肩。

这里提供了一个世界上主流数据库的特性比较,有兴趣的可以从中管窥PostgreSQL在数据库领域中的成就。
http://en.wikipedia.org/wiki/Comparison_of_relational_database_management_systems

PostGIS则是PostgreSQL的一个扩展,目的是使PostgreSQL支持空间数据的存储和使用,其本质类似于ArcSDE和Oracle Spatial Extension。PostGIS是采用GPL许可发布的,完整地实现了OGC的《Simple Features Specification for SQL》规范,并于2006年获得OGC认证。在此基础上,PostGIS还对规范进行了一些扩展,在后面的特性中我们可以慢慢了解到。

2009年4月22日星期三

SDE与PostGIS的集成

PostgreSQL:http://www.postgresql.org/
PostGIS:http://postgis.refractions.net/
PostGIS Manual:http://postgis.refractions.net/documentation/manual-1.3/

安装PostgreSQL
这里没有使用SDE自带的PostgreSQL,而是独立的PostgreSQL 8.3.6.

安装PostGIS扩展
可以通过PostgreSQL的Application Stack Builder安装,或者下载独立安装包。这里下载了PostGIS 1.3.5安装包进行安装。

这个版本在安装PostGIS的时候会提示错误,原因是PostgreSQL锁住了这个文件,因此先停掉PostgreSQL,但安装完成后必然造成初始化PostGIS数据库失败,可以在PostgreSQL启动后再运行一次PostGIS安装程序,再见到这个错误的时候忽略。

这时通过pgAdmin连接到数据库,可以看到PostGIS创建了相关的数据库。

安装ArcSDE for PostgreSQL
在安装ArcSDE for PostgreSQL之前,首先新建一个基于PostGIS模板的数据库用于ArcSDE。注意,使用sde角色需要有管理员权限并可创建数据库对象。

然后安装ArcSDE相关程序文件,接着运行Post Installation。

我们希望SDE在保存数据的时候使用PG_GEOMETRY格式保存空间数据,而不是默认的ST_GEOMETRY,因为使用PG_GEOMETRY格式可以使用PostGIS的功能。这里需要自定义“dbtune.sde”文件 。将这个文件备份后,修改以下这段:
GEOMETRY_STORAGE "ST_GEOMETRY"
为:
GEOMETRY_STORAGE "PG_GEOMETRY"
然后,选择这个修改过的“dbtune.sde”文件。

以下与其它ArcSDE Post Installation类似。

测试SDE图层使用PostGIS功能
在ArcCatalog中新建到ArcSDE的连接:

然后使用ArcCatalog导入若干Feature Class到SDE,这里使用了Sample的World中的cities等图层进行测试:

打开pgAdmin,刷新sde数据库中的sde模式,查询cities表:

下面测试一下不通过ArcSDE直接对这个表进行PostGIS空间查询。打开SQL命令窗口,测试使用如下SQL:
SELECT * FROM cities WHERE ST_Distance(shape, GeomFromText('POINT(116.39 39.9)', 4326))<10

以上空间查询SQL代表查找在4326空间参考下,与点(116.39, 39.9)【北京附近】距离10以内的所有城市,查询结果如下,总共29条记录:

2009年4月21日星期二

ArcGIS Server Java ADF 案例教程 16

三 WebContext的refresh方法

提示:本小节内容是ADF内部实现机制,对ADF的使用没有影响,如没有兴趣可以跳过。

当调用WebContext的refresh方法的时候, ADF会同步WebContext中所有相关属性。比如当我改变了WebMap的视图范围,然后再调用WebContext的refresh方法,那么ADF会通知WebContext中的WebOverview等对象去同步相关信息,比如同步改变视图范围等。以上过程ADF在后台是通过Observer机制实现的。当ADF调用WebContext的refresh方法时,ADF会通知所有注册到WebContext的WebContextObserver 对象,调用其update方法。实现WebContextObserver这个接口的大概有如下这些类:AWSTocFunctionality, ExtentHistory, MapToolsTask, PrintTask, RoutingTask, SearchAttributesTask, WebMap, WebOverview, WebScaleBar。

以上对WebContext的刷新都是在服务器端。但是,当一个Command被点击以后,服务器端的更新又是怎么被输出到浏览器端的呢?下面就让我们从浏览器端开始,详细地了解一下Command的工作原理。

以下是上一节的案例中用a:command标签在JSF页面中定义了一个Command,在下面我们看一下这段标签在浏览器中相应的输出:

以下是上面的标签在浏览器中的输出:
var zoomIn = new EsriMapServerAction("zoomIn", "");
zoomIn.clientPostBack = true;
zoomIn.defaultImage = "/AgsDemo/images/tasks/maptools/zoomin.png";
zoomIn.hoverImage = "/AgsDemo/images/tasks/maptools/zoominU.png";
zoomIn.selectedImage = "/AgsDemo/images/tasks/maptools/zoominD.png";
zoomIn.isDisabled = false;
zoomIn.showLoading = true;
toolbar.addToolItem(zoomIn);


可见,一个Command对应输出的是EsriMapServerAction这个JavaScript对象,这个对象在“/WebContent/js/esri_map.js”中定义。打开EsriMapServerAction的定义你会发现,当点击这个Command的时候,ADF会提交当前页面;当然,由于我们设置了clientPostBack属性为TRUE,因此交互通过AJAX方式进行,其回调函数为EsriControls的processPostBack函数——EsriControls在“/WebContent/js/esri_core.js”中定义,它包含了所有的ADF客户端组件。

提示:以上这段这里说的很简单,其实包含很多的内容,有兴趣的话可以在这几个JavaScript脚本中仔细研究相关对象和调用关系。

ADF在服务器端会有一个PostBackPhaseListener 去监听所有的AJAX请求,有兴趣的话你可以解开“/WebContent/WEB-INF/lib/arcgis_webcontrols.jar”这个库文件,在其中的“META-INF”目录下有一个“faces-config.xml”中定义了这个PhaseListener。

AJAX请求的参数中会有一个“doPostBack”值,当一个AJAX请求发到服务器的时候,PostBackPhaseListener会监听到这个请求并找到“doPostBack”这个值从而执行后续的操作。当这些操作完成以后,PostBackPhaseListener通过AJAXUtil 的writeResponse方法输出AJAX响应到请求的浏览器。

2009年4月20日星期一

ArcGIS Server Java ADF 案例教程 15

二 【案例】自定义Command使地图等比放大

在这一小节,我们通过自定义Command定制一个按钮,这个按钮每点击一次,地图会放大1倍。通过这个简单案例,让我们来熟悉一下Command的用法。

实现这个功能的主要环节是实现自己的监听器类,以下是这个类的定义:
import com.esri.adf.web.data.WebContext;
import com.esri.adf.web.util.WebUtil;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.faces.event.ActionListener;

public class ZoomInListener implements ActionListener
{
public static double ZOOM_FACTOR = 0.5;

public void processAction(ActionEvent event) throws AbortProcessingException
{
WebContext webContext = WebUtil.getWebContext(event.getComponent());
webContext.getWebMap().getCurrentExtent().expand(ZOOM_FACTOR);
webContext.refresh();
}
}

当然,在JSF页面中的用法和上面类似,也很简单:

这个ZoomInListener监听器类主要需要实现了ActionListener的processAction方法,当用户点击这个id为“zoomIn”的Command的时候,这个监听器会被触发,然后其processAction方法就会被执行。

在这个processAction方法中,我们首先获得WebContext对象,通过WebContext我们就可以获得WebMap对象,进而对它的currentExtent属性进行操作(地图范围缩小1倍),再刷新WebContext对象。这个过程很简单,中间有两个环节这里稍作解释。

一个环节是如何获得WebContext对象。processAction是JSF的ActionListener接口的一个方法,这个方法的参数是ActionEvent ,因此它的属性与ADF基本没有关系,所以,获得WebContext就需要用到ADF的WebUtil工具类,ESRI的工程师为了可以在各种环境下获得WebContext这个ADF中非常重要的环境,制造了WebUtil这个工具类及其getWebContext方法,通过这个方法我们可以很方便地获得WebContext对象。

另外一个环节就是WebContext对象的refresh方法,我们在对服务器端的WebMap对象进行一些操作以后,调用了WebContext的refresh方法以后,浏览器中的地图就会刷新成新的视图,其相关的TOC、Overview等组件也会跟着刷新,这很奇妙吧。

ArcGIS Server Java ADF 案例教程 14

Command、Tool和Button

这一章我们将讨论的主要对象是Toolbar的孩子们和亲戚,涉及到两个孩子:Command和Tool,还有一个亲戚:Button。它们是基于ADF对WebGIS功能进行扩展的第一步,它可以让你在地图工具条中加入若干自定义的按钮来执行特定的功能。

在第三章中我们把Toolbar列为ADF常用组件之一,但是它和Map、TOC、Overview等其它组件是有区别的,我们打开“context-attributes.xml”配置文件可以发现,Map、TOC、Overview等组件都有相应的JavaBean定义,但是Toolbar没有——因为它不需要在服务器端绑定一个“WebToolbar”对象来记录Toolbar的状态信息。

下面让我们分别来看看这些组件的用法吧。

一 Command
Command在Toolbar中表现为一个按钮,当用户点击这个按钮以后,ADF将会立即执行一些操作。点击Command相当于直接给ADF一个命令,它并不改变当前地图的状态,比如当前地图正在进行漫游,点击Command以后再对地图进行操作时它还是漫游状态。

最典型的Command比如“地图全图”功能,当用户点击以后,当前地图会缩放到全图显示。下面的代码定义了这样一个Command。

从这段代码我们可以看到,Command的最关键的用法在于定义其子元素“f:actionListener”,它的“type”属性指向一个实现了某些功能的监听器类(注意,并不是对象)。这里的“ZoomFullExtentListener”是ADF自带的监听器;当然,这个类也可以是任何实现了ActionListener 接口的类,我们在后面的案例会实现我们自己的监听器类。

下面我们再来看两个稍微有点不同的Command定义。这两个Command的功能是提供两个按钮“后退”、“前进”,当点击“后退”时地图会返回到上一视图范围;当点击“前进”时地图会前进到后一视图范围。下面的代码定义了这样两个Command。

这里并没有使用监听器,而是使用了Command的action属性。这个属性将绑定一个服务器端对象的方法,当点击这个Command的时候,ADF会去调用这个服务器端方法。“后退”、“前进”的功能主要使用了ADF中的ExtentHistory 对象,它可以维护地图的视图范围历史记录。下面它是在“context-attributes.xml”中的定义:

当我们点击“后退”按钮的时候,ADF会调用这个服务器端“history”对象的doPrevious方法,因此地图会切换到上一视图;点击“前进”也是类似。另外,这两个Command的disabled属性分别绑定到“history”对象的canUndo和canRedo属性,这也很好理解,当地图已经是最新的视图的时候,用户当然应该不可以点击“前进”这个按钮,此时“history”的canRedo属性值为TRUE,因此“前进”这个按钮会呈现不可用的状态。

稍微总结一下,上面主要介绍了Command的两种定义用法,一种定义监听器类作为Command的子元素;另外一种绑定服务器端方法到Command的action属性。对于自定义Command来说,一般使用监听器会比较容易一些。

2009年4月17日星期五

基于PHP的Flex Socket安全策略设置

Flex打开Socket连接前会按几个步骤检查安全策略配置,如不成功将报安全沙箱错误。最先检查的就是目标主机的843端口是否提供了安全策略配置,因此在843端口监听Socket请求并输出安全策略配置是最直接的解决Flex Socket连接沙箱错误的办法。

如下PHP代码在服务器端被执行以后将会完成上述工作:

在Window中,在控制台中执行“telnet localhost 843”命令后如果可以输出安全策略文件,表明设置成功。

B/S架构中的数据推送技术

B/S架构中服务器向浏览器推送数据在很多场合都有需求,比如实时的监控报警、实时的调度,等等。凡是对实时性要求越高的场景,越是需要服务器及时、准确地向浏览器推送数据。这里我们就讨论一下在B/S架构下,可以实现从服务器向浏览器推送数据的几种技术及其相应的特点。

基于HTTP协议

1. HTTP协议的特点

纯的HTTP协议在本质上是无状态、无连接的,它基于请求/响应的工作模式,使得浏览器在每次发生请求的时候和服务器建立连接,当接收到响应的时候断开连接;在这种情况下,要让服务器主动向浏览器发送数据是不可能的。

技术总是为需求服务的,在很多浏览器需要及时获取服务器数据更新的需要下,技术人员们变通地发明了一些基于HTTP协议的“伪”长连接技术,实现了服务器数据向浏览器的“准”推送。

2. 定时刷新

定时刷新是最粗糙的实现快速感知服务器数据变化的方法,原理就是不停地刷新页面从而显示最新的服务器端数据。定时刷新从技术上大体有两种实现方法:一是通过HTML的META标签设置页面刷新间隔,比如以下的标签会在浏览器中每隔10秒刷新当前页面:
<meta equiv="refresh" content="10" />

这种方式会看到页面有明显的刷新,用户体验会比较差;与此相对应的另外一种方式就是AJAX,在JavaScript脚本中添加一个定时器,每隔一定时间向服务器发送一个AJAX请求,得到响应以后去更新页面的内容。以下JavaScript代码会每隔10秒发送一个AJAX请求去更新当前页面的部分内容:
setInterval(function(){sendAJAXRequest()},10000);

如图 1是定时刷新的模型图:
图 1 定时刷新的模型图

定时刷新这种模式相当于一个下级每隔一段时间打电话给上级,请示当前指示。当然,如果在这个时间段内发生了一些情况,他们之间是没办法沟通的,所以信息并不是“实时”的;另外,刷新间隔到底设置多少合适也是个问题,服务器的负担也比较大。

3. 轮询
轮询也是通过AJAX进行的,它与AJAX定时刷新的区别仅在于接收到AJAX响应以后的工作。定时刷新完全是浏览器主动的,在浏览器和服务器之间存在一定的断开间隔;而轮询这种方式会在AJAX响应中再次发送AJAX请求,也就是说当响应结束,浏览器和服务器之间一旦断开连接的时候,浏览器会再次发送请求建立连接。

如图 2是轮询的模型图:
图 2 轮询的模型图

轮询这种模式相当于一个下级不停给上级打电话请示,上级指示下达以后挂了电话,下级可能去执行指示或者不执行,但是随即下级将再次给上级打电话继续请示。这种模式可以保证数据更新比较实时,但是服务器负担也是一个问题。

顺便提一下,轮询这种模式一般会在浏览器的XMLHttpRequest的readyState为4(响应数据传输结束)的时候调用回调函数,此时连接已经关闭;但是在Firefox中支持Streaming AJAX模式,也就是XMLHttpRequest的readyState为3(响应数据仍在传输)的时候调用回调函数,此时连接尚未关闭。

4. 基于iframe的流模式

iframe可以在当前页面中嵌入一个文档页面,如果把这个页面设为隐藏,并将其src属性设置为一个特殊的请求,数据将会源源不断地从服务器发送到浏览器。

比如以下的JSP页面,它将每隔10秒更新一个数输出到浏览器,不出意外它将会永远执行下去,浏览器和服务器之间的连接也永远不会关闭:
<% int i = 1; try { while (true) { out.print("

"+(i++)+"

");
out.flush();

try {
Thread.sleep(10000);
} catch (InterruptedException e) {}
}
} catch (Exception e) {}
%>


但是这样做有个很显然的不足,由于响应始终没有结束,因此浏览器里面的加载进度条会始终显示加载没有完成。当然,这也并不是没有办法解决,比如http://www.zeitoun.net/articles/comet_and_php/start就提供了IE、Opera、WebKit核心(Chrome/Safari)、Gecko核心(Firefox)等浏览器的解决方案。

如图 3是基于iframe流模式的模型图:
图 3 基于iframe流模式的模型图

基于iframe的流模式相当于一个下级给上级打电话,上级不停发出指示,下级一边接收指示、一边执行。这种模式有着比较好的实时性,比如Gmail就是采用这种模式。

5. 开源框架Pushlets
Pushlets是一个实现了AJAX轮询和iframe流模式的开源框架(Java+JavaScript),对此有兴趣的可以参考:http://www.pushlets.com/,Pushlets采用LGPL许可。

基于消息

1. 基于消息的架构

使用消息可以实现松散耦合的分布式数据通讯。通过消息从服务器向浏览器推送数据一般需要一个消息中间件(Message Oriented Middleware,MOM),服务器将数据推送给消息中间件,消息中间件再将数据以消息的方式推送给浏览器。基于消息的架构有个特别大的优点,那就是不但可以实现服务器向单浏览器、服务器向多浏览器推送数据;还支持浏览器到浏览器之间的数据推送。这将会在预警、调度等场合有非常大的用武之地。

图 4和图 5分别是基于订阅/发布模式和点到点模式的消息传递示意。在B/S架构中,服务器可以发布消息,所有订阅该主题的浏览器都会接受到该消息,这就实现了从服务器向多浏览器的数据推送;浏览器或者服务器也可以向特定的对象发送消息,消息将在一个消息队列中被发送,对方浏览器就可以收到该消息,这就实现了服务器向某特定浏览器或者浏览器之间的数据推送。

2. Java消息服务(JMS)


JMS是一组公开的Java API,它定义了与消息相关的接口和语义,目前JMS已经成为J2EE中的重要组成部分。
图 6 JMS工作原理

如图 6是JMS工作原理。当有客户端连接到JMS服务器的时候,JMS的连接工厂会根据连接类型来创建一个虚拟连接,这个连接会具体负责消息的传递;在这个连接建立完成后,会产生一个会话,会话中保存了消息生产者或消息消费者的信息;消息的消费者会对感兴趣的消息目的地(队列或主题)建立一个监听,消息的生产者则负责把消息发送到这个目的地上。

另外JMS还有一些值得一提的特点,比如支持事务性会话、可以设置消息的持久性、设置消息的优先级、允许消息过期、可以构建长期订阅等。这些特性在各种企业级应用环境下都有可能提高应用的功能或性能。

实现JMS的商业中间件有IBM MQSeries、BEA WebLogic JMS等;开源中间件有OpenJMS、Apache ActiveMQ等。

3. ActiveMQ

ActiveMQ是Apache基金会的著名开源项目,目前Release版本5.2完整支持JMS1.1和J2EE 1.4规范。ActiveMQ采用Apache 2.0许可发布。ActiveMQ的优势在于其支持集群部署、支持多种应用层协议和诸多客户端开发语言等特点。

ActiveMQ主要支持以下协议:
 OpenWire
 REST
 Stomp
 WS Notification
 XMPP
 AMQP

下面这里将使用ActiveMQ和Stomp协议来演示各种方式的数据的推送。

4. Stomp协议

Stomp是一种简单、实现容易的协议,因此支持非常广泛,这里采用Stomp协议的主要原因也是因为其支持的客户端开发语言最多,在各种环境下都有用武之地。这些开发语言主要包括:
 ActionScript 3
 C
 C++
 .Net
 Delphi
 Perl
 PHP
 Python
 Ruby

下面主要用到了ActionScript和.Net语言。

5. 发布/订阅模式的实现

发布/订阅模式适用于广播性质的数据推送。比如在实时监控系统中,当我们的指令中心需要向所有监控目标发送信息的时候,这种模式就比较适合。为了简单起见,这里的服务器端和浏览器端都使用了ActionScript实现。

在浏览器端,我们需要订阅服务器主题。比如所有的接收指令的目标都需要监听“Alarm”频道,那么在浏览器中的代码应该如下:
private function sub():void
{
var ch:ConnectHeaders = new ConnectHeaders();
stomp.connect("localhost", 61613, ch);
stomp.subscribe( "/topic/Alarm" );
}
private function onStompMessage(event:MessageEvent):void
{
var byteArray:ByteArray = event.message.body;
var str:String = byteArray.toString();
}


图 7 发布/订阅模式的数据推送

6. 点对点模式的实现

点对点模式适合类似调度的功能场景。比如在监控系统中,当某个指令需要下达给具体某个目标的时候,就需要点对点模式的数据推送。

实现点对点模式的数据推送需要知道数据发送的目的地,以下代码演示了如何从指令中心“党中央”发送指令到“毒蛇”的过程:
private function sendMsg():void
{
var destination:String = "/queue/毒蛇";
stomp.sendTextMessage(destination, “注意隐蔽”);
}


当然,“毒蛇”需要监听所有发送给他的消息:
private function login():void
{
var ch:ConnectHeaders = new ConnectHeaders();
stomp.connect("localhost", 61613, ch);
stomp.subscribe( "/queue/毒蛇" );
}

图 8 点对点模式的数据推送

7. 实时监控Demo

这里通过消息模式实现了一个实时监控的演示。服务器端是使用.Net Stomp API写的控制台程序,Demo测试使用1000个监控目标,数据发送间隔0.5秒,数据推送到“realmonitor”频道。浏览器端使用Flex,监听“realmonitor”频道。

在服务器端,这个Demo中设置每100个目标信息拼装成一条消息,也就是说每0.5秒会发送10条消息。如果每条消息的信息(坐标和一些属性信息)大约50字节的话,每条消息大概5KB;每秒发送两次,总共大概100KB数据量会被推送。

图 9 Flex中实时监控效果

如图 9是在Flex中实现的实时监控效果。

2009年4月15日星期三

ArcGIS Server Java ADF 案例教程 13

【案例】给地图加上弹出气泡

在这一小节里,我们将会在地图上加一个气泡,很多时候你需要弹出一些简短的信息到地图上,气泡就是为了满足这样的要求的。

首先让我们回忆下这一章的主要内容,前面讲了ADF一些常用的地图组件,然后从Map组件入手详细分析了它在服务器端和浏览器端的具体实现。现在我们的气泡功能主要是对浏览器端的Map进行功能扩展,那么首先应该想到的就是EsriMap这个JavaScript类,我们在EsriMap中弹出一个div,再放上我需要的信息不就可以了么?

等等,在页面上弹出一个div是很简单,但是用户还会操作地图,比如拖动一下啊什么的,那这个div怎么跟地图一起移动呢?再回想下图 9,你应该可以得到答案吧?对了,把这个div放到imageGrid中去。

下面让我们来具体实现这个功能,先看一下效果吧,如图 10。
图 10 地图弹出气泡效果

在这个简单的页面上,我们放置了两个按钮。当点击“显示气泡”按钮的时候,地图上会弹出一个提示框,提示框中有我们想要显示给用户的信息;当点击“隐藏气泡”按钮的时候,这个提示框会隐藏掉;另外,当我们拖动地图的时候,这个气泡也会跟着地图一起移动。

在这里主要封装了两个JavaScript函数,分别绑定到“显示气泡”和“隐藏气泡”按钮的onclick事件上。
我们先来看一下“显示气泡”按钮的事件,它将会触发showPopUp函数,这个函数执行的操作如下:
function showPopUp()
{
var popUpId = 'divPopUp';
var popUp = document.getElementById(popUpId);
if( popUp==null )
{
popUp = document.createElement('div');
popUp.id = popUpId;
popUp.innerHTML = "这是我要弹出的信息";
var map = EsriControls.maps['map'];
map.imageGrid.appendChild(popUp);
}
var style = 'display:block;position:absolute;z-index:2;';
style += 'left:100; top:100;';
style += 'background-image:url(images/wuyf/popup.png);width:400px; height:200px;';
style += 'background-repeat:no-repeat;';
style += 'padding:10px;';
EsriUtils.setElementStyle(popUp, style);
}

首先,我们试图通过getElementById()方法去获得气泡div是否已经存在;如果不存在,则新建这个div并添加到地图上。我们看到,把气泡div(id为divPopUp)添加到地图上的过程实际上就是把这个div添加到EsriMap的imageGrid中去,这主要是通过appendChild(popUp)这个方法实现的。

仅仅把这个div添加到imageGrid以后并不能看到这个它,在下面还有一些其它的设置,否则我们的气泡div会被地图遮挡住。这里主要的概念是CSS中的z-index属性,使用这个属性来控制imageGrid中各个子元素的叠置顺序,imageGrid中地图图片的z-index值为1,因此我们需要给气泡div设置一个比1大的数值才可以。另外,在气泡div的样式中我们还必须通过“display:block;”来设置这个div可见,再通过“position:absolute;”来设置这个div的定位模式,这些都是必须的。

顺便提一句,这里使用的是EsriUtils的setElementStyle()方法来给气泡div来设置CSS属性;你直接用obj.style.attributes=value;的方法来设置也是一样的。

再来看一下“隐藏气泡”按钮的事件,这就简单多了:
function hidePopUp()
{
var popUpId = 'divPopUp';
var popUp = document.getElementById(popUpId);
if( popUp!=null )
{
EsriUtils.setElementStyle(popUp, "display:none;");
}
}


这里事实上只做了一件事情,就是把气泡div的display样式设置成“none”,浏览器就不会去渲染这个气泡了。

通过这个小案例,你是不是对ADF中的Map组件,特别是JavaScript脚本库中的EsriMap有了更加深入的理解了?

2009年4月8日星期三

ArcGIS Server Java ADF 案例教程 12

EsriMap详解

提示:本小节所涉及内容主要由ADF自己完成,分析只是为了更好地了解原理,为将来有可能做的扩展进行准备。

EsriMap在“/WebContent/js/esri_map.js”中定义,让我们从Map组件输出的JavaScript代码开始去了解ADF在浏览器中所做的工作。

下面的代码是一个简单地图输出的JavaScript代码(为了便于阅读,我对某些代码作了等价的修改),其中“AgsTest”是我的项目名称:

现在让我们开始看浏览器一步一步执行的JavaScript代码。除了引入一些ADF的JavaScript脚本,首先将会执行的是位于最上面的一些变量声明和位于最下面的esriInitItems.push("esriMapInitmap()")代码,这里会将esriMapInitmap()这个函数添加到esriInitItems这个初始化数组里面去;然后,在页面加载完成后会触发window.onload事件,以致调用esriInitApp(e)函数;这时,由于esriMapInitmap()已经被添加到esriInitItems中,因此这个函数将被调用。下面让我们仔细看看在esriMapInitmap()中发生了些什么。

首先第一件事情就是实例化一个EsriMap对象,这一步其实有很多内涵,马上就会详细解释;其次,ADF会针对地图中最重要的数据源进行配置,我们在上面的代码可以看到,由于我们使用的是动态Map Service,因此ADF会新建一个EsriMapSourceDynamic(继承自EsriMapSource)对象,并通过EsriMap的addMapSource()方法添加到地图中;然后,addImage()方法会添加这个EsriMapSource对象的图片地址,可以看到,这个图片地址是一个相对路径,以下这个地址表明EsriMapSource中使用的图片格式是MIME 数据,jsessionid参数指示了当前会话的id:
/AgsTest/mimedata;jsessionid=5E13F6727CAA0433934A85EFA1AB8D49?wname=esriWebSession&id=map28171097&uniqueId=24691856-0

下面回过头去详细看一下在实例化EsriMap的时候所发生的事情,这一切相关的操作都封装在了“esri_map.js”这个文件中了,我们可以打开这个文件来看一下EsriMap实例化的详细内容。

首先,EsriMap是继承自EsriControl,关于EsriControl及其相关的内容,有兴趣可以在ADF JavaScript库中继续深入研究。事实上我们对EsriMap感兴趣的应该主要是两点:其一,地图图片在EsriMap中的放置关系;其二,对地图做操作时候EsriMap与ArcGIS Server的通讯。

我们先来看下EsriMap中地图图片的放置关系。EsriMap最外层的div是container,默认id是“EsriMapCell_map”(在“map.xsl”中定义),它是放置地图的容器,一般不作操作;在container中还有几个层次的div:divObject、controlDiv、imageGrid,它们和container的关系如图 9所示。地图图片会放在imageGrid中。
图 9 EsriMap中放置图片的div

下面我们以动态地图为例,看一下当拖动地图时EsriMap是如何与ArcGIS Server通讯并更新图片的。我们首先找到EsriMap中有以下这段代码:
EsriControls.addPostBackTagHandler("map", EsriControls.maps[self.id].updateAsync);

EsriControls在“/WebContent/js/esri_core.js”中定义,它负责管理当前页面上所有的ADF的DHTML组件。以上的代码给EsriMap对象添加了一个回调函数updateAsync,它将会在页面收到AJAX响应并且响应中有“map”标签的时候被调用。

当我们拖动地图的时候,客户端的JavaScript会将需要的地图范围发给服务器(有兴趣可以参考EsriMapPan和EsriMapContinuousPan函数),服务器处理完成后会返回如下的响应:

这时,updateAsync函数就会被调用,它会更新页面上EsriMap相关的一系列div和其它对象的属性值,从而更新地图视图。具体更新的细节,有兴趣可以详细研究下updateAsync函数,这里就不赘述了。

从这个Map组件的服务器端对象和客户端对象,我们已经基本了解了Map的工作原理。ADF的其余几个常用组件:TOC、Toolbar、Overview等也是万变不离其宗,虽各有特点,但大体思路和原理也大概如此,有兴趣的话你还可以做更深入的研究。

ArcGIS Server Java ADF 案例教程 11

Map组件详解
以上几个组件的使用非常简单,不需要很多设置就可以搭起一个有模有样的WebGIS页面了——当然,这是因为ADF在后台为我们做了很多事情。后面我们希望从Map组件入手,看看在一个会话中,ADF是怎么把后台的ArcGIS服务呈现到用户的浏览器,并使之能响应用户操作的。

当一个用户会话开始时,让我们首先看一下Map组件在服务器端绑定的WebMap对象,WebMap对象的定义我们可以在“/WebContent/WEB-INF/context-attributes.xml”中找到。在前面我们已经知道,一个JSF应用在启动的时候会读取“faces-config.xml”文件来配置应用;这里的“context-attributes.xml”虽然不是默认配置文件,但是在这里它也会被读取,因为在“/WebContent/WEB-INF/web.xml”中有如下的配置:

前面我们已经提过,“web.xml”是Java Web工程的部署描述文件,上述这段“web.xml”中的配置会指定JSF应用启动时需要读取的若干配置文件路径,“context-attributes.xml”赫然名列其中。对了,你也可以自己新建一个配置文件,然后在这里进行一下配置,比如我习惯新建一个“context-resources.xml”文件来定义WebContext需要的所有服务资源,然后再在“web.xml”中添加如上虚线框中的内容。

在“context-attributes.xml”中首先就是对WebMap的定义:

这段配置文件的含义就是:在应用中,在有需要的时候会实例化一个名为map的WebMap对象供使用,并且其地图输出格式为24位的PNG图片。

从图 3的结构我们可以看到,这个WebMap对象将会被放置到WebContext的attributes中进行使用和管理。比如按照下面这段“faces-config.xml”中的定义,当得到一个WebContext对象mapContext以后,我们就可以通过mapContext.attributes["map"]就可以得到WebMap对象了:

好,目前为止我们已经知道了在一个会话中,Map组件在服务器上绑定的WebMap对象如何生成,并且如何引用了。下面让我们看看Map组件和WebMap怎么配合渲染成浏览器中的一个地图的。

前面在JSP页面中我们是通过“a:map”标签来定义一个Map组件的,实际在ADF中,其对应的JSF组件为MapControl 。MapControl中包含WebMap、MapRenderer 、XslUrl等属性,WebMap负责维护地图的功能,MapRenderer负责将其输出成XML,最后再通过XslUrl指定的XSL文件将XML解释为DHTML输出。

打开“/src/xsl”目录,我们会发现下面有很多“*.xsl”文件,其中的“map.xsl”就是我们的MapControl渲染时候默认使用的XSL文件。当有用户向服务器发送请求的时候,JSF会首先创建或者还原MapControl这个视图对象,然后再使用其MapRenderer按照XSL文件渲染成DHTML响应输出给浏览器。

你可以运行下你的应用,然后查看地图页面的源代码,从中会发现类似以下的代码,这些就是Map组件的MapRenderer渲染的。

在这里出现了EsriMap这个JavaScript类, 它将会实例化浏览器端的地图对象。在下面这一节里,我们将会对EsriMap做详细解释。这里先稍微做一下总结,如图 8,借用帮助里的一张图片,应该可以帮助你了解ADF组件的基本原理。
图 8 Map组件的生命周期

2009年4月3日星期五

ArcGIS Server Java ADF 案例教程 10

几个ADF常用组件
图 6 ADF常用组件

如图 6所示,ADF中几个与地图相关的组件的命名和功能都和ArcEngine很类似,主要包括Map、Toolbar、TOC、Overview等。我们先大致看一下这几个组件的用法和大概功能。
Map应该是所有ADF应用中都要使用的组件,它是一个地图的容器,内置了对各种地图服务的使用和操作。它在服务器端绑定的是一个WebMap 对象。以下标签在页面中添加了一个Map组件:

Toolbar是绑定到Map的工具条,在Toolbar中可以添加Tool和Command以实现相应的地图操作,而它本身仅仅是一个容器。以下标签在页面中添加了一个Toolbar组件,同时绑定到id为“map”的Map组件,并在其中放置了一个可以设置当前地图操作为“Pan”的Tool:

TOC是Table Of Contents的缩写,它提供一个面板来显示当前地图的图层信息,并可以进行隐藏/显示图层、缩放到图层等操作。它在服务器端绑定的是一个WebToc 对象。以下标签在页面中添加了一个TOC组件,同时绑定到id为“map”的Map组件:

Overview提供鹰眼图功能,它在一个全图的地图上用一个矩形框来表示当前地图的视图范围。它在服务器端绑定的是一个WebOverview 对象。以下标签在页面中添加了一个Overview组件,同时绑定到id为“map”的Map组件: