本文使用官方C# Driver,实现在MongoDB中存储,查询空间数据(矢量)

空间数据的存储
本例中,从一个矢量文件(shapefile格式)中读取矢量要素空间信息以及属性表,并写入到MongoDB中去,其中读取shapefile文件以及将空间信息转成json的功能通过Ogr库实现

  1. //打开MongoDB的Collection
  2. MongoDatabase db = server.GetDatabase("aa");
  3. MongoCollection colSheng = db.GetCollection("sheng");
  4. //使用Ogr库打开Shapefile文件
  5. DataSource ds = Ogr.Open(@"c:\temp\sheng.shp", 0);
  6. Layer lyr = ds.GetLayerByIndex(0);
  7. //读取要素数量和字段数量
  8. int feaCount = lyr.GetFeatureCount(0);
  9. int fieldCount = lyr.GetLayerDefn().GetFieldCount();
  10. //读取所有字段名
  11. List<string> fieldNames  =new List<string>();
  12. for (int i = 0; i < fieldCount; i++)
  13. {
  14. fieldNames.Add(lyr.GetLayerDefn().GetFieldDefn(i).GetName());
  15. }
  16. //循环将所有要素添加到MongoDB中
  17. for (int i = 0; i < feaCount; i++)
  18. {
  19. //使用Ogr库将矢量要素的空间信息转成Json格式
  20. Feature fea = lyr.GetFeature(i);
  21. Geometry geo = fea.GetGeometryRef();
  22. string json = geo.ExportToJson(null);
  23. BsonDocument doc = new BsonDocument();
  24. //将Json格式的空间信息存到Collection中
  25. //BsonValue bs = BsonValue.Create(json);                  //这种方法是不可以的,添加到库里之后无法使用空间查询语句查询
  26. BsonValue bs2 = BsonDocument.Parse(json);               //这种方法才是正确的
  27. //doc.Add(new BsonElement("geom", bs));
  28. doc.Add(new BsonElement("geo",bs2));
  29. //通过循环将所有字段的属性信息存入Collection中
  30. for (int j = 0; j < fieldCount; j++)
  31. {
  32. string tmpFieldVal = fea.GetFieldAsString(j);
  33. doc.Add(new BsonElement(fieldNames[j],tmpFieldVal));
  34. }
  35. var res  = colSheng.Insert<BsonDocument>(doc);
  36. }
 
然后,可以查看一下存储到MongoDB中的矢量数据是什么样的
在命令行中输入:
  1. > db.sheng.find().limit(1)
结果为
  1. { "_id" : ObjectId("5371bf4e1dbba31914224563"), "geo" : { "type" : "Polygon", "coordinates" : [ [ [ 89.8496, 14.093 ], [ 90.3933, 14.004 ], [ 90.2708, 13.4708 ], [ 89.7284, 13.5597 ], [ 89.8496, 14.093 ] ] ] }, "pyname" : "sx", "boxtype" : "inter", "date" : "2012/6/5 12:41:42" }
可以看到名称为geo的这个Field,里边存的就是矢量要素的坐标信息
 
空间查询与空间索引
可用的空间操作包括geointersect,geowithin,near等,参考http://docs.mongodb.org/manual/reference/operator/query-geospatial/
这里使用geointersect为例说明一下:

  1. //获取Collection
  2. MongoDatabase db = server.GetDatabase("aa");
  3. MongoCollection colSheng = db.GetCollection("sheng");
  4. //定义一个查询框或查询多边形
  5. var poly = GeoJson.Polygon<GeoJson2DCoordinates>(
  6. GeoJson.Position(100, 20),
  7. GeoJson.Position(110, 20),
  8. GeoJson.Position(110, 40),
  9. GeoJson.Position(100, 40),
  10. GeoJson.Position(100, 20));
  11. //以这个查询多边形为条件定义一条查询语句
  12. var queryFilter2 = Query.GeoIntersects("geo", poly);
  13. //进行查询,输出MongoCursor
  14. cur = colSheng.FindAs<BsonDocument>(queryFilter2).SetFields( "pyname", "date");
  15. //获取结果
  16. var res = cur.ToArray();
  17. for (int i = 0; i < res.Count(); i++)
  18. {
  19. BsonDocument tmpDoc = res.ElementAt(i);
  20. //do something you want
  21. }
