SharpMap介绍及源码分析

本文发表于《3sNews新闻周刊》第一期,有删改,请勿转载。

SharpMap是一个基于.net 2.0使用C#开发的Map渲染类库,可以渲染各类GIS数据(目前支持ESRI Shape和PostGIS格式),可应用于桌面和Web程序。

其网址为:http://sharpmap.iter.dk/

SharpMap的发布许可(License)为GNU General Public License,开发者为Morten Nielsen(http://www.iter.dk/)。目前的稳定版本为0.8(9.0beta已发布),代码行数近10000行,实现了以下功能:

支持的数据格式:
      PostGreSQL/PostGIS,ESRI Shapefile
      支持WMS layers
      支持ECW 和 JPEG2000 栅格数据格式
Windows Forms 控件,可以移动和缩放
通过HttpHandler支持ASP.net程序
点、线、多边形、多点、多线和多多边形等几何类型
几何集合(GeometryCollections)等OpenGIS Simple Features Specification
可通过Data Providers(增加数据类型支持)、Layer Types(增加层类型)和Geometry Types等扩展
图形使用GDI+渲染,支持anti-aliased等
专题图

可以看出,SharpMap目前可以算是一个实现了最基本功能的GIS系统,但一些很重要的功能,例如投影,比例尺,空间分析,图形的属性信息,查询检索等等,还没有或者还在开发中。一个好消息是,作者在SharpMap的网站写到:Diego Guidi(NetTopologySuite的开发者)已经创建了一个SharpMap和NTS之间的一个连接,这样,就可以在SharpMap中使用NTS的空间变换、缓冲区等功能。

笔者之所以在这里分析ShrapMap,出于以下原因:

      SharpMap足够小(小于10000行),且具备了一个GIS软件的基本功能,容易下手;
      基于.net和C#开发;
      开放源码(不开放就没有办法分析,废话);
      SharpMap还在开发中,可以通过跟踪其源码学习提高。

一项技术或者一个工具,知其然和知其所以然,对于应用的深度和熟练程度还是具有很大的影响,特别是程序开发。分析SharpMap,不一定是要使用SharpMap,是希望通过分析SharpMap,可以了解一个GIS系统的纵剖面,从而可以更好的进行GIS的应用和开发。

源码结构

以下是SharpMap在VS 2005下的Class视图和Solution视图,可以看出SharpMap由SharpMap和其他14个次级名称空间组成,其中SharpMap名称空间下的Map类为这个系统的核心所在。



Map类,位于SharpMap命名空间下,通过创建Map对象的实例来生成地图。Map对象由包含Layer对象组成Layers集合,通过GetMap方法来Render地图。

Converts名称空间,提供数据转换服务。

Forms名称空间,包含MapImage控件,一个简单的User Control(用户控件),封装了Map类,用于Windows Form编程。

Geometries名称空间,包括了SharpMap要使用到的各种几何类及其接口类,例如点、线、面等类。

是SharpMap的基础之一,所有几何对象都继承自Geometry这个抽象类,其中定义了几何对象应该具备的公共操作,例如大小、ID、外接矩阵、几何运算等等。
 
Layers名称空间,包括了ILayer接口,Layer集合类等,代表地图的图层。
 
Layer是一个抽象类,实现了ILayer接口,Layer目前有3个子类,分别是VectorLayer、LabelLayer和WmsLayer,分别代3种不同数据类型的图层。

Providers名称空间,包括了IProvider接口和Shape文件、PostGIS数据的读取实现。该名称空间为SharpMap提供数据读(写)支持,通过面向接口的设计,可以比较容易的增加各类数据格式。

Rendering名称空间,目前包括矢量渲染器类和几个专题图渲染器类,该类可以将几何对象根据其Style设置渲染为一个System.Drawing.Graphics对象。

Styles名称空间,该名称空间主要提供了图层的样式设置类,例如线样式、点样式、填充样式等.
 
Utilities名称空间包括Algorithms类(目前仅实现了一个方法);Providers类,是Provider的一个Helper,应用了反射机制;Surrogates主要用于系统的Pen和Brush的序列化;Transform提供了从图片坐标到地理坐标的互相变换,也即桌面GIS的二次开发中经常使用的屏幕坐标和地理坐标的转换,主要用于地图的渲染、交互操作等。

Utilities.SpatialIndexing用于对象的空间索引,我们后面还会继续介绍,Web名称空间实现了HttpHandler和Caching类,用于网络环境。

运行机制

通过剖析其名称空间,我们对SharpMap源码的结构和组成有了大概的了解,下来我们通过上面的应用实例来剖析其运行机制。

SharpMap.Map myMap = new SharpMap.Map(picMap.Size);

这句代码创建了一个新的Map的对象,Map对象包括了中点、大小、缩放比例、图层等字段,Layers集合对象包括了地图的各个图层,可以通过Layers.Add方法增加新的图层,通过GetLayerByName方法返回某个图层。缩放等方法是通过Center和Zoom属性来控制的。当地图图层修改和地图渲染后会触发相应的事件。

接着就需要新建不同的Layer对象,设置其属性和数据源以及样式。例如:

SharpMap.Layers.VectorLayer myLayer = new SharpMap.Layers.VectorLayer("My layer");
string ConnStr = "Server=127.0.0.1;Port=5432;User Id=postgres;Password=password;Database=myGisDb;";
//创建图层的数据源
myLayer.DataSource = new SharpMap.Providers.PostGIS(ConnStr, "myTable", "the_geom", 32632);

不同的Providers对象,例如PostGIS或者Shape对象负责打开相应的空间数据集,读取数据,返回部分或者全部的空间对象。

接着就需要设置Layer的样式,例如填充、线形等属性。目前,Style对象只是简单的封装了System.Drawing.Pen、System.Drawing.Brush、System.Drawing.Bitmap等对象。并增加图层到Map的Layers集合:

myMap.Layers.Add(myLayer);

我们可以通过设置Map的Center、Zoom、Size等属性来进行Map操作,例如缩放、平移等操作,例如封装Map对象为一个User Control控件,主要的动作就是操作Map的各个属性。

最后,我们就可以通过GetMap对象返回一个System.Drawing.Image对象,代表目前的地图,用于显示或输出:

System.Drawing.Image imgMap = myMap.GetMap(); //Renders the map

GetMap是整个SharpMap的核心之一,我们来一步步剖析其流程。

System.Drawing.Image img = new System.Drawing.Bitmap();
System.Drawing.Graphics g 
= System.Drawing.Graphics.FromImage(img);
g.Clear(
this.BackColor);
foreach (SharpMap.ILayer layer in this.Layers)
{    
if(layer.Enabled && layer.MaxVisible>=this.Zoom &&
    layer.MinVisible
<this.Zoom)
        layer.Render(g,
this);
}

if (MapRendered != null) MapRendered(g); //Fire render event
g.Dispose();
return img;

以上代码就是GetMap的全部代码,首先创建一个System.Drawing.Image对象,然后为其创建一个System.Drawing.Graphics对象,通过这个对象来渲染地图到这个Image对象,最后返回这个对象。

其核心在于循环所有图层,如果其在显示范围之内,则调用Layer对象的Render方法,渲染这个图层。

抽象类Layer中的Render方法为:

if(LayerRendered!=null) LayerRendered(this, g);

VectorLayer子类的Render方法为:

g.SmoothingMode = this.SmoothingMode;
 
List
<SharpMap.Geometries.Geometry> features = this.DataSource.GetFeaturesInView(map.Envelope);
//Linestring outlines is drawn by drawing the layer once with a thicker line
//before drawing the "inline" on top.
if (this.Style.EnableOutline)
{
    
foreach (SharpMap.Geometries.Geometry feature in features)
    
{
        
//首先绘制所有图形对象的线轮廓
        
    }

}

 
//double i = 0;
foreach (Geometries.Geometry feature in features)
{
    
switch (feature.GeometryType)
    
{
        
//根据不同的图形绘制其
        
    }

}

base.Render(g,map);

这段代码首先根据地图的显示范围,获取范围内的所有对象:GetFeaturesInView,然后逐次绘制不同这些对象。绘制对象时,调用了SharpMap.Rendering对象的不同方法。在SharpMap.Rendering对象中,不同几何对象的绘制方法最终调用了.net的System.Drawing中的GDI+的绘制方法,完成地图的绘制。

需要说明的是不同的数据源的GetFeaturesInView方法实现方法是不同的,目前版本的Shape数据源的Provider是使用了空间索引算法,PostGIS数据源则通过PostGIS的空间索引接口来获取需要的数据。

系统效率的好坏,基本上就在于这里,一个是如何获取视图内的空间对象,关键在于空间索引;一个是渲染机制,例如使用DirectX加快渲染。

这样,我们就基本完成了对SharpMap的剖析。SharpMap是一个刚刚启动不久的项目,开发者到目前为止只有一人,所以,实现的功能有限,有些设计也不是很成熟,例如缺乏空间分析和检索功能,但完全可以作为一个非常好的教学系统来使用。

从作者Morten Nielsen了解到,该项目是从2005年夏天开始的,目的是:

It was a good case for learning some of the new .NET 2.0 features and for getting "under the hood" of many of the GIS-related algorithms needed, like spatial indexing, spatial translations and topology rules.
通过项目,可以学习.net 2.0的一些新的特性,而且可以了解GIS相关的一些底层算法,例如空间索引、空间变换、拓扑规则等。

从中我们是否可以看到对技术的一种不同的态度,少一些空谈,多一些务实,这是笔者个人的感受。


后续文章:
SharpMap深度分析:地图渲染、坐标和比例尺
SharpMap深度分析:地图数据Provider

posted @ 2006-02-24 19:38 马维峰 阅读(13309) 评论(41)  编辑 收藏 所属分类: [02] GIS[06] .net

  回复  引用  查看    
#1楼 2006-02-25 08:41 | Paker Liu      
少一些空谈,多一些务实
  回复  引用  查看    
#2楼 2006-02-25 18:01 | onekey      
我也关注SHARPMAP很久了,
有几点不是很清楚:
1。读取SHP文件生成的二叉树是做什么用的?
2。每次把所有的数据都载入,数据量大的时候性能会出问题。
另外期待SHARPMAP中加入读取DBF文件的功能
  回复  引用  查看    
#3楼 [楼主]2006-02-25 18:44 | 马维峰      
@onekey
第一个问题,是建立一个空间索引,这样,可以快速的决心空间数据的检索,例如检索出窗口内的所有对象,有索引了就无须检索全部的对象;本来想展开这个问题,但篇幅和时间问题,后面再说了,呵呵;
第二个,他没有把数据都载入啊。

现在的0.9好像已经实现了一些其他数据读取的Provider,还没有研究。

继续交流。

  回复  引用    
#4楼 2006-02-26 18:09 | ltjabc [未注册用户]
有具体的用途
来说一下
会更好
  回复  引用  查看    
#5楼 2006-02-26 22:42 | Flyingis      
师兄费了不少功夫,学习不少。
  回复  引用  查看    
#6楼 2006-02-27 10:33 | edison1024      
这玩意现在的发布版本也就算个半成品,也就是按OGC SFS把geometry的部分实现了一些(很多方法都没实现)。feature那部分甚至一点都没写哦。

不过新手通过学习这个来了解OGC SFS还是不错的。
  回复  引用    
#7楼 2006-02-27 14:42 | xiaoqu800 [未注册用户]
用过,用上海的样品图(shape file)总出问题,最终还是调用灵图api来得省事,效果可以看下http://www.xq800.cn/wikimap/%b2%e2%ca%d4%c7%f8/%d0%a1%c7%f8%b5%d8%cd%bc.html
  回复  引用    
#8楼 2006-02-28 06:41 | Morten [未注册用户]
Wow nice article! Although the google-translation wasn't perfect I got enough to see that you really worked through my code and understand the basics of how it works.

Would be great with an english version of your article as well :-)
  回复  引用  查看    
