一般情况下,一个ArcGIS Engine 工程师转向Web端开发,如果没有相关的web思想,很难转换这个角色,但是如果你有ArcGIS Engine的开发经验,势必会对Web API的开发有很大的帮助,至少今天的主题需要你有ArcGIS Engine开发的经验
Web API 已经提供了足够多个功能供开发者去完善相关的业务功能,特别是GP Service的加入,让Web API在分析方面如虎添翼,但是不可否认,Web技术由于受到网络的限制,浏览器的限制,一些大数据量的分析可能让Web开发者比较挠头,而且如果想实现目前WebAPI还没有提供的功能,基本上无路可走,但是幸好,我们可以使用ArcObject的资源来完善我们的Web应用,也就是ArcGIS for Server的SOE的扩展。
参考文档
ArcGIS for Server的SOE扩展:http://resources.arcgis.com/en/help/arcobjects-net/conceptualhelp/#/What_is_a_server_object_extension/0001000000zv000000/
关于GP和SOE 的对比问题
这方面不是本博客的主题,但是有必要简单给大家进行说明一下:
1.GP适合复杂业务流程,较低并发下的应用
2.SOE适合单一功能的模块,较高并发下的应用
SOE开发环境
ArcObject10.1、VS2010(AO支持的版本)、ArcGIS10.1 for Server、ArcSDE10.1、ArcGIS for JS 3.9
开发目的
一个地图服务,该地图服务包含一个subway的点图层,该图层有一个name字段,存储相关的地铁站名称,比如东直门站、北京站,使用一个查询条件获得某一个点要素,然后再进行缓冲区(半径1000),然后进行叠加分析,获得该缓冲区下所有的地铁站要素信息,在web端进行渲染。
当然该需求不管通过ArcGIS for JS基础API或者GP Service都可以实现,但是本次主题的目的就是通过这个例子来了解一下ArcGIS for Server的SOE扩展的开发流程。
开发思路
1:首先我们需要进行核心需求的ArcObject代码编程实现,也就是获得要素类对象,然后根据用户提供的属性条件过滤,获得相关要素,然后进行buffer,然后再对该要素类进行空间查询获得符合条件的数据,其实这个熟悉AO开发的是一个非常简单的功能,但是如果希望进行SOE扩展就需要了解SOE开发的相关问题
2:在VS上创建SOE程序,系统会自动生成一个相关模板,我们以开放REST为例
生成完毕之后,我们就可以看到相关的模板代码。
为了便于用户了解SOE的生命周期和对应接口,我觉得先给大家介绍SOE的部署和调试,如果我们会使用调试,那么我们就可以根据相关模板代码,一步步的进行了解SOE是怎样一个相关原理。
SOE部署
我们生成了相关的SOE版本,在ArcGIS 10.2版本,支持了影像服务的扩展,所以在我们需要在模板里面输入支持地图服务还是影像服务。如下所示“MapServer”。
[ServerObjectExtension("MapServer",
AllCapabilities = "",
DefaultCapabilities = "",
Description = "",
DisplayName = "RestSOE2",
Properties = "",
SupportsREST = true,
SupportsSOAP = false)]
我们需要在初始化函数Init里面添加如下代码,才能进入调试状态
public void Init(IServerObjectHelper pSOH)
{
//生命周期开始时调试
System.Diagnostics.Debugger.Launch();
serverObjectHelper = pSOH;
}
生成解决方案,在bin目录下可以看到以.soe后缀名的文件。
PS:该.soe后缀的文件可以将.soe名称修改为.zip,那么用户可以看到相关dll以及元数据xml文件信息。
打开Manger,点击site选项,在GIS Server页选择Extensions
然后再Services选项中,选择某个服务比如JS 地图服务,在该服务的Capablities选择新的RestSOE2扩展,然后保存和重新启动服务即可。
SOE调试
如果前面在init函数中已经添加了调试代码,那么在该服务重启过程中就会弹出如下对话框
选择相应的SOE 程序点击是,那么系统就会进入调试状态
掌握了SOE的调试,用户可以跟踪相关的调试顺序,了解SOE的具体执行步骤。
注意:
1:我的VS、AO、ArcGIS Server都在同一个环境下,相关调试没有问题,但是如果VS和Server不再一台机器上,调用调试可能会有问题,咨询相关人员回答在不同环境下是可以调试的,但是我并没有解决。
2:因为64Bit的关系,代码不能像C#+ArcGIS Engine 开发可以实时修改代码进行调试
3:SOE的代码如果需要修改,停止调试,然后修改代码,然后重新生成SOE,然后删除manager的扩展项,然后添加新修改的soe文件,然后再针对某个服务添加扩展重新启动服务,这个就比较麻烦,建议现在纯AO环境下进行调试,这样会减少比较多的麻烦。
SOE生命周期
通过跟踪代码,我们可以了解SOE的生命周期,建议用户还需要提前查看相关帮助
============在添加扩展SOE服务的重启过程============
1:MapServer初始化
2:SOE初始化
public void Init(IServerObjectHelper pSOH)
{
System.Diagnostics.Debugger.Launch();
serverObjectHelper = pSOH;
}
3:MapServer启动
4:SOE构造
public void Construct(IPropertySet props)
{
configProps = props;
}
===============生成相关的SOE服务连接,点击该连接=====
5:SOE活动——REST/SOAP处理请求——SOE停止活动
这是一个相关循环的过程。
在点击链接时会创建Schema
public string GetSchema()
{
return reqHandler.GetSchema();
}
然后进行REST/SOAP处理请求
public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties)
{
return reqHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties);
}
请求完毕之后,创建Root资源,也就是可以看到的SOE介绍信息
private byte[] RootResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties)
{
responseProperties = null;
JsonObject result = new JsonObject();
result.AddString("hello", "world");
return Encoding.UTF8.GetBytes(result.ToJson());
}
点击相关的sampleOperation函数,我们输入相关参数值,点击GET请求
我们可以看到系统还会进行REST/SOAP请求,然后进入相关的主体函数中,核心的代码应该在该函数中编写即可
private byte[] SampleOperHandler(NameValueCollection boundVariables,
JsonObject operationInput,
string outputFormat,
string requestProperties,
out string responseProperties)
{
responseProperties = null;
string parm1Value;
bool found = operationInput.TryGetString("parm1", out parm1Value);
if (!found || string.IsNullOrEmpty(parm1Value))
throw new ArgumentNullException("parm1");
string parm2Value;
found = operationInput.TryGetString("parm2", out parm2Value);
if (!found || string.IsNullOrEmpty(parm2Value))
throw new ArgumentNullException("parm2");
JsonObject result = new JsonObject();
result.AddString("parm1", parm1Value);
result.AddString("parm2", parm2Value);
return Encoding.UTF8.GetBytes(result.ToJson());
}
6:MapServer停止
7:SOE关闭
public void Shutdown()
{
}
关闭之后需要用户释放相关的对象资源。
关于Schema
一般情况下,我们实现一个功能都会需要输入相关参数,和参与该功能的图层或者字段等,那么我们将Schema称之为SOE中的资源和操作的层次结构,资源(resource)就是从服务器端返回的信息块,比如图层列表、缓存服务的比例尺级别,如果这次实验用的的图层名称subway,字段名称name,操作(operations)就是让服务器资源做的一些事情,比如我们设置的where条件以及缓冲区bufferRedius等
private RestResource CreateRestSchema()
{
RestResource rootRes = new RestResource(soe_name, false, RootResHandler);
RestOperation sampleOper = new RestOperation("QueryBuffer",
new string[] { "WhereClause", "BufferRadius" },
new string[] { "json" },
DoQueryBuffer);
rootRes.operations.Add(sampleOper);
RestResource propertiesResource = new RestResource("properties", false, PropertiesResHandler);
rootRes.resources.Add(propertiesResource);
return rootRes;
}
关于SOE的处理流程
1:客户端请求参数,请求参数反序列化
2:SOE业务逻辑实现处理
3:结果序列化
关于参数的序列化或者反序列化,都使用了ESRI.ArcGIS.SOESupport;
序列化
反序列化
我个人觉得一句话总结,一切皆JSON。最终返回的结果都是JSON格式即可。
比如本次主题的目的就是查询出来符合条件和缓冲区面积的点要素几何,那么我们需要获得这些相关Geometry对象的JSON格式的集合即可。
private byte[] GetResultJson(List<IFeature> pList)
{
List<JsonObject> pJoList = null;
if (pList!=null)
{
IFeature pfea = null;
pJoList = new List<JsonObject>();
for (int i = 1; i < pList.Count; i++)
{
pfea = pList[i];
pJoList.Add(Conversion.ToJsonObject(pfea.ShapeCopy));
}
}
JsonObject resultJson = new JsonObject();
resultJson.AddArray("geometries", pJoList.ToArray());
byte[] result = Encoding.UTF8.GetBytes(resultJson.ToJson());
return result;
}
关于获取数据源信息
SOE开发与AO开发不一样的地方,AO可以直接获得SDE连接对象,然后打开获得相关的IFeatureClass对象等,那么SOE跟这个不太一样,它需要通过服务来获得对象,主要关注IMapServerDataAccess获得相关的MSD数据,有两个功能可以使用
一般情况下使用GetDataSource,那么如果有关联关系的可以使用GetDisplayDataSouce。
//获取数据
IMapServer3 mapServer = (IMapServer3)serverObjectHelper.ServerObject;
IMapLayerInfo layerInfo;
IMapLayerInfos layerInfos = mapServer.GetServerInfo(mapServer.DefaultMapName).MapLayerInfos;
// 获取查询图层id
int layercount = layerInfos.Count;
int layerIndex = 0;
string sName = "";
for (int i = 0; i < layercount; i++)
{
layerInfo = layerInfos.get_Element(i);
//默认的oracle数据库的sde用户,查询出来的layerInfo.Name为sde.subway所以需要去掉sde.信息
sName=layerInfo.Name;
if (sName != "")
{
sName= sName.Substring(sName.IndexOf(".") + 1, (sName.Length - sName.IndexOf(".")) - 1).Trim();
}
if (sName == m_LayerName)
{
layerIndex = i;
break;
}
}
IMapServerDataAccess dataAccess = (IMapServerDataAccess)mapServer;
this.m_FeatureClass = (IFeatureClass)dataAccess.GetDataSource(mapServer.DefaultMapName, layerIndex);
相关SOE源代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Server;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.SOESupport;
//TODO: sign the project (project properties > signing tab > sign the assembly)
// this is strongly suggested if the dll will be registered using regasm.exe <your>.dll /codebase
namespace RestSOE1
{
[ComVisible(true)]
[Guid("99611947-9d7d-4f6f-a4c4-d835ce4a7fa5")]
[ClassInterface(ClassInterfaceType.None)]
[ServerObjectExtension("MapServer",//use "MapServer" if SOE extends a Map service and "ImageServer" if it extends an Image service.
AllCapabilities = "",
DefaultCapabilities = "",
Description = "Query Buffer SOE",
DisplayName = "RestSOE1",
Properties = "Field_Name=name;Layer_Name=subway",
SupportsREST = true,
SupportsSOAP = false)]
public class RestSOE1 : IServerObjectExtension, IObjectConstruct, IRESTRequestHandler
{
private string soe_name;
private IPropertySet configProps;
private IServerObjectHelper serverObjectHelper;
private ServerLogger logger;
private IRESTRequestHandler reqHandler;
private IFeatureClass m_FeatureClass = null;
//查询图层名称
private string m_LayerName = "";
//查询字段名称
private string m_FieldName = "";
public RestSOE1()
{
soe_name = this.GetType().Name;
logger = new ServerLogger();
reqHandler = new SoeRestImpl(soe_name, CreateRestSchema()) as IRESTRequestHandler;
}
#region IServerObjectExtension Members
public void Init(IServerObjectHelper pSOH)
{
//生命周期开始时调试
System.Diagnostics.Debugger.Launch();
serverObjectHelper = pSOH;
}
public void Shutdown()
{
this.soe_name = null;
this.m_FeatureClass = null;
this.logger = null;
this.m_FieldName = null;
this.m_LayerName = null;
this.serverObjectHelper = null;
this.configProps = null;
this.reqHandler = null;
}
#endregion
#region IObjectConstruct Members
public void Construct(IPropertySet props)
{
configProps = props;
if (props.GetProperty("Field_Name") != null)
{
this.m_FieldName = props.GetProperty("Field_Name") as string;
}
else
{
throw new ArgumentNullException();
}
if (props.GetProperty("Layer_Name") != null)
{
this.m_LayerName = props.GetProperty("Layer_Name") as string;
}
else
{
throw new ArgumentNullException();
}
try
{
//获取数据
IMapServer3 mapServer = (IMapServer3)serverObjectHelper.ServerObject;
IMapLayerInfo layerInfo;
IMapLayerInfos layerInfos = mapServer.GetServerInfo(mapServer.DefaultMapName).MapLayerInfos;
// 获取查询图层id
int layercount = layerInfos.Count;
int layerIndex = 0;
string sName = "";
for (int i = 0; i < layercount; i++)
{
layerInfo = layerInfos.get_Element(i);
//默认的oracle数据库的sde用户,查询出来的layerInfo.Name为sde.subway所以需要去掉sde.信息
sName=layerInfo.Name;
if (sName != "")
{
sName= sName.Substring(sName.IndexOf(".") + 1, (sName.Length - sName.IndexOf(".")) - 1).Trim();
}
if (sName == m_LayerName)
{
layerIndex = i;
break;
}
}
IMapServerDataAccess dataAccess = (IMapServerDataAccess)mapServer;
this.m_FeatureClass = (IFeatureClass)dataAccess.GetDataSource(mapServer.DefaultMapName, layerIndex);
if (this.m_FeatureClass == null)
{
logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Layer name not found.");
return;
}
if (this.m_FeatureClass.FindField(this.m_FieldName) == -1)
{
logger.LogMessage(ServerLogger.msgType.error, "Construct", 8000, "SOE custom error: Field not found in layer.");
}
}
catch (Exception e)
{ }
}
#endregion
#region IRESTRequestHandler Members
public string GetSchema()
{
return reqHandler.GetSchema();
}
public byte[] HandleRESTRequest(string Capabilities, string resourceName, string operationName, string operationInput, string outputFormat, string requestProperties, out string responseProperties)
{
return reqHandler.HandleRESTRequest(Capabilities, resourceName, operationName, operationInput, outputFormat, requestProperties, out responseProperties);
}
#endregion
private RestResource CreateRestSchema()
{
RestResource rootRes = new RestResource(soe_name, false, RootResHandler);
RestOperation sampleOper = new RestOperation("QueryBuffer",
new string[] { "WhereClause", "BufferRadius" },
new string[] { "json" },
DoQueryBuffer);
rootRes.operations.Add(sampleOper);
RestResource propertiesResource = new RestResource("properties", false, PropertiesResHandler);
rootRes.resources.Add(propertiesResource);
return rootRes;
}
private byte[] PropertiesResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties)
{
responseProperties = "{\"Content-Type\" : \"application/json\"}";
JsonObject result = new JsonObject();
result.AddString("Field_Name", this.m_FieldName);
result.AddString("Layer_Name", this.m_LayerName);
return Encoding.UTF8.GetBytes(result.ToJson());
}
private byte[] RootResHandler(NameValueCollection boundVariables, string outputFormat, string requestProperties, out string responseProperties)
{
responseProperties = null;
JsonObject result = new JsonObject();
result.AddString("名称", "查询缓冲Soe");
result.AddString("描述", "通过属性查询条件,获得一个点要素,然后再做缓冲区");
result.AddString("方法", "条件过滤加上缓冲区");
return Encoding.UTF8.GetBytes(result.ToJson());
}
private byte[] DoQueryBuffer(NameValueCollection boundVariables,
JsonObject operationInput,
string outputFormat,
string requestProperties,
out string responseProperties)
{
responseProperties = null;
string WhereClause;
bool found = operationInput.TryGetString("WhereClause", out WhereClause);
if (!found || string.IsNullOrEmpty(WhereClause))
throw new ArgumentNullException("WhereClause");
double? BufferRadius;
found = operationInput.TryGetAsDouble("BufferRadius", out BufferRadius);
if (!found || !BufferRadius.HasValue)
throw new ArgumentNullException("BufferRadius");
List<IFeature> pList = GetQueryBufferGeometryList(this.m_FieldName,
WhereClause, BufferRadius);
return GetResultJson(pList);
}
private IGeometry GetQueryBufferGeometry(string sFieldName, IFeatureClass pfc,
string sWhereClause, object sBufferRadius)
{
IGeometry pGeo = null;
IQueryFilter qf = new QueryFilterClass();
//这里只测试字符串查询,其他类型不再进行逻辑检查和编写
string sWhere=sFieldName+"='"+sWhereClause+"'";
qf.WhereClause = sWhere;
IFeatureCursor pCur= pfc.Search(qf, true);
IFeature pFea = pCur.NextFeature();
ITopologicalOperator pTO = null;
while (pFea != null)
{
pTO = pFea.ShapeCopy as ITopologicalOperator;
pGeo = pTO.Buffer(Convert.ToDouble(sBufferRadius));
pFea = pCur.NextFeature();
}
return pGeo;
}
private List<IFeature> GetQueryBufferGeometryList(string sFieldName,
string sWhereClause, object sBufferRadius)
{
List<IFeature> pFeatureList = new List<IFeature>();
IQueryFilter qf = new QueryFilterClass();
//这里只测试字符串查询,其他类型不再进行逻辑检查和编写
string sWhere = sFieldName + "='" + sWhereClause + "'";
qf.WhereClause = sWhere;
IFeatureCursor pCur = this.m_FeatureClass.Search(qf, false);
IFeature pFea = pCur.NextFeature();
ITopologicalOperator pTO = null;
IGeometry pBufferGeo = null;
while (pFea != null)
{
pTO = pFea.ShapeCopy as ITopologicalOperator;
pBufferGeo = pTO.Buffer(Convert.ToDouble(sBufferRadius));
pFea = pCur.NextFeature();
}
ISpatialFilter sf = new SpatialFilterClass();
sf.Geometry = pBufferGeo;
sf.SpatialRel = esriSpatialRelEnum.esriSpatialRelIntersects;
sf.GeometryField = this.m_FeatureClass.ShapeFieldName;
ISpatialReference sr=(this.m_FeatureClass as IGeoDataset).SpatialReference;
sf.set_OutputSpatialReference(this.m_FeatureClass.ShapeFieldName, sr);
IFeatureCursor pFCur = this.m_FeatureClass.Search(sf, false);
while ((pFea = pFCur.NextFeature()) != null)
{
pFeatureList.Add(pFea);
}
return pFeatureList;
}
private byte[] GetResultJson(List<IFeature> pList)
{
List<JsonObject> pJoList = null;
if (pList!=null)
{
IFeature pfea = null;
pJoList = new List<JsonObject>();
for (int i = 1; i < pList.Count; i++)
{
pfea = pList[i];
pJoList.Add(Conversion.ToJsonObject(pfea.ShapeCopy));
}
}
JsonObject resultJson = new JsonObject();
resultJson.AddArray("geometries", pJoList.ToArray());
byte[] result = Encoding.UTF8.GetBytes(resultJson.ToJson());
return result;
}
}
}
很久不写Engine代码了,连很简单的东西都忘记的差不多了。我们可以通过前面介绍的调试方法,测试是否可以获得正确的结果?
ArcGIS for JS 调用SOE服务
关于ArcGIS for JS调用SOE 服务主要使用了如下代码:
var WhereClause = dom.byId("WhereClause").value;
var BufferRadius = dom.byId("BufferRadius").value;
var content = {
'WhereClause': WhereClause,
'BufferRadius': BufferRadius,
'f': "json"
};
var Request = esriRequest({
url: soeUrl,
content: content,
handleAs: "json",
callbackParamName: "callback"
});
Request.then(
function (responses) {...........});
使用esriRequest,传入soe的REST,然后将属性参数传入(content),然后对请求进行枚举查询,由于我们SOE获得的结果是查询的IGeometry的Array,那么执行对返回结果进行循环获得,然后将相关结果作为一个graphic添加到map即可。
ArcGIS for JS调用SOE 源代码如下
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no" />
<title>Simple Map</title>
<link rel="stylesheet" type="text/css" href="http://localhost/arcgis_js_v39_api/arcgis_js_api/library/3.9/3.9/js/dojo/dijit/themes/tundra/tundra.css" />
<link rel="stylesheet" type="text/css" href="http://localhost/arcgis_js_v39_api/arcgis_js_api/library/3.9/3.9/js/esri/css/esri.css" />
<script type="text/javascript" src="http://localhost/arcgis_js_v39_api/arcgis_js_api/library/3.9/3.9"> </script>
<script type="text/javascript" src="jsapi_vsdoc_v32_2012.js"></script>
<style>
html, body, #mapDiv {
padding: 0;
margin: 0;
height: 100%;
}
#messages {
background-color: #fff;
box-shadow: 0 0 5px #888;
font-size: 1.1em;
max-width: 15em;
padding: 0.5em;
position: absolute;
right: 20px;
top: 300px;
z-index: 40;
}
</style>
<script>
var map;
require([
"esri/map", "esri/layers/FeatureLayer",
"esri/layers/ArcGISDynamicMapServiceLayer",
"esri/request",
"esri/graphic", "esri/symbols/SimpleMarkerSymbol",
"esri/geometry/Point",
"esri/SpatialReference",
"esri/config", "esri/Color", "dojo/_base/array", "dojo/on", "dojo/dom", "dojo/domReady!"
], function (
Map, FeatureLayer, ArcGISDynamicMapServiceLayer,
esriRequest,
Graphic, SimpleMarkerSymbol,
Point,SpatialReference,
esriConfig, Color,arrayUtils,on, dom
) {
// use a proxy page if a URL generated by this page is greater than 2000 characters
//
// this should not be needed as nearly all query & select functions are performed on the client
esriConfig.defaults.io.proxyUrl = "/proxy";
map = new Map("mapDiv", {
});
var mapUrl = "http://192.168.220.125:6080/arcgis/rest/services/JS/MapServer";
var soeUrl = "http://192.168.220.125:6080/arcgis/rest/services/JS/MapServer/exts/RestSOE1/QueryBuffer";
//Takes a URL to a non cached map service.
var dynamicMapServiceLayer = new ArcGISDynamicMapServiceLayer(mapUrl);
map.addLayer(dynamicMapServiceLayer);
on(dom.byId("execute"), "click", function () {
var WhereClause = dom.byId("WhereClause").value;
var BufferRadius = dom.byId("BufferRadius").value;
var content = {
'WhereClause': WhereClause,
'BufferRadius': BufferRadius,
'f': "json"
};
var Request = esriRequest({
url: soeUrl,
content: content,
handleAs: "json",
callbackParamName: "callback"
});
Request.then(
function (responses) {
map.graphics.clear();
var markerSymbol = new SimpleMarkerSymbol();
markerSymbol.setPath("M16,4.938c-7.732,0-14,4.701-14,10.5c0,1.981,0.741,3.833,2.016,5.414L2,25.272l5.613-1.44c2.339,1.316,5.237,2.106,8.387,2.106c7.732,0,14-4.701,14-10.5S23.732,4.938,16,4.938zM16.868,21.375h-1.969v-1.889h1.969V21.375zM16.772,18.094h-1.777l-0.176-8.083h2.113L16.772,18.094z");
markerSymbol.setColor(new Color("#00FFFF"));
markerSymbol.setSize(30);
markerSymbol.setOffset(20, 10);
var wgs = new SpatialReference({
"wkid": 102100
});
for (var i = 0; i < responses.geometries.length; i++) {
var pt = new Point(responses.geometries[i].x, responses.geometries[i].y, wgs);
var graphic = new Graphic(pt, markerSymbol);
map.graphics.add(graphic);
}
});
});
});
</script>
</head>
<body>
查询条件
<input type="text" id="WhereClause" value="北京站">
缓冲区半径
<input type="text" id="BufferRadius" value="1500">
<input id="execute" type="button" value="执行查询">
<div id="mapDiv"></div>
</body>
</html>
获得的查询页面和结果如下
欢迎关注微信公众号“GIS带我奔跑”获取更多技术文章。
原文链接:https://blog.csdn.net/linghe301/article/details/38434469