关于空间索引,可参考http://docs.mongodb.org/manual/applications/geospatial-indexes/
这里不详细说了
 
空间查询运算的问题:
在使用GeoIntersect进行空间查询时,遇到了查询结果与ArcGIS不一致的情况,详细看了一下,像是MongoDB的一个BUG(目前使用的是2.6.0版本)
具体信息如下(在命令行中操作):
 
Collection中的坐标

  1. > db.test.find()
  2. { "_id" : ObjectId("535884771dbba31858ad2101"), "geo" : { "type" : "Polygon", "coordinates" : [ [ [ 96.722, 38.755 ], [ 97.3482, 38.6922 ], [ 97.1674, 38.0752 ], [ 96.5474, 38.1383 ], [ 96.722, 38.755 ] ] ] } }

使用的查询语句

  1. > db.test.find({ "geo" : { "$geoIntersects" : { "$geometry" : { "type" : "Polygon", "coordinates" : [[[91.0, 33.0], [102.0, 33.0], [102.0, 38.0], [91.0, 38.0], [91.0, 33.0]]] } } } })

查询结果:

  1. { "_id" : ObjectId("535884771dbba31858ad2101"), "geo" : { "type" : "Polygon", "coordinates" : [ [ [ 96.722, 38.755 ], [ 97.3482, 38.6922 ], [ 97.1674, 38.0752 ], [ 96.5474, 38.1383 ], [ 96.722, 38.755 ] ] ] } }

但可以看到,collection中只有一条记录,且该记录所有点的Y坐标均大于38.0,为什么查询结果里,这条记录与语句中的Box相交呢。。。很奇怪
因为有这样的问题,所以还不放心直接将空间查询用于实际应用,而是通过一种变通的方法进行简单的空间查询,测试后发现,可能是由于空间索引的问题,这种方式查询比自带的GeoIntersects方法要快

 
大致思路为:为每一条记录均生成一个最小外接矩形,得到其xmax,xmin,ymax,ymin四个边界值,用数值的形式保存至Collection中,每次进行空间查询时,首先通过最小外接矩形进行一次筛选,判断这些最小外接矩形与查询语句中多边形的最小外接矩形之间的关系,如果相交,那么进行第二步判断,通过Ogr组件判断实际的多边形是否相交,返回最后结果
首先是生成最小外接矩形的代码:

  1. //获取Collection
  2. MongoDatabase db = server.GetDatabase("aa");
  3. MongoCollection colsheng= db.GetCollection("sheng");
  4. //查询所有记录
  5. var cur = colsheng.FindAllAs<BsonDocument>();
  6. long totalCount = cur.Count();
  7. //遍历所有记录
  8. for (int i = 0; i <= totalCount/1000; i++)
  9. {
  10. if (i * 1000 >= totalCount) continue;
  11. int skip = i * 1000;
  12. var cur2 = cur.Clone<BsonDocument>().SetSkip(skip).SetLimit(1000);
  13. var lst = cur2.ToArray();
  14. for (int j = 0; j < lst.Count(); j++)
  15. {
  16. //获取一条记录对应的BsonDocument
  17. BsonDocument doc = lst[j];
  18. var id = doc["_id"];                    //该记录对应的ID
  19. BsonDocument geo = doc["geo"].ToBsonDocument();
  20. string geostr = geo[1].ToString();        //该记录对应空间信息的Json字符串
  21. List<double> coords = GetCoordLstFromString(geostr);        //解析Json串,获得所有点的坐标(这里子函数就省略了)
  22. double xmin = 181, xmax = -181, ymin = 91, ymax = -91;        //四个边界值,由于图层为经纬度,所以初值设为这些值
  23. //计算最大最小值
  24. for (int k = 0; k < coords.Count; k++)
  25. {
  26. if (k % 2 == 0)
  27. {
  28. if (coords[k] < xmin) xmin = coords[k];
  29. if (coords[k] > xmax) xmax = coords[k];
  30. }
  31. else
  32. {
  33. if (coords[k] < ymin) ymin = coords[k];
  34. if (coords[k] > ymax) ymax = coords[k];
  35. }
  36. }
  37. //将最大最小值写入Collection
  38. var tmpQuery = Query.EQ("_id", id);
  39. var tmpUpdate = MongoDB.Driver.Builders.Update.Set("xmax", xmax);
  40. var tmpres = col02c.Update(tmpQuery, tmpUpdate);
  41. tmpUpdate = MongoDB.Driver.Builders.Update.Set("xmin", xmin);
  42. tmpres = col02c.Update(tmpQuery, tmpUpdate);
  43. tmpUpdate = MongoDB.Driver.Builders.Update.Set("ymax", ymax);
  44. tmpres = col02c.Update(tmpQuery, tmpUpdate);
  45. tmpUpdate = MongoDB.Driver.Builders.Update.Set("ymin", ymin);
  46. tmpres = col02c.Update(tmpQuery, tmpUpdate);
  47. }
  48. }
 