#9楼 [楼主]2006-02-28 10:39 | 马维峰      
@Morten
Thank you.
  回复  引用  查看    
#10楼 2006-02-28 16:19 | onekey      
哇塞!Morten这个高手也居然在这里露面了

:)
  回复  引用  查看    
#11楼 2006-02-28 21:09 | edison1024      
Morten?? 哈哈,原作者都来了。
  回复  引用    
#12楼 2006-03-07 23:08 | happysofter [未注册用户]
老兄,召集几个人,加入SharpMap开发组吧!
  回复  引用  查看    
#13楼 [楼主]2006-03-08 16:53 | 马维峰      
@happysofter
有兴趣可以上SharpMap网站讨论,甚至加入。
对于我本人,看这些东西的本意是学习有关系统的实现,参考参考。
  回复  引用    
#14楼 2006-07-05 11:30 | 璉璉 [未注册用户]
關於 Multi-Polygon / Multi-Polyline ,台灣這邊 GIS 學門會將這兩個詞翻譯成 複合多邊形 / 複合多重折線 ,雖然有可能跟 complex-polygon 搞混,不過我自己是覺得滿貼切的,多多邊形,外行人可能不知道是啥,複合多邊形對外行人來說,反正就是很複雜又合在一起的多邊形,比較容易意會~
  回复  引用    
