.
675 |
--------------------------------------------------------------------------------
/Part.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace ArcShapeFile
6 | {
7 | ///
8 | /// A Part is a connected sequence of two or more points in a line or polygon. Parts may or may not be connected to one another. Parts may or may not intersect one another. A part can be used to describe holes in polygons, or polygons and lines that share the same database attributes.
9 | ///
10 | public class Part
11 | {
12 |
13 | #region ********** Local Variables **********
14 |
15 | private double mvarMBRXMin = 0;
16 | private double mvarMBRXMax = 0;
17 | private double mvarMBRYMin = 0;
18 | private double mvarMBRYMax = 0;
19 | private double? mvarZMin = null;
20 | private double? mvarZMax = null;
21 | private double? mvarMeasureMin = null;
22 | private double? mvarMeasureMax = null;
23 | private int mvarStart = 0;
24 | private int mvarFinish = 0;
25 | private bool mvarDonut = false;
26 | private ePartType mvarPartType = ePartType.none;
27 | private double mvarPartArea = 0;
28 | private eDirection? mvarDirection = null;
29 | private double? mvarPartCentroidX = null;
30 | private double? mvarPartCentroidY = null;
31 | private double? mvarPartPerimeter = null;
32 |
33 | #endregion
34 |
35 | #region ********** Public Part Properties **********
36 |
37 | ///
38 | /// The End property of each Part denotes the ordinal position of the last vertice that describes the part shape within the Vertices collection.
39 | ///
40 | ///
41 | /// Use this property, together with the property to navigate your way through each part of a multipart shape record.
42 | ///
43 | ///
44 | ///
45 | /// foreach(Part p in shp.Parts)
46 | /// {
47 | /// Console.WriteLine("Part begins at vertice no. {0} and ends at vertice no. {1}", p.Begins, p.Ends);
48 | /// for(int v = p.Begins; v <= p.Ends; v++)
49 | /// {
50 | /// Console.WriteLine("Vertice Position: {0} X Value: {1} Y Value {2}", v, shp.Vertices[v].X_Cord, shp.Vertices[v].Y_Cord);
51 | /// }
52 | /// }
53 | ///
54 | ///
55 | ///
56 | public int Ends
57 | {
58 | get { return mvarFinish; }
59 | set { mvarFinish = value; }
60 | }
61 |
62 |
63 | ///
64 | /// The Begins property of each Part denotes the ordinal position of the first vertice that describes the part shape within the Vertices collection.
65 | ///
66 | ///
67 | /// Use this property, together with the property to navigate your way through each part of a multipart shape record.
68 | ///
69 | ///
70 | ///
71 | /// foreach(Part p in shp.Parts)
72 | /// {
73 | /// Console.WriteLine("Part begins at vertice no. {0} and ends at vertice no. {1}", p.Begins, p.Ends);
74 | /// for(int v = p.Begins; v <= p.Ends; v++)
75 | /// {
76 | /// Console.WriteLine("Vertice Position: {0} X Value: {1} Y Value {2}", v, shp.Vertices[v].X_Cord, shp.Vertices[v].Y_Cord);
77 | /// }
78 | /// }
79 | ///
80 | ///
81 | ///
82 | public int Begins
83 | {
84 | get { return mvarStart; }
85 | set { mvarStart = value; }
86 | }
87 |
88 |
89 | ///
90 | /// Returns the area of the part for polygon shapes.
91 | ///
92 | ///
93 | /// Referencing this property through the Parts collection you will notice that some values are negative and some are positive. If the value is positive then the polygon vertices are ordered clockwise about the normal, otherwise it's counter clockwise with a negative area indicating a hole
94 | ///
95 | public double Area
96 | {
97 | get { return mvarPartArea; }
98 | set
99 | {mvarPartArea = value; }
100 | }
101 |
102 |
103 | ///
104 | /// This property denotes the direction in which each vertice part has been captured.
105 | ///
106 | ///
107 | /// Polygon rings are normally captured in a clockwise direction ... unless they are holes, in which case they should be captured as anticlockwise. If you are using this library to create your own polygons and are unsure as to the direction of each part you can (and should) force the issue using the SetPartDirection method.
108 | ///
109 | public eDirection? Direction
110 | {
111 | get { return mvarDirection; }
112 | set { mvarDirection = value; }
113 | }
114 |
115 |
116 | ///
117 | /// Returns an object that represents the X Centroid of the polygon shape described by this Part
118 | ///
119 | public double? CentroidX
120 | {
121 | get { return mvarPartCentroidX; }
122 | set { mvarPartCentroidX = value; }
123 | }
124 |
125 |
126 | ///
127 | /// Returns an object that represents the Y Centroid of the polygon shape described by this Part
128 | ///
129 | public double? CentroidY
130 | {
131 | get { return mvarPartCentroidY; }
132 | set { mvarPartCentroidY = value; }
133 | }
134 |
135 |
136 | ///
137 | /// Provides the length of the perimeter around the part polygon shape or the length a line shape.
138 | ///
139 | ///
140 | /// Forget about using this property on MultiPatch shapes - the calculated value will be way out. This property is really designed to cater for polygons and lines.
141 | /// If you want the perimeter length of the entire shape use the ShapeFile Perimeter property
142 | ///
143 | public double? Perimeter
144 | {
145 | get { return mvarPartPerimeter; }
146 | set { mvarPartPerimeter = value; }
147 | }
148 |
149 |
150 | ///
151 | /// Returns Double that represents the bounding rectangle minimum X of the polygon shape described by this Part
152 | ///
153 | public double MBRXMin
154 | {
155 | get { return mvarMBRXMin; }
156 | set { mvarMBRXMin = value; }
157 | }
158 |
159 |
160 |
161 | ///
162 | /// Returns Double that represents the bounding rectangle maximum X of the polygon shape described by this Part
163 | ///
164 | public double MBRXMax
165 | {
166 | get { return mvarMBRXMax; }
167 | set { mvarMBRXMax = value; }
168 | }
169 |
170 |
171 |
172 | ///
173 | /// Returns Double that represents the bounding rectangle minimum Y of the polygon shape described by this Part
174 | ///
175 | public double MBRYMin
176 | {
177 | get { return mvarMBRYMin; }
178 | set { mvarMBRYMin = value; }
179 | }
180 |
181 |
182 |
183 | ///
184 | /// Returns a Double that represents the bounding rectangle maximum Y of the polygon shape described by this Part
185 | ///
186 | public double MBRYMax
187 | {
188 | get { return mvarMBRYMax; }
189 | set { mvarMBRYMax = value; }
190 | }
191 |
192 |
193 | ///
194 | /// Returns a Double that represents the bounding rectangle minimum Z of the polygonZ shape described by this Part
195 | ///
196 | public double? zMin
197 | {
198 | get { return mvarZMin; }
199 | set { mvarZMin = value; }
200 | }
201 |
202 |
203 |
204 | ///
205 | /// Returns a Double that represents the bounding rectangle minimum Z of the polygonZ shape described by this Part
206 | ///
207 | public double? zMax
208 | {
209 | get { return mvarZMax; }
210 | set { mvarZMax = value; }
211 | }
212 |
213 |
214 | ///
215 | /// Returns a Double that represents the bounding rectangle minimum measure of the polygonZ and polygonM shape described by this Part
216 | ///
217 | public double? MeasureMin
218 | {
219 | get { return mvarMeasureMin; }
220 | set { mvarMeasureMin = value; }
221 | }
222 |
223 |
224 |
225 | ///
226 | /// Returns aDouble that represents the bounding rectangle maximum measure of the polygonZ and polygonM shapes described by this Part
227 | ///
228 | public double? MeasureMax
229 | {
230 | get { return mvarMeasureMax; }
231 | set { mvarMeasureMax = value; }
232 | }
233 |
234 |
235 |
236 | ///
237 | /// A polygon consists of one or more rings, or parts. A polygon may contain multiple outer rings. The order of vertices or orientation for a ring indicates which side of the ring is the interior of the polygon. The neighborhood to the right of an observer walking along the ring in vertex order is the neighborhood inside the polygon. Vertices of rings defining holes in polygons are always in a counterclockwise direction. Vertices for a single, ringed polygon are, therefore, always in a clockwise order.
238 | ///
239 | ///
240 | /// The IsHole property attempts to indicate that the polygon part is a hole by comparing the minimum bounding rectangles of all parts of the current ShapeFile record. A True value is returned if the part is contained with the bounds of any other part of the current ShapeFile record. As a further check a line is drawn from the interior polygon part through the largest polygon part and the number of intersections counted (an odd number indicates that the part is within the larger part). Why this method? I have found that ShapeFiles created by some translators use parts to group distinct polygons that share the same attribute data, that is they do not conform to the idea that polygon consists of a series of outer rings.
241 | ///

242 | /// The figure on the left shows how parts can be used to denote distinct polygons with the same attributes, while the figure on the right shows how parts can be used to create holes in the polygon. One thing that you should note programatically is that polygon holes must be Anti Clockwise. You can force this before creating your shape with the SetPartDirection method for any parts in your shape.
243 | ///
244 | /// SetPartDirection Method
245 | public bool IsHole
246 | {
247 | get { return mvarDonut; }
248 | set { mvarDonut = value; }
249 | }
250 |
251 |
252 | ///
253 | /// A MultiPatch shape consists of a number of surfaces, each described by a vertice part. What type of MultiPatch shape each part is is described by the PatchType. The parts of a MultiPatch can be of the following types:
254 | ///
255 | ///
256 | /// The following is straight out of the ESRI technical description. A sequence of parts (or rings) can describe a polygonal surface patch with holes. The sequence typically consists of all Outer Ring, representing the outer boundary of the patch followed by a number of Inner Rings representing holes. When the individual types of rings in a collection of rings representing a polygonal patch with holes are unknown, the sequence must start with First Ring, followed by a number of Rings. A sequence of Rings not preceded by a First Ring is treated as a sequence of Outer Rings without holes.
257 | /// The following figure shows examples of all types of MultiPatch parts.
258 | ///
259 | ///
260 | public ePartType PartType
261 | {
262 | get { return mvarPartType; }
263 | set { mvarPartType = value; }
264 | }
265 |
266 |
267 | #endregion
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/Parts.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace ArcShapeFile
6 | {
7 | ///
8 | /// The Parts Collection consists of one or more Part objects, each representing the beginning and end point of each ring or part in the current record.
9 | ///
10 | /// Some ShapeFile records consist of polygons with donuts inside them or multiple shapes that share the same database attribution. With the Parts collection
11 | /// I've tried to give you a bit more information about these features, including the start and end ordinal of the vertices that make up each ring, the centroid of the ring and a few other bits and pieces.
12 | /// ou can refer to each Part object within the collection by:
13 | ///
14 | /// - Iteration by using the 0 based ordinal - i.e. for(int i=0;i < shp.Parts.Count; i++)
15 | /// - Iteration by reference - i.e. foreach(Part pt in shp.Parts)
16 | ///
17 | public class Parts : System.Collections.CollectionBase
18 | {
19 |
20 | #region ********** Internal Methods **********
21 |
22 | internal void Add(int Begins)
23 | {
24 | //create a new object
25 | Part objNewMember = default(Part);
26 | objNewMember = new Part();
27 |
28 | //set the properties passed into the method
29 | objNewMember.Begins = Begins;
30 |
31 | List.Add(objNewMember);
32 |
33 | }
34 |
35 | #endregion
36 |
37 | #region ********** Public Methods **********
38 |
39 | ///
40 | /// Grabs the Part information based on the Index
41 | ///
42 | /// The index of the Part within the collection
43 | public Part this[int Index]
44 | {
45 | get
46 | {
47 | return (Part)List[Index];
48 | }
49 | }
50 |
51 | #endregion
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Projection.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace ArcShapeFile
6 | {
7 | ///
8 | /// A class containing the values read from a PRJ file
9 | ///
10 | ///
11 | /// This class is essentially read only and grabs the available details from the projection (.PRJ) file with the same name as the ShapeFile. To write
12 | /// a projection file use the method.
13 | public class Projection
14 | {
15 |
16 | #region ********** Local Variables **********
17 |
18 | string mvarCoordSystem;
19 | string mvarProjSystem;
20 | string mvarDatum;
21 | string mvarSpheroidName;
22 | double mvarEqRadius;
23 | double mvarFlatInv;
24 | double mvarPolarRadius;
25 | double mvarEccentricity;
26 | string mvarPrimeMerName;
27 | double mvarPrimeMeridian;
28 | double mvarCentralMeridian;
29 | string mvarGeoSpaceUnitName;
30 | double mvarGeoSpaceUnitSize;
31 | string mvarType;
32 |
33 | string mvarProjectionName;
34 | double mvarFalseEast;
35 | double mvarFalseNorth;
36 | double mvarLatOrigin;
37 | double mvarLongOrigin;
38 | double mvarScaleFactor;
39 | string mvarProjectionUnitName;
40 | double mvarProjectionUnitSize;
41 |
42 | #endregion
43 |
44 | #region ********** Public Projection Properties **********
45 | ///
46 | /// What type of projection is this?
47 | ///
48 | public string Type
49 | {
50 | get { return mvarType; }
51 | internal set { mvarType = value; }
52 | }
53 | ///
54 | /// The Geographic Cooordinate System Name
55 | ///
56 | public string GeoCoordSystem
57 | {
58 | get { return mvarCoordSystem; }
59 | internal set { mvarCoordSystem = value; }
60 | }
61 | ///
62 | /// The Projection Name
63 | ///
64 | public string ProjCoordSystem
65 | {
66 | get { return mvarProjSystem; }
67 | internal set { mvarProjSystem = value; }
68 | }
69 | ///
70 | /// The Datum used in the projection
71 | ///
72 | public string Datum
73 | {
74 | get { return mvarDatum; }
75 | internal set { mvarDatum = value; }
76 | }
77 | ///
78 | /// The name of the spheroid used in the projection
79 | ///
80 | public string SpheroidName
81 | {
82 | get { return mvarSpheroidName; }
83 | internal set { mvarSpheroidName = value; }
84 | }
85 | ///
86 | /// The Equatorial Radius
87 | ///
88 | public double EquitorialRadius
89 | {
90 | get { return mvarEqRadius; }
91 | internal set { mvarEqRadius = value; }
92 | }
93 | ///
94 | /// The Inverse of the flattening
95 | ///
96 | public double FlatteningInverse
97 | {
98 | get { return mvarFlatInv; }
99 | internal set { mvarFlatInv = value; }
100 | }
101 | ///
102 | /// The Polar Radius
103 | ///
104 | public double PolarRadius
105 | {
106 | get { return mvarPolarRadius; }
107 | internal set { mvarPolarRadius = value; }
108 | }
109 | ///
110 | /// The Eccentricity
111 | ///
112 | public double Eccentricity
113 | {
114 | get { return mvarEccentricity; }
115 | internal set { mvarEccentricity = value; }
116 | }
117 | ///
118 | /// The name of the Prime Meridian
119 | ///
120 | public string PrimeMeridianName
121 | {
122 | get { return mvarPrimeMerName; }
123 | internal set { mvarPrimeMerName = value; }
124 | }
125 | ///
126 | /// The Prime Meridian value
127 | ///
128 | public double PrimeMeridian
129 | {
130 | get { return mvarPrimeMeridian; }
131 | internal set { mvarPrimeMeridian = value; }
132 | }
133 | ///
134 | /// The Central Meridian value
135 | ///
136 | public double CentralMeridian
137 | {
138 | get { return mvarCentralMeridian; }
139 | internal set { mvarCentralMeridian = value; }
140 | }
141 | ///
142 | /// The name of the coordinate system units
143 | ///
144 | public string GeoSpaceUnitName
145 | {
146 | get { return mvarGeoSpaceUnitName; }
147 | internal set { mvarGeoSpaceUnitName = value; }
148 | }
149 | ///
150 | /// The size of the coordinate system units
151 | ///
152 | public double GeoSpaceUnitSize
153 | {
154 | get { return mvarGeoSpaceUnitSize; }
155 | internal set { mvarGeoSpaceUnitSize = value; }
156 | }
157 | ///
158 | /// The name of the Projection
159 | ///
160 | public string ProjectionName
161 | {
162 | get { return mvarProjectionName; }
163 | internal set { mvarProjectionName = value; }
164 | }
165 | ///
166 | /// The projection false East value
167 | ///
168 | public double FalseEast
169 | {
170 | get { return mvarFalseEast; }
171 | internal set { mvarFalseEast = value; }
172 | }
173 | ///
174 | /// The projection false North value
175 | ///
176 | public double FalseNorth
177 | {
178 | get { return mvarFalseNorth; }
179 | internal set { mvarFalseNorth = value; }
180 | }
181 | ///
182 | /// Latitude of Origin value
183 | ///
184 | public double LatitudeOrigin
185 | {
186 | get { return mvarLatOrigin; }
187 | internal set { mvarLatOrigin = value; }
188 | }
189 | ///
190 | /// Longitude of Origin value
191 | ///
192 | public double LongitudeOrigin
193 | {
194 | get { return mvarLongOrigin; }
195 | internal set { mvarLongOrigin = value; }
196 | }
197 | ///
198 | /// Scale factor
199 | ///
200 | public double ScaleFactor
201 | {
202 | get { return mvarScaleFactor; }
203 | internal set { mvarScaleFactor = value; }
204 | }
205 | ///
206 | /// The name of the units used by the projection
207 | ///
208 | public string ProjectionUnitName
209 | {
210 | get { return mvarProjectionUnitName; }
211 | internal set { mvarProjectionUnitName = value; }
212 | }
213 | ///
214 | /// The size of the units used by the projection
215 | ///
216 | public double ProjectionUnitSize
217 | {
218 | get { return mvarProjectionUnitSize; }
219 | internal set { mvarProjectionUnitSize = value; }
220 | }
221 |
222 |
223 | #endregion
224 |
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ArcShapeFile
2 | A C# library to read and write ESRI Shapefiles
3 |
4 | The ESRI Shapefile format is a commonly shared geographic data format. This is my attempt to read and write to it. The set of C# classes that make up the DLL allow you to read and create both the vector/point data and the associated attribute record. The source code comes with a reasonably comprehensive help file that includes code C# and VB.Net examples.
5 |
6 | Background
7 | The Shapefile format first made it's appearence back in the 1980's. Despite it's limitations it remains a standard format for moving data between GIS systems. Essentially the format as described in the ESRI Shapefile Technical Description consists on three files
8 | - .shp - holds the vertice data (x, y and in some cases z and measure values)
9 | - .shx - contains the offset and length of each record in the .shp
10 | - .dbf - contains the attribute data for each record. This file is based on the old Ashton Tate dBASE III format, though without memo field support so text fields are limited to 254 characters and the field names are limited to 10 characters
11 | A further limitation of the ShapeFile format is that each Shapefile can only contain one type of shape (i.e. all points or all lines or all polygons). There is one exception and that is you can have a null record to act as a placeholder within the Shapefile structure.
12 |
13 | Additionally I've added support for the .prj file. This is a WKT file holding the projection information - handy when you want to use this data in different systems as most seem to recognise it. I've also added support of the .cfg codepage file, though this still in the development phase and may need some additional work.
I originally developed this code in VS 2005 so I've keep the style (relatively) simple - no vars, just explicit dimensioning and no LINQ (though that meant I had to write my own query parser) - so you should be able to easily import the code into any VS version and run it as is.
14 |
--------------------------------------------------------------------------------
/ShapeOCX.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rosspickard/ArcShapeFile/b885396cd478a917883fd0e67734c618ea8e60d6/ShapeOCX.ico
--------------------------------------------------------------------------------
/Vertice.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Runtime.InteropServices;
5 |
6 | namespace ArcShapeFile
7 | {
8 | ///
9 | /// Contains the X, Y, Z coordinates and measure information of a vertice
10 | ///
11 | [ClassInterface(ClassInterfaceType.AutoDual)]
12 | [Guid("1BBEA337-0B68-418c-B380-BB54B38866A1")]
13 | public class Vertice : ArcShapeFile.IVertice
14 | {
15 |
16 | #region ********** Local Variables **********
17 |
18 | private double mvarX_Cord;
19 | private double mvarY_Cord;
20 | private double? mvarZ_Cord;
21 | private double? mvarMeasure;
22 | //private int mvarPartNo;
23 | //private ePartType mvarPartType;
24 |
25 | #endregion
26 |
27 | #region ********** Public Vertice Properties **********
28 |
29 |
30 | ///
31 | /// The X component of the cartesian coordinate
32 | ///
33 | ///
34 | /// This is one part of the standard 2 dimesional cartesian plain coordinate pair. Adding new Vertices should be done through the method.
35 | /// The ModifyShape method will write any changes to existing vertices back to the ShapeFile.
36 | ///
37 | public double X_Cord
38 | {
39 | get { return mvarX_Cord; }
40 | set
41 | {
42 | if (mvarX_Cord != value)
43 | Globals.mvarVerticeChange = true;
44 | mvarX_Cord = value;
45 | }
46 | }
47 |
48 | ///
49 | /// The Y component of the cartesian coordinate
50 | ///
51 | ///
52 | /// This is one part of the standard 2 dimesional cartesian plain coordinate pair. Adding new Vertices should be done through the method.
53 | /// The ModifyShape method will write any changes to existing vertices back to the ShapeFile.
54 | ///
55 | public double Y_Cord
56 | {
57 | get { return mvarY_Cord; }
58 | set
59 | {
60 | if (mvarY_Cord != value)
61 | Globals.mvarVerticeChange = true;
62 | mvarY_Cord = value;
63 | }
64 | }
65 |
66 | ///
67 | /// The Z or height component of the current 3 dimensional Vertice record
68 | ///
69 | ///
70 | /// Z coordinates are only applicable to shapes of type X,Y,Z Measure. The Z_Cord value may be null. Null values are written to ShapeFiles as any value less than 10-38
71 | ///
72 | public double? Z_Cord
73 | {
74 | get { return mvarZ_Cord; }
75 | set
76 | {
77 | if (mvarZ_Cord != value)
78 | Globals.mvarVerticeChange = true;
79 | mvarZ_Cord = value;
80 | }
81 | }
82 |
83 | ///
84 | /// The measure component of the current Vertice record
85 | ///
86 | ///
87 | /// Measures are only applicable to shapes of type X,Y Measure or X,Y,Z Measure. The Measure value may be null. Null values are written to ShapeFiles as any value less than 10-38
88 | ///
89 | public double? Measure
90 | {
91 | get { return mvarMeasure; }
92 | set
93 | {
94 | if (!mvarMeasure.Equals(value))
95 | {
96 | Globals.mvarVerticeChange = true;
97 | }
98 | mvarMeasure = value;
99 | }
100 | }
101 |
102 | #endregion
103 |
104 |
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WKTProjectionInfo.xls:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rosspickard/ArcShapeFile/b885396cd478a917883fd0e67734c618ea8e60d6/WKTProjectionInfo.xls
--------------------------------------------------------------------------------
/WKTReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Text;
5 | using System.IO;
6 |
7 | namespace ArcShapeFile
8 | {
9 | /* ****************************************************************************
10 | * This is my own interpretation of a Well Known Text reader for spatial
11 | * projection info as implemented in the ESRI prj file. I've tried to
12 | * emulate the XML reader structure in that each base class (or Node) will
13 | * have a name, a parent, attributes and possibly children (or Nodes).
14 | * The tricky part is iterating through the nodes to find the one you want
15 | * I've provided a couple of methods to make it easier for you:
16 | * GetNodesByName - returns all nodes regardless of depth that match your
17 | * criteria.
18 | * GetAttribValuebyName - Returns the value of the xth attribute that
19 | * matches your criteria
20 | * GetAttribName - Returns the name of the attibute gievn a tag name and/or
21 | * parent tag name
22 | * Anyway ... it seems to work
23 | * *****************************************************************************
24 | */
25 | ///
26 | /// This is my own interpretation of a Well Known Text reader for spatial
27 | /// projection info as implemented in the ESRI prj file. I've tried to
28 | /// emulate the XML reader structure in that each base class (or Node) will
29 | /// have a name, a parent, attributes and possibly children (or Nodes).
30 | /// The tricky part is iterating through the nodes to find the one you want
31 | /// I've provided a couple of methods to make it easier for you:
32 | ///
33 | /// -
34 | /// GetNodesByName - Returns all nodes regardless of depth that match your criteria
35 | ///
36 | ///
37 | /// -
38 | /// GetAttribValuebyName - Returns the value of the xth attribute that matches your criteria
39 | ///
40 | ///
41 | /// -
42 | /// GetAttribName - Returns the name of the attibute given a tag name and/or parent tag name
43 | ///
44 | ///
45 | /// Anyway ... it seems to work
46 | ///
47 | public class WKTReader
48 | {
49 | private ArrayList LevelList;
50 |
51 | private WKTNodes mvarNodes;
52 |
53 | ///
54 | /// The collection of individual nodes from the WKT input
55 | ///
56 | public WKTNodes Nodes
57 | {
58 | get { return mvarNodes; }
59 | }
60 |
61 | ///
62 | /// Opens the WKT file and loads all the data into the reader's nodes
63 | ///
64 | /// The name of the WKT file to load
65 | public void Read(string filename)
66 | {
67 | if (!System.IO.File.Exists(filename))
68 | { throw new Exception("WKT File does not exist"); }
69 | LevelList = new ArrayList();
70 | mvarNodes = new WKTNodes();
71 |
72 | try
73 | {
74 |
75 | string inData = System.IO.File.ReadAllText(filename);
76 | // Remove unwanted characters
77 | inData = inData.Replace("\t", "");
78 | inData = inData.Replace("\n", "");
79 | inData = inData.Replace("\r", "");
80 | inData = inData.Replace(", ", ",");
81 |
82 |
83 | int majLevel = 0;
84 |
85 | while (inData != "")
86 | {
87 | int brkPos = inData.IndexOf("[");
88 | if (brkPos == -1)
89 | return;
90 |
91 | string valName = (inData.Substring(0, brkPos)).Trim();
92 | if (valName.Contains(","))
93 | valName = valName.Substring(valName.IndexOf(",") + 1);
94 | string innerText = LoadParameterFromWKT(valName, inData);
95 | string param = null;
96 | inData = (inData.Substring(brkPos + 2 + innerText.Length)).Trim();
97 |
98 | if (innerText.Contains("["))
99 | {
100 | brkPos = innerText.IndexOf("[");
101 | brkPos = innerText.LastIndexOf(",", brkPos);
102 | param = (innerText.Substring(0, brkPos)).Trim();
103 | }
104 | else
105 | { param = innerText; }
106 |
107 | majLevel++;
108 | mvarNodes.Add("Top", valName, innerText, param, "");
109 |
110 | }
111 |
112 | FindAllLevels();
113 | }
114 | catch { }
115 | }
116 |
117 | private void FindAllLevels()
118 | {
119 | bool HasChildren = true;
120 | LevelList.Clear();
121 | for (int i = 0; i < mvarNodes.Count; i++)
122 | {
123 | LevelList.Add(i.ToString() + ".");
124 | }
125 |
126 | int startSearch = 0;
127 | int noAdded = 0;
128 |
129 | while (HasChildren)
130 | {
131 | HasChildren = false;
132 | for (int i = startSearch; i < LevelList.Count; i++)
133 | {
134 | startSearch += noAdded;
135 | noAdded = 0;
136 | string thisLevel = LevelList[i].ToString();
137 | thisLevel = thisLevel.Substring(0, thisLevel.Length - 1);
138 | string[] testLevels = thisLevel.Split('.');
139 | WKTNode tNode = mvarNodes[Convert.ToInt32(testLevels[0])];
140 | for (int j = 1; j < testLevels.Length; j++)
141 | { tNode = tNode.Children[Convert.ToInt32(testLevels[j])]; }
142 | if (tNode.hasChildren)
143 | {
144 | // Does this already exist in the list?
145 | for (int nNode = 0; nNode < tNode.Children.Count; nNode++)
146 | {
147 | string newLevel = LevelList[i].ToString() + nNode.ToString() + ".";
148 | if (!LevelList.Contains(newLevel))
149 | {
150 | HasChildren = true;
151 | LevelList.Add(newLevel);
152 | noAdded++;
153 | }
154 | }
155 | }
156 |
157 | }
158 |
159 | }
160 |
161 | for (int i = 0; i < LevelList.Count; i++)
162 | {
163 | System.Diagnostics.Debug.WriteLine(LevelList[i].ToString());
164 | }
165 | }
166 |
167 | private string LoadParameterFromWKT(string Header, string WKTString)
168 | {
169 | int paramStart = WKTString.IndexOf(Header);
170 | if (paramStart < 0)
171 | { return null; }
172 | paramStart += Header.Length + 1;
173 | string tString = WKTString.Substring(paramStart);
174 | int paramEnd = tString.IndexOf("]");
175 | int NoOfClosedBrks = 1;
176 | int NoOfOpenBrks = 0;
177 |
178 | while (NoOfClosedBrks != NoOfOpenBrks)
179 | {
180 | NoOfOpenBrks = 1;
181 | int brkPos = 0;
182 | do
183 | {
184 | // how many "[" are there between the start and the first closed brackets
185 |
186 | brkPos = tString.IndexOf("[", brkPos + 1);
187 | if (brkPos > -1 & brkPos < paramEnd)
188 | NoOfOpenBrks++;
189 | }
190 | while (brkPos > -1 & brkPos < paramEnd);
191 | if (NoOfOpenBrks != NoOfClosedBrks)
192 | {
193 | paramEnd = tString.IndexOf("]", paramEnd + 1);
194 | NoOfClosedBrks++;
195 | NoOfOpenBrks = 0;
196 | };
197 | }
198 |
199 | return tString.Substring(0, paramEnd);
200 | }
201 |
202 | ///
203 | /// Returns all the nodes in the WKT string that match
204 | ///
205 | /// The name of the Parent of the Tag (e.g. DATUM is the parent of SHEROID)
206 | /// "The name of the Tag (e.g. DATUM)
207 | /// The name of the Attribute to search for (e.g. Latitude_Of_Origin)
208 | public WKTNodes GetNodesbyName(string ParentName, string TagName, string AttribName)
209 | {
210 | WKTNodes getNodes = new WKTNodes();
211 | // Iterate through all the nodes and their children to find the match
212 | foreach (string testLevel in LevelList)
213 | {
214 | string[] splitLevels = testLevel.Split('.');
215 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
216 | for (int i = 1; i < splitLevels.Length - 1; i++)
217 | {
218 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
219 | }
220 | if (retNode.ParentName.ToUpper() == ParentName.ToUpper() & retNode.TagName.ToUpper() == TagName.ToUpper() & retNode.AttributeName.ToUpper() == AttribName.ToUpper())
221 | { getNodes.Add(retNode); }
222 | }
223 | return getNodes;
224 | }
225 |
226 | ///
227 | /// Returns all the nodes in the WKT string that match
228 | ///
229 | /// "The name of the Tag (e.g. DATUM)
230 | /// The name of the Attribute to search for (e.g. Latitude_Of_Origin)
231 | public WKTNodes GetNodesbyName(string TagName, string AttribName)
232 | {
233 | WKTNodes getNodes = new WKTNodes();
234 | // Iterate through all the nodes and their children to find the match
235 | foreach (string testLevel in LevelList)
236 | {
237 | string[] splitLevels = testLevel.Split('.');
238 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
239 | for (int i = 1; i < splitLevels.Length - 1; i++)
240 | {
241 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
242 | }
243 | if (retNode.TagName.ToUpper() == TagName.ToUpper() & retNode.AttributeName.ToUpper() == AttribName.ToUpper())
244 | { getNodes.Add(retNode); }
245 | }
246 | return getNodes;
247 | }
248 |
249 | ///
250 | /// Returns all the nodes in the WKT string that match
251 | ///
252 | /// "The name of the Tag (e.g. DATUM)
253 | public WKTNodes GetNodesbyName(string TagName)
254 | {
255 | WKTNodes getNodes = new WKTNodes();
256 | // Iterate through all the nodes and their children to find the match
257 | foreach (string testLevel in LevelList)
258 | {
259 | string[] splitLevels = testLevel.Split('.');
260 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
261 | for (int i = 1; i < splitLevels.Length - 1; i++)
262 | {
263 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
264 | }
265 | if (retNode.TagName.ToUpper() == TagName.ToUpper())
266 | { getNodes.Add(retNode); }
267 | }
268 | return getNodes;
269 | }
270 |
271 | ///
272 | /// Gets an attribute value for a particular Tag
273 | ///
274 | /// The name of the Parent of the Tag (e.g. DATUM is the parent of SHEROID)
275 | /// "The name of the Tag (e.g. DATUM)
276 | /// The name of the Attribute to search for (e.g. Latitude_Of_Origin)
277 | /// The item number of the Attribute array (defaults to 0)
278 | public string GetAttribValuebyName(string ParentName, string TagName, string AttribName, int AttribNumber)
279 | {
280 | string RetString = null;
281 | // Iterate through all the nodes and their children to find the match
282 | foreach (string testLevel in LevelList)
283 | {
284 | string[] splitLevels = testLevel.Split('.');
285 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
286 | for (int i = 1; i < splitLevels.Length - 1; i++)
287 | {
288 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
289 | }
290 | if (!String.IsNullOrEmpty(retNode.AttributeName))
291 | {
292 | if (retNode.ParentName.ToUpper() == ParentName.ToUpper() & retNode.TagName.ToUpper() == TagName.ToUpper() & retNode.AttributeName.ToUpper() == AttribName.ToUpper())
293 | {
294 | RetString = (string)retNode.Attributes[AttribNumber];
295 | break;
296 | }
297 | }
298 | }
299 | return RetString;
300 | }
301 |
302 | ///
303 | /// Gets an attribute value for a particular Tag
304 | ///
305 | /// "The name of the Tag (e.g. DATUM)
306 | /// The name of the Attribute to search for (e.g. Latitude_Of_Origin)
307 | /// The item number of the Attribute array (defaults to 0)
308 | public string GetAttribValuebyName(string TagName, string AttribName, int AttribNumber)
309 | {
310 | string RetString = null;
311 | // Iterate through all the nodes and their children to find the match
312 | foreach (string testLevel in LevelList)
313 | {
314 | string[] splitLevels = testLevel.Split('.');
315 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
316 | for (int i = 1; i < splitLevels.Length - 1; i++)
317 | {
318 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
319 | }
320 |
321 | if (!String.IsNullOrEmpty(retNode.AttributeName))
322 | {
323 | if (retNode.TagName.ToUpper() == TagName.ToUpper() & retNode.AttributeName.ToUpper() == AttribName.ToUpper())
324 | {
325 | RetString = (string)retNode.Attributes[AttribNumber];
326 | break;
327 | }
328 | }
329 | }
330 | return RetString;
331 | }
332 |
333 | ///
334 | /// Gets an attribute value for a particular Tag
335 | ///
336 | /// The name of the Parent of the Tag (e.g. DATUM is the parent of SHEROID)
337 | /// "The name of the Tag (e.g. DATUM)
338 | /// The name of the Attribute to search for (e.g. Latitude_Of_Origin)
339 | public string GetAttribValuebyName(string ParentName, string TagName, string AttribName)
340 | {
341 | string RetString = null;
342 | // Iterate through all the nodes and their children to find the match
343 | foreach (string testLevel in LevelList)
344 | {
345 | string[] splitLevels = testLevel.Split('.');
346 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
347 | for (int i = 1; i < splitLevels.Length - 1; i++)
348 | {
349 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
350 | }
351 | if (!String.IsNullOrEmpty(retNode.AttributeName))
352 | {
353 | if (retNode.ParentName.ToUpper() == ParentName.ToUpper() & retNode.TagName.ToUpper() == TagName.ToUpper() & retNode.AttributeName.ToUpper() == AttribName.ToUpper())
354 | {
355 | RetString = (string)retNode.Attributes[0];
356 | break;
357 | }
358 | }
359 | }
360 | return RetString;
361 | }
362 |
363 | ///
364 | /// Gets an attribute value for a particular Tag
365 | ///
366 | /// "The name of the Tag (e.g. DATUM)
367 | /// The name of the Attribute to search for (e.g. Latitude_Of_Origin)
368 | public string GetAttribValuebyName(string TagName, string AttribName)
369 | {
370 | string RetString = null;
371 | // Iterate through all the nodes and their children to find the match
372 | foreach (string testLevel in LevelList)
373 | {
374 | string[] splitLevels = testLevel.Split('.');
375 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
376 | for (int i = 1; i < splitLevels.Length - 1; i++)
377 | {
378 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
379 | }
380 | if (!String.IsNullOrEmpty(retNode.AttributeName))
381 | {
382 | if (retNode.TagName.ToUpper() == TagName.ToUpper() & retNode.AttributeName.ToUpper() == AttribName.ToUpper())
383 | {
384 | RetString = (string)retNode.Attributes[0];
385 | break;
386 | }
387 | }
388 | }
389 | return RetString;
390 | }
391 |
392 | ///
393 | /// Gets an attribute name for a particular Tag
394 | ///
395 | /// The name of the Parent of the Tag (e.g. DATUM is the parent of SHEROID)
396 | /// "The name of the Tag (e.g. DATUM)
397 | public string GetAttribName(string ParentName, string TagName)
398 | {
399 | string RetString = null;
400 | // Iterate through all the nodes and their children to find the match
401 | foreach (string testLevel in LevelList)
402 | {
403 | string[] splitLevels = testLevel.Split('.');
404 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
405 | for (int i = 1; i < splitLevels.Length - 1; i++)
406 | {
407 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
408 | }
409 |
410 | if (retNode.ParentName.ToUpper() == ParentName.ToUpper() & retNode.TagName.ToUpper() == TagName.ToUpper() )
411 | {
412 | RetString = (string)retNode.AttributeName;
413 | break;
414 | }
415 | }
416 | return RetString;
417 | }
418 |
419 | ///
420 | /// Gets an attribute name for a particular Tag
421 | ///
422 | /// "The name of the Tag (e.g. DATUM)
423 | public string GetAttribName(string TagName)
424 | {
425 | string RetString = null;
426 | // Iterate through all the nodes and their children to find the match
427 | foreach (string testLevel in LevelList)
428 | {
429 | string[] splitLevels = testLevel.Split('.');
430 | WKTNode retNode = mvarNodes[Convert.ToInt32(splitLevels[0])];
431 | for (int i = 1; i < splitLevels.Length - 1; i++)
432 | {
433 | retNode = retNode.Children[Convert.ToInt32(splitLevels[i])];
434 | }
435 | if (retNode.TagName.ToUpper() == TagName.ToUpper())
436 | {
437 | RetString = (string)retNode.AttributeName;
438 | break;
439 | }
440 | }
441 | return RetString;
442 | }
443 |
444 |
445 | }
446 |
447 | ///
448 | /// A collection of WKT nodes
449 | ///
450 | public class WKTNodes : System.Collections.CollectionBase
451 | {
452 | private int Counter = 0;
453 | private string sLevel = "";
454 |
455 | internal void Add(string Parent, string Name, string InnerText, string Parameters, string Level)
456 | {
457 | //create a new object
458 | string[] paramList;
459 | WKTNode objNewMember = default(WKTNode);
460 | objNewMember = new WKTNode();
461 | WKTNodes Kids = new WKTNodes();
462 |
463 | sLevel = Level;
464 | Counter++;
465 |
466 | //set the properties passed into the method
467 | objNewMember.TagName = Name.Replace("\"", "");
468 | objNewMember.InnerText = InnerText;
469 | objNewMember.ParentName = Parent;
470 | objNewMember.Children = Kids;
471 | //if (Parent == "Top")
472 | //{ objNewMember.Level = Level; }
473 | //else
474 | objNewMember.Level = Level + "." + Counter.ToString();
475 |
476 |
477 | if (Parameters != null)
478 | {
479 | paramList = Parameters.Split(',');
480 | if(paramList[0].StartsWith("\""))
481 | {objNewMember.AttributeName = paramList[0].Replace("\"", "");}
482 | else
483 | {objNewMember.Attributes.Add(paramList[0].Trim());}
484 | for (int i = 1; i < paramList.Length; i++)
485 | {
486 | // TO DO: Because the paramList is an ArrayList there is no reason why everything has to be
487 | // Stored as a string.
488 | objNewMember.Attributes.Add(paramList[i].Replace("\"", "").Trim());
489 | }
490 | }
491 |
492 | List.Add(objNewMember);
493 |
494 | while (InnerText != null)
495 | {
496 | int brkPos = InnerText.IndexOf("[");
497 | string valName = null;
498 | string subText = null;
499 | string param = null;
500 | if (brkPos > -1)
501 | {
502 | valName = (InnerText.Substring(0, brkPos)).Trim();
503 | if (valName.Contains(","))
504 | valName = (valName.Substring(valName.LastIndexOf(",") + 1)).Trim();
505 | subText = LoadParameterFromWKT(valName, InnerText);
506 | InnerText = (InnerText.Substring(brkPos + 2 + subText.Length)).Trim();
507 | if (InnerText.StartsWith(","))
508 | InnerText = InnerText.Substring(1);
509 | InnerText = InnerText.Trim();
510 |
511 | if (subText.Contains("["))
512 | {
513 | brkPos = subText.IndexOf("[");
514 | brkPos = subText.LastIndexOf(",", brkPos);
515 | param = subText.Substring(0, brkPos).Trim();
516 | }
517 | else
518 | {
519 | param = subText;
520 | subText = null;
521 | }
522 | }
523 | else
524 | {
525 | param = InnerText;
526 | InnerText = null;
527 | }
528 | if (InnerText == "")
529 | InnerText = null;
530 |
531 | objNewMember = (WKTNode)List[List.Count - 1];
532 | objNewMember.hasChildren = true;
533 | objNewMember.Children.Add(objNewMember.TagName, valName, subText, param, objNewMember.Level);
534 | }
535 |
536 | }
537 | internal void Add(WKTNode addNode)
538 | {
539 | List.Add(addNode);
540 | }
541 |
542 | private string LoadParameterFromWKT(string Header, string WKTString)
543 | {
544 | if (Header == null)
545 | { return null; }
546 | int paramStart = WKTString.IndexOf(Header);
547 | if (paramStart < 0)
548 | { return null; }
549 | paramStart += Header.Length + 1;
550 | string tString = WKTString.Substring(paramStart);
551 | int paramEnd = tString.IndexOf("]");
552 | int NoOfClosedBrks = 1;
553 | int NoOfOpenBrks = 0;
554 |
555 | while (NoOfClosedBrks != NoOfOpenBrks)
556 | {
557 | NoOfOpenBrks = 1;
558 | int brkPos = 0;
559 | do
560 | {
561 | // how many "[" are there between the start and the first closed brackets
562 |
563 | brkPos = tString.IndexOf("[", brkPos + 1);
564 | if (brkPos > -1 & brkPos < paramEnd)
565 | NoOfOpenBrks++;
566 | }
567 | while (brkPos > -1 & brkPos < paramEnd);
568 | if (NoOfOpenBrks != NoOfClosedBrks)
569 | {
570 | paramEnd = tString.IndexOf("]", paramEnd + 1);
571 | NoOfClosedBrks++;
572 | NoOfOpenBrks = 0;
573 | };
574 | }
575 |
576 | return tString.Substring(0, paramEnd);
577 | }
578 |
579 | ///
580 | /// Well Know Text node by Index
581 | ///
582 | ///
583 | public WKTNode this[int Index]
584 | {
585 | get
586 | {
587 | return (WKTNode)List[Index];
588 | }
589 | }
590 | ///
591 | /// Well Know Text node by Name
592 | ///
593 | ///
594 | public WKTNode this[string Name]
595 | {
596 | get
597 | {
598 | int retIndex = -1;
599 | for (int Index = 0; Index < List.Count; Index++)
600 | {
601 | WKTNode testField = (WKTNode)List[Index];
602 | if (testField.TagName == Name.ToUpper())
603 | {
604 | retIndex = Index;
605 | break;
606 | }
607 | }
608 | if (retIndex > -1)
609 | { return (WKTNode)List[retIndex]; }
610 | else
611 | { return null; }
612 |
613 | }
614 | }
615 |
616 | }
617 |
618 | ///
619 | /// A Node read from a WKT file
620 | ///
621 | public class WKTNode
622 | {
623 | private string mvarInnerText;
624 | private bool mvarhasInner;
625 | private string mvarParent;
626 | private string mvarName;
627 | private string mvarAttribName;
628 | private string mvarLevel;
629 | private WKTNodes mvarChildren;
630 |
631 | ArrayList mvarParams = new ArrayList();
632 |
633 | ///
634 | /// The name of the Attribute
635 | ///
636 | public string AttributeName
637 | {
638 | get { return mvarAttribName; }
639 | set { mvarAttribName = value; }
640 | }
641 | ///
642 | /// The Text Tag
643 | ///
644 | public string TagName
645 | {
646 | get { return mvarName; }
647 | set { mvarName = value; }
648 | }
649 | ///
650 | /// Then Name of the Parnet
651 | ///
652 | public string ParentName
653 | {
654 | get { return mvarParent; }
655 | set { mvarParent = value; }
656 | }
657 | internal string Level
658 | {
659 | get { return mvarLevel; }
660 | set { mvarLevel = value; }
661 | }
662 | ///
663 | /// The Node Inner Text
664 | ///
665 | public string InnerText
666 | {
667 | get { return mvarInnerText; }
668 | set { mvarInnerText = value; }
669 | }
670 | ///
671 | /// Do any children nodes exist?
672 | ///
673 | public bool hasChildren
674 | {
675 | get { return mvarhasInner; }
676 | set { mvarhasInner = value; }
677 | }
678 | ///
679 | /// The attribute of the node
680 | ///
681 | public ArrayList Attributes
682 | {
683 | get { return mvarParams; }
684 | set { mvarParams = value; }
685 | }
686 | ///
687 | /// The children of the node
688 | ///
689 | public WKTNodes Children
690 | {
691 | get { return mvarChildren; }
692 | set { mvarChildren = value; }
693 | }
694 |
695 | }
696 | }
697 |
--------------------------------------------------------------------------------
/bin/Release/ArcShapeFile.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rosspickard/ArcShapeFile/b885396cd478a917883fd0e67734c618ea8e60d6/bin/Release/ArcShapeFile.dll
--------------------------------------------------------------------------------
/bin/Release/ArcShapeFile.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rosspickard/ArcShapeFile/b885396cd478a917883fd0e67734c618ea8e60d6/bin/Release/ArcShapeFile.pdb
--------------------------------------------------------------------------------
/bin/Release/ArcShapeFile.tlb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rosspickard/ArcShapeFile/b885396cd478a917883fd0e67734c618ea8e60d6/bin/Release/ArcShapeFile.tlb
--------------------------------------------------------------------------------