然后是查询的代码:

  1. //获取Collection
  2. MongoDatabase db = server.GetDatabase("aa");
  3. MongoCollection colSheng = db.GetCollection("zy02c");
  4. //第一步,通过四边界筛选,
  5. var query = Query.And(Query.GT("xmax", 91.0), Query.LT("xmin", 102.0), Query.GT("ymax", 33.0), Query.LT("ymin", 38.0));
  6. var cur = colSheng.FindAs<BsonDocument>(query);
  7. //定义第二空间运算时的条件多边形(Ogr格式的定义)
  8. Geometry queryGeoLR = new Geometry(wkbGeometryType.wkbLinearRing);
  9. queryGeoLR.AddPoint(91.0, 33.0,0);
  10. queryGeoLR.AddPoint(102.0, 33.0,0);
  11. queryGeoLR.AddPoint(102.0, 38.0,0);
  12. queryGeoLR.AddPoint(91.0, 38.0,0);
  13. queryGeoLR.AddPoint(91.0, 33.0,0);
  14. Geometry queryGeo = new Geometry(wkbGeometryType.wkbPolygon);
  15. queryGeo.AddGeometry(queryGeoLR);
  16. //循环查询到的结果
  17. var lst = cur.ToArray();
  18. for (int i = lst.Length-1; i >=0; i--)
  19. {
  20. //获取当前记录对应的BsonDocument
  21. BsonDocument doc = lst[i];
  22. var id = doc["_id"];            //当前记录的ID
  23. BsonDocument geo = doc["geo"].ToBsonDocument();
  24. string geostr = geo[1].ToString();        //当前记录对应空间信息的Json字符串
  25. //通过Json串获取坐标值,并生成对应的Geometry对象
  26. List<double> coords = GetCoordLstFromString(geostr);
  27. Geometry resGeoLR = new Geometry(wkbGeometryType.wkbLinearRing);
  28. for (int j = 0; j < coords.Count / 2; j++)
  29. {
  30. resGeoLR.AddPoint_2D(coords[j], coords[j + 1]);
  31. }
  32. resGeoLR.AddPoint_2D(coords[0], coords[1]);
  33. Geometry resGeo = new Geometry(wkbGeometryType.wkbPolygon);
  34. resGeo.AddGeometry(resGeoLR);
  35. //判断是该Geometry与条件多边形是否相交
  36. if (resGeo.Intersects(queryGeo))
  37. {
  38. //do something
  39. }
  40. }