#15楼 2006-07-14 11:10 | willow [未注册用户]
有个问题:
SharpMap中如何把不同地理信息分到不同的层中?就是按照交通、水源等等分类?
因为我看到shapefile中似乎没有相关的信息,如果我想用shapefile作为图源,怎么才能把这些图放到对应的层中去呢?
谢谢!
  回复  引用    
#16楼 2006-07-14 16:10 | gislove@gmail.com [未注册用户]
//国家
SharpMap.Layers.VectorLayer lyrCou = new SharpMap.Layers.VectorLayer("countries");
lyrCou.DataSource = new SharpMap.Data.Providers.ShapeFile(@"D:\资料2\开源\sharpMap framework2.0\SharpMap_source_v0.9.2327\src\winformdemo\App_Data\countries.shp");
lyrCou.Style.Outline = new Pen(Color.Magenta, 3f);
lyrCou.Style.EnableOutline = true;
this.mapImage1.Map.Layers.Add(lyrCou);

//城市
SharpMap.Layers.VectorLayer lyrCity = new SharpMap.Layers.VectorLayer("cities");
lyrCity.DataSource = new SharpMap.Data.Providers.ShapeFile(@"D:\资料2\开源\sharpMap framework2.0\SharpMap_source_v0.9.2327\src\winformdemo\App_Data\cities.SHP");
lyrCity.Style.Outline = new Pen(Color.Magenta, 3f);
lyrCity.Style.EnableOutline = true;
this.mapImage1.Map.Layers.Add(lyrCity);

