ShapeFile.cs
上传用户:sex100000
上传日期:2013-11-09
资源大小:1377k
文件大小:35k
源码类别:

GIS编程

开发平台:

C#

  1. // Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
  2. //
  3. // This file is part of SharpMap.
  4. // SharpMap is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation; either version 2 of the License, or
  7. // (at your option) any later version.
  8. // 
  9. // SharpMap is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. // GNU Lesser General Public License for more details.
  13. // You should have received a copy of the GNU Lesser General Public License
  14. // along with SharpMap; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  16. using System;
  17. using System.Collections.Generic;
  18. using System.Collections.ObjectModel;
  19. using System.IO;
  20. using System.Drawing;
  21. namespace SharpMap.Data.Providers
  22. {
  23. /// <summary>
  24. /// Shapefile geometry type.
  25. /// </summary>
  26. public enum ShapeType : int
  27. {
  28. /// <summary>
  29. /// Null shape with no geometric data
  30. /// </summary>
  31. Null = 0,
  32. /// <summary>
  33. /// A point consists of a pair of double-precision coordinates.
  34. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.Point"/>
  35. /// </summary>
  36. Point = 1,
  37. /// <summary>
  38. /// PolyLine is an ordered set of vertices that consists of one or more parts. A part is a
  39. /// connected sequence of two or more points. Parts may or may not be connected to one
  40. /// another. Parts may or may not intersect one another.
  41. /// SharpMap interpretes this as either <see cref="SharpMap.Geometries.LineString"/> or <see cref="SharpMap.Geometries.MultiLineString"/>
  42. /// </summary>
  43. PolyLine = 3,
  44. /// <summary>
  45. /// A polygon consists of one or more rings. A ring is a connected sequence of four or more
  46. /// points that form a closed, non-self-intersecting loop. A polygon may contain multiple
  47. /// outer rings. The order of vertices or orientation for a ring indicates which side of the ring
  48. /// is the interior of the polygon. The neighborhood to the right of an observer walking along
  49. /// the ring in vertex order is the neighborhood inside the polygon. Vertices of rings defining
  50. /// holes in polygons are in a counterclockwise direction. Vertices for a single, ringed
  51. /// polygon are, therefore, always in clockwise order. The rings of a polygon are referred to
  52. /// as its parts.
  53. /// SharpMap interpretes this as either <see cref="SharpMap.Geometries.Polygon"/> or <see cref="SharpMap.Geometries.MultiPolygon"/>
  54. /// </summary>
  55. Polygon = 5,
  56. /// <summary>
  57. /// A MultiPoint represents a set of points.
  58. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.MultiPoint"/>
  59. /// </summary>
  60. Multipoint = 8,
  61. /// <summary>
  62. /// A PointZ consists of a triplet of double-precision coordinates plus a measure.
  63. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.Point"/>
  64. /// </summary>
  65. PointZ = 11,
  66. /// <summary>
  67. /// A PolyLineZ consists of one or more parts. A part is a connected sequence of two or
  68. /// more points. Parts may or may not be connected to one another. Parts may or may not
  69. /// intersect one another.
  70. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.LineString"/> or <see cref="SharpMap.Geometries.MultiLineString"/>
  71. /// </summary>
  72. PolyLineZ = 13,
  73. /// <summary>
  74. /// A PolygonZ consists of a number of rings. A ring is a closed, non-self-intersecting loop.
  75. /// A PolygonZ may contain multiple outer rings. The rings of a PolygonZ are referred to as
  76. /// its parts.
  77. /// SharpMap interpretes this as either <see cref="SharpMap.Geometries.Polygon"/> or <see cref="SharpMap.Geometries.MultiPolygon"/>
  78. /// </summary>
  79. PolygonZ = 15,
  80. /// <summary>
  81. /// A MultiPointZ represents a set of <see cref="PointZ"/>s.
  82. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.MultiPoint"/>
  83. /// </summary>
  84. MultiPointZ = 18,
  85. /// <summary>
  86. /// A PointM consists of a pair of double-precision coordinates in the order X, Y, plus a measure M.
  87. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.Point"/>
  88. /// </summary>
  89. PointM = 21,
  90. /// <summary>
  91. /// A shapefile PolyLineM consists of one or more parts. A part is a connected sequence of
  92. /// two or more points. Parts may or may not be connected to one another. Parts may or may
  93. /// not intersect one another.
  94. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.LineString"/> or <see cref="SharpMap.Geometries.MultiLineString"/>
  95. /// </summary>
  96. PolyLineM = 23,
  97. /// <summary>
  98. /// A PolygonM consists of a number of rings. A ring is a closed, non-self-intersecting loop.
  99. /// SharpMap interpretes this as either <see cref="SharpMap.Geometries.Polygon"/> or <see cref="SharpMap.Geometries.MultiPolygon"/>
  100. /// </summary>
  101. PolygonM = 25,
  102. /// <summary>
  103. /// A MultiPointM represents a set of <see cref="PointM"/>s.
  104. /// SharpMap interpretes this as <see cref="SharpMap.Geometries.MultiPoint"/>
  105. /// </summary>
  106. MultiPointM = 28,
  107. /// <summary>
  108. /// A MultiPatch consists of a number of surface patches. Each surface patch describes a
  109. /// surface. The surface patches of a MultiPatch are referred to as its parts, and the type of
  110. /// part controls how the order of vertices of an MultiPatch part is interpreted.
  111. /// SharpMap doesn't support this feature type.
  112. /// </summary>
  113. MultiPatch = 31
  114. };
  115. /// <summary>
  116. /// Shapefile dataprovider
  117. /// </summary>
  118. /// <remarks>
  119. /// <para>The ShapeFile provider is used for accessing ESRI ShapeFiles. The ShapeFile should at least contain the
  120. /// [filename].shp, [filename].idx, and if feature-data is to be used, also [filename].dbf file.</para>
  121. /// <para>The first time the ShapeFile is accessed, SharpMap will automatically create a spatial index
  122. /// of the shp-file, and save it as [filename].shp.sidx. If you change or update the contents of the .shp file,
  123. /// delete the .sidx file to force SharpMap to rebuilt it. In web applications, the index will automatically
  124. /// be cached to memory for faster access, so to reload the index, you will need to restart the web application
  125. /// as well.</para>
  126. /// <para>
  127. /// M and Z values in a shapefile is ignored by SharpMap.
  128. /// </para>
  129. /// </remarks>
  130. /// <example>
  131. /// Adding a datasource to a layer:
  132. /// <code lang="C#">
  133. /// SharpMap.Layers.VectorLayer myLayer = new SharpMap.Layers.VectorLayer("My layer");
  134. /// myLayer.DataSource = new SharpMap.Data.Providers.ShapeFile(@"C:dataMyShapeData.shp");
  135. /// </code>
  136. /// </example>
  137. public class ShapeFile : SharpMap.Data.Providers.IProvider, IDisposable
  138. {
  139. private ShapeType _ShapeType;
  140. private string _Filename;
  141. private SharpMap.Geometries.BoundingBox _Envelope;
  142. private DbaseReader dbaseFile;
  143. private FileStream fsShapeIndex;
  144. private BinaryReader brShapeIndex;
  145. private FileStream fsShapeFile;
  146. private BinaryReader brShapeFile;
  147. private bool _FileBasedIndex;
  148. private bool _IsOpen;
  149. private bool _CoordsysReadFromFile = false;
  150. //private int[] _LengthOfRecord;
  151. private int _FeatureCount;
  152. /// <summary>
  153. /// Tree used for fast query of data
  154. /// </summary>
  155. private SharpMap.Utilities.SpatialIndexing.QuadTree tree;
  156. /// <summary>
  157. /// Initializes a ShapeFile DataProvider without a file-based spatial index.
  158. /// </summary>
  159. /// <param name="filename">Path to shape file</param>
  160. public ShapeFile(string filename) : this(filename,false) { }
  161. /// <summary>
  162. /// Initializes a ShapeFile DataProvider.
  163. /// </summary>
  164. /// <remarks>
  165. /// <para>If FileBasedIndex is true, the spatial index will be read from a local copy. If it doesn't exist,
  166. /// it will be generated and saved to [filename] + '.sidx'.</para>
  167. /// <para>Using a file-based index is especially recommended for ASP.NET applications which will speed up
  168. /// start-up time when the cache has been emptied.
  169. /// </para>
  170. /// </remarks>
  171. /// <param name="filename">Path to shape file</param>
  172. /// <param name="fileBasedIndex">Use file-based spatial index</param>
  173. public ShapeFile(string filename, bool fileBasedIndex)
  174. {
  175. _Filename = filename;
  176. _FileBasedIndex = fileBasedIndex;
  177. //Initialize DBF
  178. string dbffile = _Filename.Substring(0, _Filename.LastIndexOf(".")) + ".dbf";
  179. if (File.Exists(dbffile))
  180. dbaseFile = new DbaseReader(dbffile);
  181. //Parse shape header
  182. ParseHeader();
  183. //Read projection file
  184. ParseProjection();
  185. }
  186. /// <summary>
  187. /// Opens the datasource
  188. /// </summary>
  189. public void Open()
  190. {
  191. // TODO:
  192. // Get a Connector.  The connector returned is guaranteed to be connected and ready to go.
  193. // Pooling.Connector connector = Pooling.ConnectorPool.ConnectorPoolManager.RequestConnector(this,true);
  194. if (!_IsOpen)
  195. {
  196. fsShapeIndex = new FileStream(_Filename.Remove(_Filename.Length - 4, 4) + ".shx", FileMode.Open, FileAccess.Read);
  197. brShapeIndex = new BinaryReader(fsShapeIndex, System.Text.Encoding.Unicode);
  198. fsShapeFile = new FileStream(_Filename, FileMode.Open, FileAccess.Read);
  199. brShapeFile = new BinaryReader(fsShapeFile);
  200. InitializeShape(_Filename, _FileBasedIndex);
  201. if (dbaseFile != null)
  202. dbaseFile.Open();
  203. _IsOpen = true;
  204. }
  205. }
  206. /// <summary>
  207. /// Closes the datasource
  208. /// </summary>
  209. public void Close()
  210. {
  211. if (!disposed)
  212. {
  213. //TODO: (ConnectionPooling)
  214. /* if (connector != null)
  215. { Pooling.ConnectorPool.ConnectorPoolManager.Release...()
  216. }*/
  217. if (_IsOpen)
  218. {
  219. brShapeFile.Close();
  220. fsShapeFile.Close();
  221. brShapeIndex.Close();
  222. fsShapeIndex.Close();
  223. if (dbaseFile != null)
  224. dbaseFile.Close();
  225. _IsOpen = false;
  226. }
  227. }
  228. }
  229. private void InitializeShape(string filename, bool FileBasedIndex)
  230. {
  231. if (!File.Exists(filename))
  232. throw new FileNotFoundException(String.Format("Could not find file "{0}"", filename));
  233. if (!filename.ToLower().EndsWith(".shp"))
  234. throw (new System.Exception("Invalid shapefile filename: " + filename));
  235. LoadSpatialIndex(FileBasedIndex); //Load spatial index
  236. }
  237. private SharpMap.CoordinateSystems.ICoordinateSystem _CoordinateSystem;
  238. /// <summary>
  239. /// Gets or sets the coordinate system of the ShapeFile. If a shapefile has 
  240. /// a corresponding [filename].prj file containing a Well-Known Text 
  241. /// description of the coordinate system this will automatically be read.
  242. /// If this is not the case, the coordinate system will default to null.
  243. /// </summary>
  244. /// <exception cref="ApplicationException">An exception is thrown if the coordinate system is read from file.</exception>
  245. public SharpMap.CoordinateSystems.ICoordinateSystem CoordinateSystem
  246. {
  247. get { return _CoordinateSystem; }
  248. set {
  249. if (_CoordsysReadFromFile)
  250. throw new ApplicationException("Coordinate system is specified in projection file and is read only");
  251. _CoordinateSystem = value; }
  252. }
  253. /// <summary>
  254. /// Returns true if the datasource is currently open
  255. /// </summary>
  256. public bool IsOpen
  257. {
  258. get { return _IsOpen; }
  259. }
  260. /// <summary>
  261. /// Gets the <see cref="SharpMap.Data.Providers.ShapeType">shape geometry type</see> in this shapefile.
  262. /// </summary>
  263. /// <remarks>
  264. /// The property isn't set until the first time the datasource has been opened,
  265. /// and will throw an exception if this property has been called since initialization. 
  266. /// <para>All the non-Null shapes in a shapefile are required to be of the same shape
  267. /// type.</para>
  268. /// </remarks>
  269. public ShapeType ShapeType
  270. {
  271. get {
  272. return _ShapeType; }
  273. }
  274. /// <summary>
  275. /// Gets or sets the filename of the shapefile
  276. /// </summary>
  277. /// <remarks>If the filename changes, indexes will be rebuilt</remarks>
  278. public string Filename
  279. {
  280. get { return _Filename; }
  281. set {
  282. if (value != _Filename)
  283. {
  284. _Filename = value;
  285. if (this.IsOpen)
  286. throw new ApplicationException("Cannot change filename while datasource is open");
  287. ParseHeader();
  288. ParseProjection();
  289. tree = null;
  290. }
  291. }
  292. }
  293. /// <summary>
  294. /// Gets or sets the encoding used for parsing strings from the DBase DBF file.
  295. /// </summary>
  296. /// <remarks>
  297. /// The DBase default encoding is <see cref="System.Text.Encoding.UTF7"/>.
  298. /// </remarks>
  299. public System.Text.Encoding Encoding
  300. {
  301. get { return dbaseFile.Encoding; }
  302. set { dbaseFile.Encoding = value; }
  303. }
  304. #region Disposers and finalizers
  305. private bool disposed = false;
  306. /// <summary>
  307. /// Disposes the object
  308. /// </summary>
  309. public void Dispose()
  310. {
  311. Dispose(true);
  312. GC.SuppressFinalize(this);
  313. }
  314. private void Dispose(bool disposing)
  315. {
  316. if (!disposed)
  317. {
  318. if (disposing)
  319. {
  320. Close();
  321. _Envelope = null;
  322. tree = null;
  323. }
  324. disposed = true;
  325. }
  326. }
  327. /// <summary>
  328. /// Finalizes the object
  329. /// </summary>
  330. ~ShapeFile()
  331. {
  332. this.Dispose();
  333. }
  334. #endregion
  335. /// <summary>
  336. /// Reads and parses the header of the .shx index file
  337. /// </summary>
  338. private void ParseHeader()
  339. {
  340. fsShapeIndex = new FileStream(_Filename.Remove(_Filename.Length - 4, 4) + ".shx", FileMode.Open, FileAccess.Read);
  341. brShapeIndex = new BinaryReader(fsShapeIndex, System.Text.Encoding.Unicode);
  342. brShapeIndex.BaseStream.Seek(0, 0);
  343. //Check file header
  344. if (brShapeIndex.ReadInt32() != 170328064) //File Code is actually 9994, but in Little Endian Byte Order this is '170328064'
  345. throw (new ApplicationException("Invalid Shapefile Index (.shx)"));
  346. brShapeIndex.BaseStream.Seek(24, 0); //seek to File Length
  347. int IndexFileSize = SwapByteOrder(brShapeIndex.ReadInt32()); //Read filelength as big-endian. The length is based on 16bit words
  348. _FeatureCount = (2 * IndexFileSize - 100) / 8; //Calculate FeatureCount. Each feature takes up 8 bytes. The header is 100 bytes
  349. brShapeIndex.BaseStream.Seek(32, 0); //seek to ShapeType
  350. _ShapeType = (ShapeType)brShapeIndex.ReadInt32();
  351. //Read the spatial bounding box of the contents
  352. brShapeIndex.BaseStream.Seek(36, 0); //seek to box
  353. _Envelope = new SharpMap.Geometries.BoundingBox(brShapeIndex.ReadDouble(), brShapeIndex.ReadDouble(), brShapeIndex.ReadDouble(), brShapeIndex.ReadDouble());
  354. brShapeIndex.Close();
  355. fsShapeIndex.Close();
  356. }
  357. /// <summary>
  358. /// Reads and parses the projection if a projection file exists
  359. /// </summary>
  360. private void ParseProjection()
  361. {
  362. string projfile = Path.GetDirectoryName(Filename) + "\" + Path.GetFileNameWithoutExtension(Filename) + ".prj";
  363. if (System.IO.File.Exists(projfile))
  364. {
  365. try
  366. {
  367. string wkt = System.IO.File.ReadAllText(projfile);
  368. _CoordinateSystem = (SharpMap.CoordinateSystems.ICoordinateSystem)SharpMap.Converters.WellKnownText.CoordinateSystemWktReader.Parse(wkt);
  369. _CoordsysReadFromFile = true;
  370. }
  371. catch(System.Exception ex) {
  372. System.Diagnostics.Trace.TraceWarning("Coordinate system file '" + projfile + "' found, but could not be parsed. WKT parser returned:" + ex.Message);
  373. throw (ex);
  374. }
  375. }
  376. }
  377. /// <summary>
  378. /// Reads the record offsets from the .shx index file and returns the information in an array
  379. /// </summary>
  380. private int[] ReadIndex()
  381. {
  382. int[] OffsetOfRecord = new int[ _FeatureCount ];
  383. brShapeIndex.BaseStream.Seek(100, 0);  //skip the header
  384. for (int x=0; x < _FeatureCount; ++x ) 
  385. {
  386. OffsetOfRecord[x] = 2 * SwapByteOrder(brShapeIndex.ReadInt32()); //Read shape data position // ibuffer);
  387. brShapeIndex.BaseStream.Seek(brShapeIndex.BaseStream.Position + 4, 0); //Skip content length
  388. }
  389. return OffsetOfRecord;
  390. }
  391. /// <summary>
  392. /// Gets the file position of the n'th shape
  393. /// </summary>
  394. /// <param name="n">Shape ID</param>
  395. /// <returns></returns>
  396. private int GetShapeIndex(uint n)
  397. {
  398. brShapeIndex.BaseStream.Seek(100+n*8, 0);  //seek to the position of the index
  399. return 2 * SwapByteOrder(brShapeIndex.ReadInt32()); //Read shape data position
  400. }
  401. ///<summary>
  402. ///Swaps the byte order of an int32
  403. ///</summary>
  404. /// <param name="i">Integer to swap</param>
  405. /// <returns>Byte Order swapped int32</returns>
  406. private int SwapByteOrder (int i) 
  407. {
  408. byte[] buffer = BitConverter.GetBytes(i);
  409. Array.Reverse(buffer, 0, buffer.Length);
  410. return BitConverter.ToInt32(buffer, 0);
  411. }
  412. /// <summary>
  413. /// Loads a spatial index from a file. If it doesn't exist, one is created and saved
  414. /// </summary>
  415. /// <param name="filename"></param>
  416. /// <returns>QuadTree index</returns>
  417. private Utilities.SpatialIndexing.QuadTree CreateSpatialIndexFromFile(string filename)
  418. {
  419. if (System.IO.File.Exists(filename + ".sidx"))
  420. {
  421. try
  422. {
  423. return Utilities.SpatialIndexing.QuadTree.FromFile(filename + ".sidx");
  424. }
  425. catch(Utilities.SpatialIndexing.QuadTree.ObsoleteFileFormatException)
  426. {
  427. System.IO.File.Delete(filename + ".sidx");
  428. return CreateSpatialIndexFromFile(filename);
  429. }
  430. catch (System.Exception ex) { throw ex; }
  431. }
  432. else
  433. {
  434. Utilities.SpatialIndexing.QuadTree tree = CreateSpatialIndex(_Filename);
  435. tree.SaveIndex(filename + ".sidx");
  436. return tree;
  437. }
  438. }
  439. /// <summary>
  440. /// Generates a spatial index for a specified shape file.
  441. /// </summary>
  442. /// <param name="filename"></param>
  443. private Utilities.SpatialIndexing.QuadTree CreateSpatialIndex(string filename)
  444. {
  445. List<Utilities.SpatialIndexing.QuadTree.BoxObjects> objList = new List<Utilities.SpatialIndexing.QuadTree.BoxObjects>();
  446. //Convert all the geometries to boundingboxes 
  447. uint i = 0;
  448. foreach (SharpMap.Geometries.BoundingBox box in GetAllFeatureBoundingBoxes())
  449. {
  450. if (!double.IsNaN(box.Left) && !double.IsNaN(box.Right) && !double.IsNaN(box.Bottom) && !double.IsNaN(box.Top))
  451. {
  452. Utilities.SpatialIndexing.QuadTree.BoxObjects g = new Utilities.SpatialIndexing.QuadTree.BoxObjects();
  453. g.box = box;
  454. g.ID = i;
  455. objList.Add(g);
  456. i++;
  457. }
  458. }
  459. Utilities.SpatialIndexing.Heuristic heur;
  460. heur.maxdepth = (int)Math.Ceiling(Math.Log(this.GetFeatureCount(), 2));
  461. heur.minerror = 10;
  462. heur.tartricnt = 5;
  463. heur.mintricnt = 2;
  464. return new Utilities.SpatialIndexing.QuadTree(objList, 0, heur);
  465. }
  466. private void LoadSpatialIndex() { LoadSpatialIndex(false,false); }
  467. private void LoadSpatialIndex(bool LoadFromFile) { LoadSpatialIndex(false, LoadFromFile); }
  468. private void LoadSpatialIndex(bool ForceRebuild, bool LoadFromFile)
  469. {
  470. //Only load the tree if we haven't already loaded it, or if we want to force a rebuild
  471. if (tree == null || ForceRebuild)
  472. {
  473. // Is this a web application? If so lets store the index in the cache so we don't
  474. // need to rebuild it for each request
  475. if (System.Web.HttpContext.Current != null)
  476. {
  477. //Check if the tree exists in the cache
  478. if (System.Web.HttpContext.Current.Cache[_Filename] != null)
  479. tree = (Utilities.SpatialIndexing.QuadTree)System.Web.HttpContext.Current.Cache[_Filename];
  480. else
  481. {
  482. if (!LoadFromFile)
  483. tree = CreateSpatialIndex(_Filename);
  484. else
  485. tree = CreateSpatialIndexFromFile(_Filename);
  486. //Store the tree in the web cache
  487. //TODO: Remove this when connection pooling is implemented
  488. System.Web.HttpContext.Current.Cache.Insert(_Filename, tree, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromDays(1));
  489. }
  490. }
  491. else
  492. if (!LoadFromFile)
  493. tree = CreateSpatialIndex(_Filename);
  494. else
  495. tree = CreateSpatialIndexFromFile(_Filename);
  496. }
  497. }
  498. /// <summary>
  499. /// Forces a rebuild of the spatial index. If the instance of the ShapeFile provider
  500. /// uses a file-based index the file is rewritten to disk.
  501. /// </summary>
  502. public void RebuildSpatialIndex()
  503. {
  504. if (this._FileBasedIndex)
  505. {
  506. if (System.IO.File.Exists(_Filename + ".sidx"))
  507. System.IO.File.Delete(_Filename + ".sidx");
  508. tree = CreateSpatialIndexFromFile(_Filename);
  509. }
  510. else
  511. tree = CreateSpatialIndex(_Filename);
  512. if (System.Web.HttpContext.Current != null)
  513. //TODO: Remove this when connection pooling is implemented:
  514. System.Web.HttpContext.Current.Cache.Insert(_Filename, tree, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromDays(1));
  515. }
  516. /// <summary>
  517. /// Reads all boundingboxes of features in the shapefile. This is used for spatial indexing.
  518. /// </summary>
  519. /// <returns></returns>
  520. private List<SharpMap.Geometries.BoundingBox> GetAllFeatureBoundingBoxes()
  521. {
  522. int[] offsetOfRecord = ReadIndex(); //Read the whole .idx file
  523. List<SharpMap.Geometries.BoundingBox> boxes = new List<SharpMap.Geometries.BoundingBox>();
  524. if (_ShapeType == ShapeType.Point)
  525. {
  526. for (int a = 0; a < _FeatureCount; ++a)
  527. {
  528. fsShapeFile.Seek(offsetOfRecord[a]+8, 0); //skip record number and content length
  529. if ((ShapeType)brShapeFile.ReadInt32() != ShapeType.Null)
  530. {
  531. double x = brShapeFile.ReadDouble();
  532. double y = brShapeFile.ReadDouble();
  533. boxes.Add(new SharpMap.Geometries.BoundingBox(x, y, x, y));
  534. }
  535. }
  536. }
  537. else
  538. {
  539. for (int a = 0; a < _FeatureCount; ++a)
  540. {
  541. fsShapeFile.Seek(offsetOfRecord[a] + 8, 0); //skip record number and content length
  542. if ((ShapeType)brShapeFile.ReadInt32() != ShapeType.Null)
  543. boxes.Add(new SharpMap.Geometries.BoundingBox(brShapeFile.ReadDouble(), brShapeFile.ReadDouble(), brShapeFile.ReadDouble(), brShapeFile.ReadDouble()));
  544. }
  545. }
  546. return boxes;
  547. }
  548. #region IProvider Members
  549. /// <summary>
  550. /// Returns geometries whose bounding box intersects 'bbox'
  551. /// </summary>
  552. /// <remarks>
  553. /// <para>Please note that this method doesn't guarantee that the geometries returned actually intersect 'bbox', but only
  554. /// that their boundingbox intersects 'bbox'.</para>
  555. /// <para>This method is much faster than the QueryFeatures method, because intersection tests
  556. /// are performed on objects simplifed by their boundingbox, and using the Spatial Index.</para>
  557. /// </remarks>
  558. /// <param name="bbox"></param>
  559. /// <returns></returns>
  560. public Collection<SharpMap.Geometries.Geometry> GetGeometriesInView(SharpMap.Geometries.BoundingBox bbox)
  561. {
  562. //Use the spatial index to get a list of features whose boundingbox intersects bbox
  563. Collection<uint> objectlist = GetObjectIDsInView(bbox);
  564. if (objectlist.Count == 0) //no features found. Return an empty set
  565. return new Collection<SharpMap.Geometries.Geometry>();
  566.             //Collection<SharpMap.Geometries.Geometry> geometries = new Collection<SharpMap.Geometries.Geometry>(objectlist.Count);
  567.             Collection<SharpMap.Geometries.Geometry> geometries = new Collection<SharpMap.Geometries.Geometry>();
  568. for (int i = 0; i < objectlist.Count; i++)
  569. {
  570. SharpMap.Geometries.Geometry g = GetGeometryByID(objectlist[i]);
  571. if(g!=null)
  572. geometries.Add(g);
  573. }
  574. return geometries;
  575. }
  576. /// <summary>
  577. /// Returns all objects whose boundingbox intersects bbox.
  578. /// </summary>
  579. /// <remarks>
  580. /// <para>
  581. /// Please note that this method doesn't guarantee that the geometries returned actually intersect 'bbox', but only
  582. /// that their boundingbox intersects 'bbox'.
  583. /// </para>
  584. /// </remarks>
  585. /// <param name="bbox"></param>
  586. /// <param name="ds"></param>
  587. /// <returns></returns>
  588. public void ExecuteIntersectionQuery(SharpMap.Geometries.BoundingBox bbox, SharpMap.Data.FeatureDataSet ds)
  589. {
  590. //Use the spatial index to get a list of features whose boundingbox intersects bbox
  591. Collection<uint> objectlist = GetObjectIDsInView(bbox);
  592. SharpMap.Data.FeatureDataTable dt = dbaseFile.NewTable;
  593. for (int i = 0; i < objectlist.Count; i++)
  594. {
  595. SharpMap.Data.FeatureDataRow fdr = dbaseFile.GetFeature(objectlist[i], dt);
  596. fdr.Geometry = ReadGeometry(objectlist[i]);
  597. if (fdr.Geometry != null)
  598. if (fdr.Geometry.GetBoundingBox().Intersects(bbox))
  599. if (FilterDelegate == null || FilterDelegate(fdr))
  600. dt.AddRow(fdr);
  601. }
  602. ds.Tables.Add(dt);
  603. }
  604. /// <summary>
  605. /// Returns geometry Object IDs whose bounding box intersects 'bbox'
  606. /// </summary>
  607. /// <param name="bbox"></param>
  608. /// <returns></returns>
  609. public Collection<uint> GetObjectIDsInView(SharpMap.Geometries.BoundingBox bbox)
  610. {
  611. if (!this.IsOpen)
  612. throw (new ApplicationException("An attempt was made to read from a closed datasource"));
  613. //Use the spatial index to get a list of features whose boundingbox intersects bbox
  614. return tree.Search(bbox);
  615. }
  616. /// <summary>
  617. /// Returns the geometry corresponding to the Object ID
  618. /// </summary>
  619. /// <param name="oid">Object ID</param>
  620. /// <returns>geometry</returns>
  621. public SharpMap.Geometries.Geometry GetGeometryByID(uint oid)
  622. {
  623. if (FilterDelegate != null) //Apply filtering
  624. {
  625. FeatureDataRow fdr = GetFeature(oid);
  626. if (fdr!=null)
  627. return fdr.Geometry;
  628. else
  629. return null;
  630. }
  631. else return ReadGeometry(oid);
  632. }
  633. /// <summary>
  634. /// Reads and parses the geometry with ID 'oid' from the ShapeFile
  635. /// </summary>
  636. /// <remarks><see cref="FilterDelegate">Filtering</see> is not applied to this method</remarks>
  637. /// <param name="oid">Object ID</param>
  638. /// <returns>geometry</returns>
  639. private SharpMap.Geometries.Geometry ReadGeometry(uint oid)
  640. {
  641. brShapeFile.BaseStream.Seek(GetShapeIndex(oid) + 8, 0); //Skip record number and content length
  642. ShapeType type = (ShapeType)brShapeFile.ReadInt32(); //Shape type
  643. if (type == ShapeType.Null)
  644. return null;
  645. if (_ShapeType == ShapeType.Point || _ShapeType==ShapeType.PointM || _ShapeType==ShapeType.PointZ)
  646. {
  647. SharpMap.Geometries.Point tempFeature = new SharpMap.Geometries.Point();
  648. return new SharpMap.Geometries.Point(brShapeFile.ReadDouble(), brShapeFile.ReadDouble());
  649. }
  650. else if (_ShapeType == ShapeType.Multipoint || _ShapeType == ShapeType.MultiPointM || _ShapeType == ShapeType.MultiPointZ)
  651. {
  652. brShapeFile.BaseStream.Seek(32 + brShapeFile.BaseStream.Position, 0); //skip min/max box
  653. SharpMap.Geometries.MultiPoint feature = new SharpMap.Geometries.MultiPoint();
  654. int nPoints = brShapeFile.ReadInt32(); // get the number of points
  655. if (nPoints == 0)
  656. return null;
  657. for (int i = 0; i < nPoints; i++)
  658. feature.Points.Add(new SharpMap.Geometries.Point(brShapeFile.ReadDouble(), brShapeFile.ReadDouble()));
  659. return feature;
  660. }
  661. else if ( _ShapeType == ShapeType.PolyLine || _ShapeType == ShapeType.Polygon ||
  662. _ShapeType == ShapeType.PolyLineM || _ShapeType == ShapeType.PolygonM ||
  663. _ShapeType == ShapeType.PolyLineZ || _ShapeType == ShapeType.PolygonZ)
  664. {
  665. brShapeFile.BaseStream.Seek(32 + brShapeFile.BaseStream.Position, 0); //skip min/max box
  666. int nParts = brShapeFile.ReadInt32(); // get number of parts (segments)
  667. if (nParts == 0)
  668. return null;
  669. int nPoints = brShapeFile.ReadInt32(); // get number of points
  670. int[] segments = new int[nParts + 1];
  671. //Read in the segment indexes
  672. for (int b = 0; b < nParts; b++)
  673. segments[b] = brShapeFile.ReadInt32();
  674. //add end point
  675. segments[nParts] = nPoints;
  676. if ((int)_ShapeType%10 == 3)
  677. {
  678. SharpMap.Geometries.MultiLineString mline = new SharpMap.Geometries.MultiLineString();
  679. for (int LineID = 0; LineID < nParts; LineID++)
  680. {
  681. SharpMap.Geometries.LineString line = new SharpMap.Geometries.LineString();
  682. for (int i = segments[LineID]; i < segments[LineID + 1]; i++)
  683. line.Vertices.Add(new SharpMap.Geometries.Point(brShapeFile.ReadDouble(), brShapeFile.ReadDouble()));
  684. mline.LineStrings.Add(line);
  685. }
  686. if (mline.LineStrings.Count == 1)
  687. return mline[0];
  688. return mline;
  689. }
  690. else //(_ShapeType == ShapeType.Polygon etc...)
  691. {
  692. //First read all the rings
  693. List<SharpMap.Geometries.LinearRing> rings = new List<SharpMap.Geometries.LinearRing>();
  694. for (int RingID = 0; RingID < nParts; RingID++)
  695. {
  696. SharpMap.Geometries.LinearRing ring = new SharpMap.Geometries.LinearRing();
  697. for (int i = segments[RingID]; i < segments[RingID + 1]; i++)
  698. ring.Vertices.Add(new SharpMap.Geometries.Point(brShapeFile.ReadDouble(), brShapeFile.ReadDouble()));
  699. rings.Add(ring);
  700. }
  701. bool[] IsCounterClockWise = new bool[rings.Count];
  702. int PolygonCount = 0;
  703. for (int i = 0; i < rings.Count;i++)
  704. {
  705. IsCounterClockWise[i] = rings[i].IsCCW();
  706. if (!IsCounterClockWise[i])
  707. PolygonCount++;
  708. }
  709. if (PolygonCount == 1) //We only have one polygon
  710. {
  711. SharpMap.Geometries.Polygon poly = new SharpMap.Geometries.Polygon();
  712. poly.ExteriorRing = rings[0];
  713. if (rings.Count > 1)
  714. for (int i = 1; i < rings.Count; i++)
  715. poly.InteriorRings.Add(rings[i]);
  716. return poly;
  717. }
  718. else
  719. {
  720. SharpMap.Geometries.MultiPolygon mpoly = new SharpMap.Geometries.MultiPolygon();
  721. SharpMap.Geometries.Polygon poly = new SharpMap.Geometries.Polygon();
  722. poly.ExteriorRing = rings[0];
  723. for (int i = 1; i < rings.Count;i++)
  724. {
  725. if (!IsCounterClockWise[i])
  726. {
  727. mpoly.Polygons.Add(poly);
  728. poly = new SharpMap.Geometries.Polygon(rings[i]);
  729. }
  730. else
  731. poly.InteriorRings.Add(rings[i]);
  732. }
  733. mpoly.Polygons.Add(poly);
  734. return mpoly;
  735. }
  736. }
  737. }
  738. else
  739. throw (new ApplicationException("Shapefile type " + _ShapeType.ToString() + " not supported"));
  740. }
  741. /// <summary>
  742. /// Returns the data associated with all the geometries that are intersected by 'geom'.
  743. /// Please note that the ShapeFile provider currently doesn't fully support geometryintersection
  744. /// and thus only BoundingBox/BoundingBox querying are performed. The results are NOT
  745. /// guaranteed to lie withing 'geom'.
  746. /// </summary>
  747. /// <param name="geom"></param>
  748. /// <param name="ds">FeatureDataSet to fill data into</param>
  749. public void ExecuteIntersectionQuery(SharpMap.Geometries.Geometry geom, FeatureDataSet ds)
  750. {
  751. SharpMap.Data.FeatureDataTable dt = (SharpMap.Data.FeatureDataTable)dbaseFile.NewTable;
  752. SharpMap.Geometries.BoundingBox bbox = geom.GetBoundingBox();
  753. //Get candidates by intersecting the spatial index tree
  754. Collection<uint> objectlist = tree.Search(bbox);
  755. if (objectlist.Count == 0)
  756. return;
  757. for (int j = 0; j < objectlist.Count; j++)
  758. {
  759. for (uint i = (uint)dt.Rows.Count - 1; i >= 0; i--)
  760. {
  761. FeatureDataRow fdr = GetFeature(objectlist[j],dt);
  762. if (fdr.Geometry != null)
  763. if (fdr.Geometry.GetBoundingBox().Intersects(bbox))
  764. //replace above line with this:  if(fdr.Geometry.Intersects(bbox))  when relation model is complete
  765. if (FilterDelegate == null || FilterDelegate(fdr))
  766. dt.AddRow(fdr);
  767. }
  768. }
  769. ds.Tables.Add(dt);
  770. }
  771. /// <summary>
  772. /// Returns the total number of features in the datasource (without any filter applied)
  773. /// </summary>
  774. /// <returns></returns>
  775. public int GetFeatureCount()
  776. {
  777. return _FeatureCount;
  778. }
  779. /// <summary>
  780. /// Filter Delegate Method
  781. /// </summary>
  782. /// <remarks>
  783. /// The FilterMethod delegate is used for applying a method that filters data from the dataset.
  784. /// The method should return 'true' if the feature should be included and false if not.
  785. /// <para>See the <see cref="FilterDelegate"/> property for more info</para>
  786. /// </remarks>
  787. /// <seealso cref="FilterDelegate"/>
  788. /// <param name="dr"><see cref="SharpMap.Data.FeatureDataRow"/> to test on</param>
  789. /// <returns>true if this feature should be included, false if it should be filtered</returns>
  790. public delegate bool FilterMethod(SharpMap.Data.FeatureDataRow dr);
  791. private FilterMethod _FilterDelegate;
  792. /// <summary>
  793. /// Filter Delegate Method for limiting the datasource
  794. /// </summary>
  795. /// <remarks>
  796. /// <example>
  797. /// Using an anonymous method for filtering all features where the NAME column starts with S:
  798. /// <code lang="C#">
  799. /// myShapeDataSource.FilterDelegate = new SharpMap.Data.Providers.ShapeFile.FilterMethod(delegate(SharpMap.Data.FeatureDataRow row) { return (!row["NAME"].ToString().StartsWith("S")); });
  800. /// </code>
  801. /// </example>
  802. /// <example>
  803. /// Declaring a delegate method for filtering (multi)polygon-features whose area is larger than 5.
  804. /// <code>
  805. /// myShapeDataSource.FilterDelegate = CountryFilter;
  806. /// [...]
  807. /// public static bool CountryFilter(SharpMap.Data.FeatureDataRow row)
  808. /// {
  809. /// if(row.Geometry.GetType()==typeof(SharpMap.Geometries.Polygon))
  810. /// return ((row.Geometry as SharpMap.Geometries.Polygon).Area>5);
  811. /// if (row.Geometry.GetType() == typeof(SharpMap.Geometries.MultiPolygon))
  812. /// return ((row.Geometry as SharpMap.Geometries.MultiPolygon).Area > 5);
  813. /// else return true;
  814. /// }
  815. /// </code>
  816. /// </example>
  817. /// </remarks>
  818. /// <seealso cref="FilterMethod"/>
  819. public FilterMethod FilterDelegate
  820. {
  821. get
  822. {
  823. return _FilterDelegate;
  824. }
  825. set
  826. {
  827. _FilterDelegate = value;
  828. }
  829. }
  830. /*
  831. /// <summary>
  832. /// Returns a colleciton of columns from the datasource [NOT IMPLEMENTED]
  833. /// </summary>
  834. public System.Data.DataColumnCollection Columns
  835. {
  836. get {
  837. if (dbaseFile != null)
  838. {
  839. System.Data.DataTable dt = dbaseFile.DataTable;
  840. return dt.Columns;
  841. }
  842. else
  843. throw (new ApplicationException("An attempt was made to read DBase data from a shapefile without a valid .DBF file"));
  844. }
  845. }*/
  846. /// <summary>
  847. /// Gets a datarow from the datasource at the specified index
  848. /// </summary>
  849. /// <param name="RowID"></param>
  850. /// <returns></returns>
  851. public SharpMap.Data.FeatureDataRow GetFeature(uint RowID)
  852. {
  853. return GetFeature(RowID, null);
  854. }
  855. /// <summary>
  856. /// Gets a datarow from the datasource at the specified index belonging to the specified datatable
  857. /// </summary>
  858. /// <param name="RowID"></param>
  859. /// <param name="dt">Datatable to feature should belong to.</param>
  860. /// <returns></returns>
  861. public SharpMap.Data.FeatureDataRow GetFeature(uint RowID, FeatureDataTable dt)
  862. {
  863. if (dbaseFile != null)
  864. {
  865. SharpMap.Data.FeatureDataRow dr = (SharpMap.Data.FeatureDataRow)dbaseFile.GetFeature(RowID, (dt==null)?dbaseFile.NewTable:dt);
  866. dr.Geometry = ReadGeometry(RowID);
  867. if (FilterDelegate == null || FilterDelegate(dr))
  868. return dr;
  869. else
  870. return null;
  871. }
  872. else
  873. throw (new ApplicationException("An attempt was made to read DBase data from a shapefile without a valid .DBF file"));
  874. }
  875. /// <summary>
  876. /// Returns the extents of the datasource
  877. /// </summary>
  878. /// <returns></returns>
  879. public SharpMap.Geometries.BoundingBox GetExtents()
  880. {
  881. if (tree == null)
  882. throw new ApplicationException("File hasn't been spatially indexed. Try opening the datasource before retriving extents");
  883. return tree.Box;
  884. }
  885. /// <summary>
  886. /// Gets the connection ID of the datasource
  887. /// </summary>
  888. /// <remarks>
  889. /// The connection ID of a shapefile is its filename
  890. /// </remarks>
  891. public string ConnectionID
  892. {
  893. get { return this._Filename; }
  894. }
  895. private int _SRID = -1;
  896. /// <summary>
  897. /// Gets or sets the spatial reference ID (CRS)
  898. /// </summary>
  899. public int SRID
  900. {
  901. get { return _SRID; }
  902. set { _SRID = value; }
  903. }
  904. #endregion
  905. }
  906. }