PageRenderTime 28ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/GeoRouting/branches/SharpMap/Data/Providers/ShapeFile.cs

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