//河流
SharpMap.Layers.VectorLayer lyrRiver = new SharpMap.Layers.VectorLayer("rivers");
lyrRiver.DataSource = new SharpMap.Data.Providers.ShapeFile(@"D:\资料2\开源\sharpMap framework2.0\SharpMap_source_v0.9.2327\src\winformdemo\App_Data\rivers.shp");
lyrRiver.Style.Outline = new Pen(Color.Magenta, 3f);
lyrRiver.Style.EnableOutline = true;
this.mapImage1.Map.Layers.Add(lyrRiver);


//适当位置
this.mapImage1.Map.ZoomToExtents();

//System.Drawing.Image imgMap = map.GetMap();

//this.mapImage1.Image = imgMap;
this.mapImage1.Refresh();
  回复  引用    
#17楼 2006-07-14 16:19 | willow [未注册用户]
@gislove@gmail.com

谢谢你!我的意思是,能不能直接从shapefile文件中直接读出地理信息,而不是知道了这个图所代表的地理信息后再加入对应的层。
其实就是问问,图源中有没有对应的地理信息?
一般的GIS系统都是怎么做的呢?
  回复  引用  查看    
#18楼 [楼主]2006-07-14 21:51 | 马维峰      
@willow
你是怎么做的呢?

  回复  引用    