MongoDB中空间数据的存储和操作的更多相关文章

  1. MongoDB中数组类型相关的操作

    概述 在MongoDB的模式中,我们经常将一些数据存储到数组类型中,即我们常见的嵌套模式设计的一种实现方式.数组的这种设计实现方式在关系数据库中是没有或者说不常见的.所以,通过本文我们来梳理一下Mon ...

  2. 利用“海底捞算法”在MongoDB中优雅地存储一棵树

    目前常见的树形结构数据库存储方案有以下四种,但是在处理无限深度.海量数据的树结构时,都存在一些问题: 1)Adjacency List(邻接表):每个节点仅记录父节点主键.优点是简单,缺点是访问子树需 ...

  3. MongoDB地理空间数据存储及检索

    目录 1.存入地理数据 GeoJSON数据存入 1.Ponit 点数据 2.LineString 线数据(多段线) 3. Polygon 多边形数据 4.MultiPoint多点.MultiLineS ...

  4. 在MongoDB中使用JOIN操作

    SQL与NoSQL最大的不同之一就是不支持JOIN,在传统的数据库中,SQL JOIN子句允许你使用普通的字段,在两个或者是更多表中的组合表中的每行数据.例如,如果你有表books和publisher ...

  5. php操作mongodb中的ISODate格式日期

    mongodb 中数据记录的日期格式为"dateCreated" : ISODate("2011-12-20T07:22:50.836Z")经过翻阅php官网中 ...

  6. 【转载】MongoDB中的MapReduce 高级操作介绍

    转载自残缺的孤独 1.概述 MongoDB中的MapReduce相当于关系数据库中的group by.使用MapReduce要实现两个函数Map和Reduce函数.Map函数调用emit(key,va ...

  7. MongoDB中的映射,限制记录和记录拼排序 文档的插入查询更新删除操作

    映射 在 MongoDB 中,映射(Projection)指的是只选择文档中的必要数据,而非全部数据.如果文档有 5 个字段,而你只需要显示 3 个,则只需选择 3 个字段即可. find() 方法 ...

  8. Java POJO类直接存储在MongoDB中

    记录Java POJO类直接存储在MongoDB中的策略. maven: <dependency> <groupId>org.mongodb</groupId> & ...

  9. 爬取豆瓣电影TOP 250的电影存储到mongodb中

    爬取豆瓣电影TOP 250的电影存储到mongodb中 1.创建项目sp1 PS D:\scrapy> scrapy.exe startproject douban 2.创建一个爬虫 PS D: ...

随机推荐

  1. python- 动态加载目录下所有的类

    # 背景 自动化测试框架中model层下有很多类,用来操作mysql的,使用的时候需要把全部的类加载进来,需要使用到动态加载类 # 解决方法 使用pkgutil,内置的方法,常用的话有两个方法 ite ...

  2. 剑指offer编程题Java实现——面试题4后的相关题目

    题目描述: 有两个排序的数字A1和A2,内存在A1的末尾有足够多的空余空间容纳A2.请实现一个函数,把A2中的所有数字插入到A1中并且所有的数字是排序的. 还是利用从后向前比较两个数组中的数字的方式来 ...

  3. Flask系列06--(中间件)Flask的特殊装饰器 before_request,after_request, errorhandler

    一.使用 Flask中的特殊装饰器(中间件)方法常用的有三个 @app.before_request # 在请求进入视图函数之前 @app.after_request # 在请求结束视图函数之后 响应 ...

  4. redis 在 Linux下的安装

    redis  和 nginx 一样,都是C语言编写的,所以我们的准备gcc 环境, 之前已经准备好了 没有准备的话(CentOs  有自带):yum install gcc-c++ 解压redis : ...

  5. jQuery基础(4)- 位置信息、事件流、事件对象、事件代理、jquery事件

    一.jQuery的位置信息 jQuery的位置信是JS的client系列.offset系列.scroll系列封装好的一些简便api. 1.宽度和高度 a.获取宽度和高度,例如: .width() // ...

  6. Asp.net管线事件(页面请求周期)

    在处理该请求时将由 HttpApplication 类执行以下事件. 希望扩展 HttpApplication 类的开发人员尤其需要注意这些事件. . 对请求进行验证,将检查浏览器发送的信息,并确定其 ...

  7. 善用Intellij插件可大幅提升我们的效率

    转自 :https://www.jianshu.com/p/686ba0ae4ac2 1. .ignore 生成各种ignore文件,一键创建git ignore文件的模板,免得自己去写 截图: 2. ...

  8. PyCharm下载与激活

    1.集成开发环境(IDE:Integrated Development Environment)PyCharm下载地址:https://www.jetbrains.com/pycharm/downlo ...

  9. Spring Cloud Hystrix

    接上篇: Spring Cloud Eureka 使用命令开启两个服务提供者 java -jar .\hello--SNAPSHOT.jar --server.port= java -jar .\he ...

  10. Evenbus简单用法

    Evenbus是一个开源插件,可以帮我们在app里面进行数据传递,传递的对象为Object,就是说可以传输任何对象,但是一般为了拓展性和维护性,我们都用来传输Bean类型. 这个插件最重要的是注册和反 ...