#19楼 2006-07-15 13:29 | willow [未注册用户]
@马维峰
我们现在的设计就是根据地理特征来分层的,可是不知道图源中有没有相应的信息:(
另外还有一个问题:shapefile中的点的坐标是经纬度吗?它的格式中似乎没有提到这个,而我们现在也是按照经纬度来保存点的。
  回复  引用  查看    
#20楼 [楼主]2006-07-16 00:27 | 马维峰      
@willow
第一,GIS数据分层一般是根据需要自己来安排;
第二,坐标可以是经纬度,也可以是其他坐标,看使用的坐标是什么。

  回复  引用    
#21楼 2006-07-16 00:38 | willow [未注册用户]
@马维峰

那么怎么知道使用的是什么坐标呢?哪里有这方面的信息呢?
  回复  引用  查看    
#22楼 [楼主]2006-07-16 00:59 | 马维峰      
@willow
有些Shape文件有一个Project文件。
没有了就可以通过看具体的点的坐标数据来判断,然后使用ArcGIS这类软件生成一个Project文件。
  回复  引用    
#23楼 2006-10-08 14:47 | Viola [未注册用户]
大家好,我已经在地图上根据经纬度加入了两个点,我想把这两个点用线连接起来,请问这方面的代码怎么写呢? 谢谢!
  回复  引用    
#24楼 2006-10-20 16:26 | guo [未注册用户]
我怎么在winapp程序中加不上sharpmap,地图显示不出来
private void Form1_Load(object sender, EventArgs e)
{
SharpMap.Map map = new SharpMap.Map(new Size(400, 300));

//Set up the countries layer
SharpMap.Layers.VectorLayer layCountries = new SharpMap.Layers.VectorLayer("Countries");
//Set the datasource to a shapefile in the App_data folder
//layCountries.DataSource = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\countries.shp"), true);
layCountries.DataSource = new SharpMap.Data.Providers.ShapeFile("D:\\work\\dotNet\\WindowsApplication1\\App_data\\countries.shp");
//Set fill-style to green
layCountries.Style.Fill = new SolidBrush(Color.Green);
//Set the polygons to have a black outline
layCountries.Style.Outline = System.Drawing.Pens.Black;
layCountries.Style.EnableOutline = true;
layCountries.SRID = 4326;

//Set up a river layer
SharpMap.Layers.VectorLayer layRivers = new SharpMap.Layers.VectorLayer("Rivers");
//Set the datasource to a shapefile in the App_data folder
layRivers.DataSource = new SharpMap.Data.Providers.ShapeFile("D:\\work\\dotNet\\WindowsApplication1\\App_data\\rivers.shp");
//Define a blue 1px wide pen
layRivers.Style.Line = new Pen(Color.Blue,1);
layRivers.SRID = 4326;

//Set up a river layer
SharpMap.Layers.VectorLayer layCities = new SharpMap.Layers.VectorLayer("Cities");
//Set the datasource to a shapefile in the App_data folder
layCities.DataSource = new SharpMap.Data.Providers.ShapeFile("D:\\work\\dotNet\\WindowsApplication1\\App_data\\cities.shp");
//Define a blue 1px wide pen
layCities.Style.Symbol = new Bitmap("D:\\work\\dotNet\\WindowsApplication1\\App_data\\icon.png");
layCities.Style.SymbolScale = 0.8f;
layCities.MaxVisible = 40;
layCities.SRID = 4326;

//Set up a country label layer
SharpMap.Layers.LabelLayer layLabel = new SharpMap.Layers.LabelLayer("Country labels");
layLabel.DataSource = layCountries.DataSource;
layLabel.Enabled = true;
layLabel.LabelColumn = "Name";
layLabel.Style = new SharpMap.Styles.LabelStyle();
layLabel.Style.ForeColor = Color.White;
layLabel.Style.Font = new Font(FontFamily.GenericSerif, 8);
layLabel.Style.BackColor = new System.Drawing.SolidBrush(Color.FromArgb(128,255,0,0));
layLabel.MaxVisible = 90;
layLabel.MinVisible = 30;
layLabel.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Center;
layLabel.SRID = 4326;

//Set up a city label layer
SharpMap.Layers.LabelLayer layCityLabel = new SharpMap.Layers.LabelLayer("City labels");
layCityLabel.DataSource = layCities.DataSource;
layCityLabel.Enabled = true;
layCityLabel.LabelColumn = "Name";
layCityLabel.Style = new SharpMap.Styles.LabelStyle();
layCityLabel.Style.ForeColor = Color.Black;
layCityLabel.Style.Font = new Font(FontFamily.GenericSerif, 9);
layCityLabel.MaxVisible = layLabel.MinVisible;
layCityLabel.Style.HorizontalAlignment = SharpMap.Styles.LabelStyle.HorizontalAlignmentEnum.Left;
layCityLabel.Style.VerticalAlignment = SharpMap.Styles.LabelStyle.VerticalAlignmentEnum.Bottom;
layCityLabel.Style.Offset = new PointF(3, 3);
layCityLabel.SRID = 4326;

//Add the layers to the map object.
//The order we add them in are the order they are drawn, so we add the rivers last to put them on top
map.Layers.Add(layCountries);
map.Layers.Add(layRivers);
map.Layers.Add(layCities);
map.Layers.Add(layLabel);
map.Layers.Add(layCityLabel);


//limit the zoom to 360 degrees width
map.MaximumZoom = 360;
map.BackColor = Color.LightBlue;

map.Zoom = 360;
map.Center = new SharpMap.Geometries.Point(0,0);
}
  回复  引用    
#25楼 2006-11-10 11:13 | guo [未注册用户]
@guo
已经解决。没注意需要getmap()
  回复  引用    
#26楼 2006-11-10 11:17 | guo [未注册用户]
马老师什么时候写个关于sharpMap的那个空间索引介绍,不明白那个启发式是怎么得出来的。
  回复  引用    
#27楼 2006-12-05 19:51 | 小粟 [未注册用户]
为了方便大家交流,我新建了一个关于开源GIS项目分析的泡泡群213096,有兴趣的朋友快加入吧,我们一起为提高中国开源实力而努力!现在已经有一些博士、硕士、系统分析师、程序员加入了,现阶段分析的项目是SharpMap。
  回复  引用    
#28楼 2007-03-04 16:14 | wanghz [未注册用户]
请问大家有没有碰到过,地图中中文显示乱码的情况,又是如何解决的呢?
  回复  引用    
#29楼 2007-03-26 18:11 | 呵呵 [未注册用户]
我也遇到过乱码,不知道怎么回事
  回复  引用    
#30楼 2007-04-05 09:49 | xcl [未注册用户]
真是太感谢了,这次毕业设计的任务因为有你这篇文章而轻松多了
  回复  引用    
#31楼 2007-05-07 16:58 | 一支鱼刺 [未注册用户]
SharpMap中文乱码是因为编码问题。

ShapeFile 的默认的Encoding为UTF7,而我们的中文一般为GB2312,把下用下面的代码就可以显示中文了。

//Set up the countries layer
SharpMap.Layers.VectorLayer layCH_Other = new SharpMap.Layers.VectorLayer("CH_Other");
//Set the datasource to a shapefile in the App_data folder
SharpMap.Data.Providers.ShapeFile shp = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\CH\P1.shp"), true);
shp.Encoding = System.Text.Encoding.GetEncoding("gb2312");
layCH_Other.DataSource = shp ;
  回复  引用    
#32楼 2007-05-31 10:41 | shang [未注册用户]
我遇到的问题也是乱码,是在shape图层的属性查询过程中,经过类似:
SharpMap.Data.Providers.ShapeFile shp = new SharpMap.Data.Providers.ShapeFile(HttpContext.Current.Server.MapPath(@"~\App_data\CH\P1.shp"), true);
shp.Encoding = System.Text.Encoding.GetEncoding("gb2312");
这样的修改之后,读取shape文件的属性是可以显示中文了(我是将属性表读到FeatureDataSet里面),但现在的问题是:shape文件属性值可以显示中文,但属性表的字段名则显示为乱码,有什么办法解决么

  回复  引用  查看    
#33楼 2007-07-02 13:46 | 王计平      
为什么没有WPF或者是SilverLight的Render出来的界面的??我觉得这个比WinForm要有前途。
  回复  引用  查看    
#34楼 2007-07-02 13:47 | 王计平      
@王计平
自我回复一下,原来这个帖子是2006/02/24发布的,那时候还没有.net 3.0呢。
  回复  引用  查看    
#35楼 2007-11-21 12:01 | ithurricane      
很想学习GIS的内容

  回复  引用    
#36楼 2008-01-07 21:53 | yyk2627 [未注册用户]
我想根据坐标在地图上生成一个点,请问该怎么做啊!?
  回复  引用    
#37楼 2008-01-15 15:38 | HeroAndRose [未注册用户]
Providers名称空间,为SharpMap提供数据读(写)支持??
我想知道:SharpMap如何写shapefile(.shp)的文件了?
望楼主赐教!
  回复  引用    
#38楼 2008-03-14 22:41 | kaka123 [未注册用户]
请问你们是一个团队吗??

多谢你们整理出来这么多资料。

你们有qq群的话,我也想加入
我的qq407818347。
  回复  引用  查看    
#39楼 2008-08-15 02:16 | 崇山峻岭      
请问博主还有没有相关的sharpmap资料,请发到我邮箱sunjunlin2008@163.com,还有一个问题:在.net平台下请你推荐相关的开发工具.请回...作为一个刚刚入门 的弟子,请你多关照.....

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2006-03-09 21:58 编辑过
"五向定位"职业成长路线公开课(上海、南京、大连)
Google站内搜索


相关链接: