/// <summary> /// Forces each of the parts to adopt an extent equal to a calculated extents. /// The extents for the shape will expand to include those. /// </summary> public Extent CalculateExtents() { Extent ext = new Extent(); foreach (PartRange part in Parts) { ext.ExpandToInclude(part.CalculateExtent()); } Extent = ext; return ext; }
/// <summary> /// Obtains a typed list of ShapefilePoint structures with double values associated with the various coordinates. /// </summary> /// <param name="filename">A string filename</param> /// <param name="progressHandler">A progress indicator</param> private void FillPoints(string filename, IProgressHandler progressHandler) { // Check to ensure the filename is not null if (filename == null) { throw new NullReferenceException(MessageStrings.ArgumentNull_S.Replace("%S",filename)); } if (File.Exists(filename) == false) { throw new FileNotFoundException(MessageStrings.FileNotFound_S.Replace("%S", filename)); } // Reading the headers gives us an easier way to track the number of shapes and their overall length etc. List<ShapeHeader> shapeHeaders = ReadIndexFile(filename); // Get the basic header information. ShapefileHeader header = new ShapefileHeader(filename); Extent = new Extent(new[] { header.Xmin, header.Ymin, header.Xmax, header.Ymax }); Envelope = Extent.ToEnvelope(); // Check to ensure that the filename is the correct shape type if (header.ShapeType != ShapeTypes.Point && header.ShapeType != ShapeTypes.PointM && header.ShapeType != ShapeTypes.PointZ) { throw new ApplicationException(MessageStrings.FileNotPoints_S.Replace("%S", filename)); } // This will set up a reader so that we can read values in huge chunks, which is much faster than one value at a time. IO.BufferedBinaryReader bbReader = new IO.BufferedBinaryReader(filename, progressHandler); if (bbReader.FileLength == 100) { bbReader.Close(); // the file is empty so we are done reading return; } // Skip the shapefile header by skipping the first 100 bytes in the shapefile bbReader.Seek(100, SeekOrigin.Begin); int numShapes = shapeHeaders.Count; byte[] bigEndian = new byte[numShapes * 8]; byte[] allCoords = new byte[numShapes * 16]; bool isM = false; bool isZ = false; if(header.ShapeType == ShapeTypes.PointM || header.ShapeType == ShapeTypes.PointZ) { isM = true; } if (header.ShapeType == ShapeTypes.PointZ) { isZ = true; } byte[] allM = new byte[8]; if(isM) allM = new byte[numShapes * 8]; byte[] allZ = new byte[8]; if(isZ) allZ = new byte[numShapes * 8]; for (int shp = 0; shp < numShapes; shp++) { // Read from the index file because some deleted records // might still exist in the .shp file. long offset = (shapeHeaders[shp].ByteOffset); bbReader.Seek(offset, SeekOrigin.Begin); bbReader.Read(bigEndian, shp * 8, 8); ShapeTypes type = (ShapeTypes)bbReader.ReadInt32(); //bbReader.Seek(4, SeekOrigin.Current); bbReader.Read(allCoords, shp * 16, 16); if(isZ) { bbReader.Read(allZ, shp*8, 8); } if (isM) { bbReader.Read(allM, shp*8, 8); } ShapeRange shape = new ShapeRange(FeatureTypes.Point); shape.StartIndex = shp; shape.ContentLength = 8; shape.NumPoints = 1; shape.NumParts = 1; ShapeIndices.Add(shape); } double[] vert = new double[2 * numShapes]; Buffer.BlockCopy(allCoords, 0, vert, 0, numShapes*16); Vertex = vert; if(isM) { double[] m = new double[numShapes]; Buffer.BlockCopy(allM, 0, m, 0, numShapes * 8); M = m; } if(isZ) { double[] z = new double[numShapes]; Buffer.BlockCopy(allZ, 0, z, 0, numShapes * 8); Z = z; } for (int shp = 0; shp < numShapes; shp++) { PartRange part = new PartRange(vert, shp, 0, FeatureTypes.Point); part.NumVertices = 1; ShapeRange shape = ShapeIndices[shp]; shape.Parts.Add(part); shape.Extent = new Extent(new[] {vert[shp*2], vert[shp*2 + 1], vert[shp*2], vert[shp*2 + 1]}); } bbReader.Dispose(); }
/// <summary> /// This creates a polygon shape from an extent. /// </summary> /// <param name="ext">The extent to turn into a polygon shape.</param> public ShapeRange(Extent ext) { Extent = ext; Parts = new List<PartRange>(); _numParts = -1; // Counter clockwise // 1 2 // 4 3 double[] coords = new double[8]; // C1 coords[0] = ext.XMin; coords[1] = ext.YMax; // C2 coords[2] = ext.XMax; coords[3] = ext.YMax; // C3 coords[4] = ext.XMax; coords[5] = ext.YMin; // C4 coords[6] = ext.XMin; coords[7] = ext.YMin; FeatureType = FeatureTypes.Polygon; ShapeType = ShapeTypes.Polygon; PartRange pr = new PartRange(coords, 0,0, FeatureTypes.Polygon); pr.NumVertices = 4; Parts.Add(pr); }
/// <summary> /// This tests each feature of the input /// </summary> /// <param name="self">This featureSet</param> /// <param name="other">The featureSet to perform intersection with</param> /// <param name="joinType">The attribute join type</param> /// <param name="progHandler">A progress handler for status messages</param> /// <returns>An IFeatureSet with the intersecting features, broken down based on the join Type</returns> public static IFeatureSet Intersection(this IFeatureSet self, IFeatureSet other, FieldJoinType joinType, IProgressHandler progHandler) { IFeatureSet result = null; ProgressMeter pm = new ProgressMeter(progHandler, "Calculating Intersection", self.Features.Count); if (joinType == FieldJoinType.All) { result = CombinedFields(self, other); // Intersection is symetric, so only consider I X J where J <= I int i=0; foreach(IFeature selfFeature in self.Features) { List<IFeature> potentialOthers = other.Select(selfFeature.Envelope); foreach (IFeature otherFeature in potentialOthers) { selfFeature.Intersection(otherFeature, result, joinType); } pm.CurrentValue = i; i++; } pm.Reset(); } if (joinType == FieldJoinType.LocalOnly) { result = new FeatureSet(); result.CopyTableSchema(self); result.FeatureType = self.FeatureType; IFeature union; pm = new ProgressMeter(progHandler, "Calculating Union", other.Features.Count); if(other.Features != null && other.Features.Count > 0) { union = other.Features[0]; for(int i = 1; i < other.Features.Count; i++) { union = union.Union(other.Features[i]); pm.CurrentValue = i; } pm.Reset(); pm = new ProgressMeter(progHandler, "Calculating Intersections", self.NumRows()); Extent otherEnvelope = new Extent(union.Envelope); for (int shp = 0; shp < self.ShapeIndices.Count; shp++) { if (!self.ShapeIndices[shp].Extent.Intersects(otherEnvelope)) continue; IFeature selfFeature = self.GetFeature(shp); selfFeature.Intersection(union, result, joinType); pm.CurrentValue = shp; } pm.Reset(); } } if (joinType == FieldJoinType.ForeignOnly) { result = new FeatureSet(); result.CopyTableSchema(other); IFeature union; if (self.Features != null && self.Features.Count > 0) { pm = new ProgressMeter(progHandler, "Calculating Union", self.Features.Count); union = self.Features[0]; for (int i = 1; i < self.Features.Count; i++) { union = union.Union(self.Features[i]); pm.CurrentValue = i; } pm.Reset(); if (other.Features != null) { pm = new ProgressMeter(progHandler, "Calculating Intersection", other.Features.Count); int j = 0; foreach (IFeature otherFeature in other.Features) { IFeature test = otherFeature.Intersection(union, result, joinType); if (test.BasicGeometry != null) { result.Features.Add(test); } pm.CurrentValue = j; j++; } } pm.Reset(); } } return result; }
/// <summary> /// This creates a new extent and cylces through the coordinates to calculate what it is. /// Since the vertices may change so easilly, this is not cached. /// </summary> /// <returns></returns> public Extent CalculateExtent() { // Create an extent for faster checking in most cases Extent ext = new Extent(); // Do this once, and then we can re-use it for each other part. foreach (Vertex point in this) { if (ext.XMax < point.X) ext.XMax = point.X; if (ext.XMin > point.X) ext.XMin = point.X; if (ext.YMin > point.Y) ext.YMin = point.Y; if (ext.YMax < point.Y) ext.YMax = point.Y; } return ext; }
///// <summary> ///// Obsolete and slow. First get the X and Y array once, and then access the members directly from that array. ///// </summary> //public double[][] Vertices //{ // get // { // int len = _vertices.Length/2; // double[][] result = new double[len][]; // for (int i = 0; i < len; i++) // { // result[i] = new[] { _vertices[i*2], _vertices[i*2+1] }; // } // return result; // } // protected set // { // for(int i = 0; i < value.Length; i++) // { // _vertices[i*2] = value[i][0]; // _vertices[i*2 + 1] = value[i][1]; // } // } //} /// <summary> /// After changing coordinates, this will force the re-calculation of envelopes on a feature /// level as well as on the featureset level. If this featureset is in index mode, /// this will update the shape extents instead. /// </summary> public void UpdateEnvelopes() { if (InternalDataSet != null) { InternalDataSet.UpdateEnvelopes(); return; } _envelope = new Envelope(); _extent = new Extent(); if(!_indexMode) { if (Features == null || Features.Count <= 0) { _extent = new Extent(-180, -90, 180, 90); _envelope = _extent.ToEnvelope(); return; } for (int shp = 0; shp < Features.Count; shp++) { Features[shp].UpdateEnvelope(); _envelope.ExpandToInclude(Features[shp].Envelope); } _extent = new Extent(_envelope); } else { if(_shapeIndices == null || _shapeIndices.Count == 0) { _extent = new Extent(-180, -90, 180, 90); _envelope = _extent.ToEnvelope(); return; } foreach (ShapeRange range in _shapeIndices) { _extent.ExpandToInclude(range.Extent); } _envelope = _extent.ToEnvelope(); } }
// X Y MultiPoints: Total Length = 28 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 8 Integer 1 Little // Byte 12 Xmin Double 1 Little // Byte 20 Ymin Double 1 Little // Byte 28 Xmax Double 1 Little // Byte 36 Ymax Double 1 Little // Byte 48 NumPoints Integer 1 Little // Byte X Points Point NumPoints Little // X Y M MultiPoints: Total Length = 34 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 28 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumPoints Integer 1 Little // Byte X Points Point NumPoints Little // Byte Y* Mmin Double 1 Little // Byte Y + 8* Mmax Double 1 Little // Byte Y + 16* Marray Double NumPoints Little // X Y Z M MultiPoints: Total Length = 44 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 18 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumPoints Integer 1 Little // Byte X Points Point NumPoints Little // Byte Y Zmin Double 1 Little // Byte Y + 8 Zmax Double 1 Little // Byte Y + 16 Zarray Double NumPoints Little // Byte Z* Mmin Double 1 Little // Byte Z+8* Mmax Double 1 Little // Byte Z+16* Marray Double NumPoints Little private void FillPoints(string filename, IProgressHandler progressHandler) { // Check to ensure the filename is not null if (filename == null) { throw new NullReferenceException(MessageStrings.ArgumentNull_S.Replace("%S", filename)); } if (File.Exists(filename) == false) { throw new FileNotFoundException(MessageStrings.FileNotFound_S.Replace("%S", filename)); } // Get the basic header information. ShapefileHeader header = new ShapefileHeader(filename); Extent = new Extent(new[] { header.Xmin, header.Ymin, header.Xmax, header.Ymax }); // Check to ensure that the filename is the correct shape type if (header.ShapeType != ShapeTypes.MultiPoint && header.ShapeType != ShapeTypes.MultiPointM && header.ShapeType != ShapeTypes.MultiPointZ) { throw new ArgumentException(MessageStrings.FileNotLines_S.Replace("%S", filename)); } // Reading the headers gives us an easier way to track the number of shapes and their overall length etc. List<ShapeHeader> shapeHeaders = ReadIndexFile(filename); // This will set up a reader so that we can read values in huge chunks, which is much faster than one value at a time. IO.BufferedBinaryReader bbReader = new IO.BufferedBinaryReader(filename, progressHandler); if (bbReader.FileLength == 100) { // The shapefile is empty so we can simply return here bbReader.Close(); return; } // Skip the shapefile header by skipping the first 100 bytes in the shapefile bbReader.Seek(100, SeekOrigin.Begin); int numShapes = shapeHeaders.Count; byte[] bigEndians = new byte[numShapes * 8]; byte[] allBounds = new byte[numShapes * 32]; ByteBlock allCoords = new ByteBlock(BLOCKSIZE); bool isM = (header.ShapeType == ShapeTypes.MultiPointZ || header.ShapeType == ShapeTypes.MultiPointM); bool isZ = (header.ShapeType == ShapeTypes.PolyLineZ); ByteBlock allZ = null; ByteBlock allM = null; if (isZ) { allZ = new ByteBlock(BLOCKSIZE); } if (isM) { allM = new ByteBlock(BLOCKSIZE); } int pointOffset = 0; for (int shp = 0; shp < numShapes; shp++) { // Read from the index file because some deleted records // might still exist in the .shp file. long offset = (shapeHeaders[shp].ByteOffset); bbReader.Seek(offset, SeekOrigin.Begin); // time: 200 ms ShapeRange shape = new ShapeRange(FeatureTypes.MultiPoint); shape.RecordNumber = bbReader.ReadInt32(false); // Byte 0 Record Number Integer 1 Big shape.ContentLength = bbReader.ReadInt32(false); // Byte 4 Content Length Integer 1 Big //bbReader.Read(bigEndians, shp * 8, 8); shape.ShapeType = (ShapeTypes)bbReader.ReadInt32(); shape.StartIndex = pointOffset; if (shape.ShapeType == ShapeTypes.NullShape) { continue; } bbReader.Read(allBounds, shp * 32, 32); shape.NumParts = 1; shape.NumPoints = bbReader.ReadInt32(); allCoords.Read(shape.NumPoints * 16, bbReader); pointOffset += shape.NumPoints; if (header.ShapeType == ShapeTypes.MultiPointM) { // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (shape.ContentLength * 2 > 44 + 4 * shape.NumParts + 16 * shape.NumPoints) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); //bbReader.Seek(16, SeekOrigin.Current); if (allM != null) allM.Read(shape.NumPoints * 8, bbReader); } } if (header.ShapeType == ShapeTypes.MultiPointZ) { bool hasM = shape.ContentLength * 2 > 60 + 4 * shape.NumParts + 24 * shape.NumPoints; double zMin = bbReader.ReadDouble(); double zMax = bbReader.ReadDouble(); // For Z shapefiles, the Z part is not optional. if (allZ != null) allZ.Read(shape.NumPoints * 8, bbReader); // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (hasM) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); if (allM != null) allM.Read(shape.NumPoints * 8, bbReader); } } // Now that we have read all the values, create the geometries from the points and parts arrays. ShapeIndices.Add(shape); } double[] vert = allCoords.ToDoubleArray(); Vertex = vert; if (isM) M = allM.ToDoubleArray(); if (isZ) Z = allZ.ToDoubleArray(); Array.Reverse(bigEndians); List<ShapeRange> shapes = ShapeIndices; double[] bounds = new double[numShapes * 4]; Buffer.BlockCopy(allBounds, 0, bounds, 0, allBounds.Length); for (int shp = 0; shp < numShapes; shp++) { ShapeRange shape = shapes[shp]; shape.Extent = new Extent(bounds, shp * 4); int endIndex = shape.NumPoints + shape.StartIndex; int startIndex = shape.StartIndex; int count = endIndex - startIndex; PartRange partR = new PartRange(vert, shape.StartIndex, 0, FeatureTypes.MultiPoint); partR.NumVertices = count; shape.Parts.Add(partR); } GC.Collect(); bbReader.Dispose(); }
private void BuildPaths(MapArgs e, IEnumerable<int> indices, out List<GraphicsPath> paths) { paths = new List<GraphicsPath>(); Extent drawExtents = new Extent(e.PixelToProj(_drawingBounds)); Dictionary<FastDrawnState, GraphicsPath> borders = new Dictionary<FastDrawnState, GraphicsPath>(); for (int selectState = 0; selectState < 2; selectState++) { foreach (IPolygonCategory category in Symbology.Categories) { FastDrawnState state = new FastDrawnState(selectState == 1, category); GraphicsPath border = new GraphicsPath(); borders.Add(state, border); paths.Add(border); } } List<ShapeRange> shapes = DataSet.ShapeIndices; double[] vertices = DataSet.Vertex; if(!DrawnStatesNeeded) { FastDrawnState state = new FastDrawnState(false, Symbology.Categories[0]); foreach (int shp in indices) { ShapeRange shape = shapes[shp]; if (!shape.Extent.Intersects(e.GeographicExtents)) continue; if(shp >= shapes.Count) continue; if (!borders.ContainsKey(state)) continue; if (drawExtents.Contains(shape.Extent)) { BuildPolygon(vertices, shapes[shp], borders[state], e, false); } else { BuildPolygon(vertices, shapes[shp], borders[state], e, true); } } } else { FastDrawnState[] states = DrawnStates; foreach (int shp in indices) { if (shp >= shapes.Count) continue; if (shp >= states.Length) { AssignFastDrawnStates(); states = DrawnStates; } if (states[shp].Visible == false) continue; ShapeRange shape = shapes[shp]; if (!shape.Extent.Intersects(e.GeographicExtents)) continue; if (drawExtents.Contains(shape.Extent)) { FastDrawnState state = states[shp]; if (!borders.ContainsKey(state)) continue; BuildPolygon(vertices, shapes[shp], borders[state], e, false); } else { FastDrawnState state = states[shp]; if (!borders.ContainsKey(state)) continue; BuildPolygon(vertices, shapes[shp], borders[state], e, true); } } } }
/// <summary> /// This will draw any features that intersect this region. To specify the features /// directly, use OnDrawFeatures. This will not clear existing buffer content. /// For that call Initialize instead. /// </summary> /// <param name="args">A GeoArgs clarifying the transformation from geographic to image space</param> /// <param name="regions">The geographic regions to draw</param> public void DrawRegions(MapArgs args, List<IEnvelope> regions) { if (FeatureSet == null) return; if(FeatureSet.IndexMode) { // First determine the number of features we are talking about based on region. List<int> drawIndices = new List<int>(); foreach (IEnvelope region in regions) { if (region != null) { // We need to consider labels that go off the screen. figure a region // that is larger. IEnvelope sur = new Envelope(region.Copy()); sur.ExpandBy(region.Width, region.Height); Extent r = new Extent(sur); // Use union to prevent duplicates. No sense in drawing more than we have to. drawIndices = drawIndices.Union(FeatureSet.SelectIndices(r)).ToList(); } } List<Rectangle> clips = args.ProjToPixel(regions); DrawFeatures(args, drawIndices, clips, true); } else { // First determine the number of features we are talking about based on region. List<IFeature> drawList = new List<IFeature>(); foreach (IEnvelope region in regions) { if (region != null) { // We need to consider labels that go off the screen. figure a region // that is larger. IEnvelope r = region.Copy(); r.ExpandBy(region.Width, region.Height); // Use union to prevent duplicates. No sense in drawing more than we have to. drawList = drawList.Union(FeatureSet.Select(r)).ToList(); } } List<Rectangle> clipRects = args.ProjToPixel(regions); DrawFeatures(args, drawList, clipRects, true); } }
/// <summary> /// Tests if this envelope is contained by the specified envelope /// </summary> /// <param name="ext"></param> /// <returns></returns> public bool Contains(Extent ext) { if (XMin > ext.XMin) return false; if (XMax < ext.XMax) return false; if (YMin > ext.YMin) return false; if (YMax < ext.YMax) return false; return true; }
/// <summary> /// Draws the GraphicsPaths. Before we were effectively "re-creating" the same geometric /// </summary> /// <param name="e"></param> /// <param name="paths"></param> private void DrawPaths(MapArgs e, IList<GraphicsPath> paths) { Graphics g = e.Device ?? Graphics.FromImage(_backBuffer); int numCategories = Symbology.Categories.Count; if(!DrawnStatesNeeded && !EditMode) { IPolygonSymbolizer ps = Symbolizer; g.SmoothingMode = ps.Smoothing ? SmoothingMode.AntiAlias : SmoothingMode.None; Extent catBounds = new Extent(DataSet.Envelope); RectangleF bounds = new RectangleF(); bounds.X = Convert.ToSingle((catBounds.XMin - e.MinX) * e.Dx); bounds.Y = Convert.ToSingle((e.MaxY - catBounds.YMax) * e.Dy); float r = Convert.ToSingle((catBounds.XMax - e.MinX) * e.Dx); bounds.Width = r - bounds.X; float b = Convert.ToSingle((e.MaxY - catBounds.YMin) * e.Dy); bounds.Height = b - bounds.Y; foreach (IPattern pattern in ps.Patterns) { IGradientPattern gp = pattern as IGradientPattern; if (gp != null) { gp.Bounds = bounds; } pattern.FillPath(g, paths[0]); } double scale = 1; if (ps.ScaleMode == ScaleModes.Geographic) { scale = e.ImageRectangle.Width / e.GeographicExtents.Width; } foreach (IPattern pattern in ps.Patterns) { if (pattern.UseOutline) { pattern.DrawPath(g, paths[0], scale); } } } else { for (int selectState = 0; selectState < 2; selectState++) { int iCategory = 0; foreach (IPolygonCategory category in Symbology.Categories) { Extent catBounds; if (CategoryExtents.Keys.Contains(category)) { catBounds = CategoryExtents[category]; } else { catBounds = CalculateCategoryExtent(category); } if (catBounds == null) catBounds = new Extent(Envelope); RectangleF bounds = new RectangleF(); bounds.X = Convert.ToSingle((catBounds.XMin - e.MinX) * e.Dx); bounds.Y = Convert.ToSingle((e.MaxY - catBounds.YMax) * e.Dy); float r = Convert.ToSingle((catBounds.XMax - e.MinX) * e.Dx); bounds.Width = r - bounds.X; float b = Convert.ToSingle((e.MaxY - catBounds.YMin) * e.Dy); bounds.Height = b - bounds.Y; int index = selectState * numCategories + iCategory; // Define the symbology based on the category and selection state IPolygonSymbolizer ps = category.Symbolizer; if (selectState == SELECTED) ps = category.SelectionSymbolizer; g.SmoothingMode = ps.Smoothing ? SmoothingMode.AntiAlias : SmoothingMode.None; foreach (IPattern pattern in ps.Patterns) { IGradientPattern gp = pattern as IGradientPattern; if (gp != null) { gp.Bounds = bounds; } paths[index].FillMode = FillMode.Winding; pattern.FillPath(g, paths[index]); } double scale = 1; if (ps.ScaleMode == ScaleModes.Geographic) { scale = e.ImageRectangle.Width / e.GeographicExtents.Width; } foreach (IPattern pattern in ps.Patterns) { if (pattern.UseOutline) { pattern.DrawPath(g, paths[index], scale); } } iCategory++; } // category } // selectState } for (int i = 0; i < Symbology.Categories.Count; i++) { paths[i].Dispose(); } if(e.Device == null) g.Dispose(); }
/// <summary> /// Tests if this envelope is contained by the specified envelope /// </summary> /// <param name="ext"></param> /// <returns></returns> public bool Within(Extent ext) { if(XMin < ext.XMin) return false; if (XMax > ext.XMax) return false; if (YMin < ext.YMin) return false; if (YMax > ext.YMax) return false; return true; }
/// <summary> /// Expands this extent to include the domain of the specified extent /// </summary> /// <param name="ext">The extent to expand to include</param> public void ExpandToInclude(Extent ext) { if (ext.XMin < XMin) XMin = ext.XMin; if (ext.YMin < YMin) YMin = ext.YMin; if (ext.XMax > XMax) XMax = ext.XMax; if (ext.YMax > YMax) YMax = ext.YMax; }
/// <summary> /// Tests for an intersection with the specified extent /// </summary> /// <param name="ext">The other extent</param> /// <returns>Boolean, true if they overlap anywhere, or even touch</returns> public bool Intersects(Extent ext) { if (ext.XMax < XMin) return false; if (ext.YMax < YMin) return false; if (ext.XMin > XMax) return false; if (ext.YMin > YMax) return false; return true; }
/// <summary> /// Skips the features themselves and uses the shapeindicies instead. /// </summary> /// <param name="region">The region to select members from</param> /// <returns>A list of integer valued shape indices that are selected.</returns> public virtual List<int> SelectIndices(Extent region) { List<int> result = new List<int>(); List<ShapeRange> shapes = ShapeIndices; // get from internal dataset if necessary if(shapes != null) { for (int shp = 0; shp < shapes.Count; shp++) { if (_shapeIndices[shp].Extent.Intersects(region)) result.Add(shp); } } return result; }
private void DrawFeatures(MapArgs e, IEnumerable<int> indices) { Graphics g; if (e.Device != null) { g = e.Device; // A device on the MapArgs is optional, but overrides the normal buffering behaviors. } else { g = Graphics.FromImage(_backBuffer); } // Only draw features that are currently visible. Extent drawExtents = new Extent(e.PixelToProj(_drawingBounds)); double minX = e.MinX; double maxY = e.MaxY; double dx = e.Dx; double dy = e.Dy; if(DrawnStatesNeeded) { FastDrawnState[] states = DrawnStates; for (int selectState = 0; selectState < 2; selectState++) { foreach (ILineCategory category in Symbology.Categories) { // Define the symbology based on the category and selection state ILineSymbolizer ls = category.Symbolizer; if (selectState == SELECTED) ls = category.SelectionSymbolizer; if (ls.Smoothing) { g.SmoothingMode = SmoothingMode.AntiAlias; } else { g.SmoothingMode = SmoothingMode.None; } // Determine the subset of the specified features that are visible and match the category ILineCategory lineCategory = category; int i = selectState; Func<FastDrawnState, bool> isMember = state => state.Category == lineCategory && state.Selected == (i == 1) && state.Visible == true; var drawnFeatures = from feature in indices where isMember(states[feature]) select feature; GraphicsPath graphPath = new GraphicsPath(); foreach (int shp in drawnFeatures) { ShapeRange shape = DataSet.ShapeIndices[shp]; if (drawExtents.Contains(shape.Extent)) { // This optimization only works if we are zoomed out far enough // that the whole linestring fits in the short integer // drawing window. Otherwise, we need to crop the line. FastBuildLine(graphPath, DataSet.Vertex, shape, minX, maxY, dx, dy); } else { for (int iPart = 0; iPart < shape.Parts.Count; iPart++) { IBasicGeometry geom = DataSet.GetFeature(shp).GetBasicGeometryN(iPart); if (drawExtents.Contains(shape.Extent) == false) { geom = e.GeographicExtents.Intersection(geom); } // Cropped geometries can be either linestrings or multi-linestrings IBasicLineString bls = geom as IBasicLineString; if (bls != null) { // the result is definitely in the linestring category BuildLineString(graphPath, bls, minX, maxY, dx, dy); } else { IMultiLineString intersect = geom as MultiLineString; if (intersect == null) continue; for (int iLine = 0; iLine < intersect.NumGeometries; iLine++) { BuildLineString(graphPath, intersect.GetBasicGeometryN(iLine) as IBasicLineString, minX, maxY, dx, dy); } } } } } double scale = 1; if (ls.ScaleMode == ScaleModes.Geographic) { scale = e.ImageRectangle.Width / e.GeographicExtents.Width; } foreach (IStroke stroke in ls.Strokes) { stroke.DrawPath(g, graphPath, scale); } graphPath.Dispose(); } } } else { // Selection state is disabled // Category is only the very first category ILineCategory category = Symbology.Categories[0]; ILineSymbolizer ls = category.Symbolizer; if (ls.Smoothing) { g.SmoothingMode = SmoothingMode.AntiAlias; } else { g.SmoothingMode = SmoothingMode.None; } // Determine the subset of the specified features that are visible and match the category ILineCategory lineCategory = category; GraphicsPath graphPath = new GraphicsPath(); foreach (int shp in indices) { ShapeRange shape = DataSet.ShapeIndices[shp]; if (drawExtents.Contains(shape.Extent)) { // This optimization only works if we are zoomed out far enough // that the whole linestring fits in the short integer // drawing window. Otherwise, we need to crop the line. FastBuildLine(graphPath, DataSet.Vertex, shape, minX, maxY, dx, dy); } else { for (int iPart = 0; iPart < shape.Parts.Count; iPart++) { IBasicGeometry geom = DataSet.GetFeature(shp).GetBasicGeometryN(iPart); if (drawExtents.Contains(shape.Extent) == false) { geom = e.GeographicExtents.Intersection(geom); } // Cropped geometries can be either linestrings or multi-linestrings IBasicLineString bls = geom as IBasicLineString; if (bls != null) { // the result is definitely in the linestring category BuildLineString(graphPath, bls, minX, maxY, dx, dy); } else { IMultiLineString intersect = geom as MultiLineString; if (intersect == null) continue; for (int iLine = 0; iLine < intersect.NumGeometries; iLine++) { BuildLineString(graphPath, intersect.GetBasicGeometryN(iLine) as IBasicLineString, minX, maxY, dx, dy); } } } } } double scale = 1; if (ls.ScaleMode == ScaleModes.Geographic) { scale = e.ImageRectangle.Width / e.GeographicExtents.Width; } foreach (IStroke stroke in ls.Strokes) { stroke.DrawPath(g, graphPath, scale); } graphPath.Dispose(); } if (e.Device == null) g.Dispose(); }
/// <summary> /// returns only the features that have envelopes that /// intersect with the specified envelope. If in indexMode, this uses the ShapeIndices to create /// features on demand, rather than loading all the features. It is much faster to use selectIndices /// when in index mode. /// </summary> /// <param name="region">The specified region to test for intersect with</param> /// <param name="affectedRegion">This returns the geographic extents that contains the modified contents.</param> /// <returns>A List of the IFeature elements that are contained in this region</returns> public virtual List<IFeature> Select(IEnvelope region, out IEnvelope affectedRegion) { List<IFeature> result = new List<IFeature>(); if(IndexMode) { ShapeRange aoi = new ShapeRange(new Extent(region)); IEnvelope env = region.Copy(); Extent affected = new Extent(); List<ShapeRange> shapes = ShapeIndices; if(shapes != null) { for(int shp = 0; shp < shapes.Count; shp++) { if (!shapes[shp].Intersects(aoi)) continue; IFeature f = GetFeature(shp); affected.ExpandToInclude(shapes[shp].Extent); result.Add(f); } } affectedRegion = affected.ToEnvelope(); return result; } affectedRegion = new Envelope(); foreach (IFeature feature in Features) { if (!feature.Envelope.Intersects(region)) continue; result.Add(feature); affectedRegion.ExpandToInclude(feature.Envelope); } return result; }
/// <summary> /// This calculates the extent for the category and caches it in the extents collection /// </summary> /// <param name="category"></param> protected virtual Extent CalculateCategoryExtent(IFeatureCategory category) { Extent ext = new Extent(new[]{double.MaxValue, double.MaxValue, double.MinValue, double.MinValue}); if(_editMode) { IDictionary<IFeature, IDrawnState> features = _drawingFilter.DrawnStates; foreach (IFeature f in DataSet.Features) { if (category == features[f].SchemeCategory) { ext.ExpandToInclude(new Extent(f.Envelope)); } } if (_categoryExtents.Keys.Contains(category)) { _categoryExtents[category] = ext.Copy(); } else { _categoryExtents.Add(category, ext.Copy()); } } else { FastDrawnState[] states = DrawnStates; List<ShapeRange> ranges = DataSet.ShapeIndices; for(int shp = 0; shp < DrawnStates.Length; shp++) { if(!_categoryExtents.ContainsKey(states[shp].Category)) { _categoryExtents.Add(states[shp].Category, ranges[shp].Extent.Copy()); } else { _categoryExtents[states[shp].Category].ExpandToInclude(ranges[shp].Extent); } } } return ext; }
/// <summary> /// If this featureset is in index mode, this will append the vertices and shapeindex of the shape. /// Otherwise, this will add a new feature based on this shape. If the attributes of the shape are not null, /// this will attempt to append a new datarow It is up to the developer /// to ensure that the object array of attributes matches the this featureset. If the Attributes of this feature are loaded, /// this will add the attributes in ram only. Otherwise, this will attempt to insert the attributes as a /// new record using the "AddRow" method. The schema of the object array should match this featureset's column schema. /// </summary> /// <param name="shape">The shape to add to this featureset.</param> public void AddShape(Shape shape) { if(InternalDataSet != null) { InternalDataSet.AddShape(shape); return; } IFeature addedFeature = null; // This first section controls the indices which need to happen regardless // because drawing uses indices even if editors like working with features. int count = (_vertices != null) ? _vertices.Length / 2 : 0; // Original number of points int totalCount = shape.Range.NumPoints + count; int start = shape.Range.StartIndex; int num = shape.Range.NumPoints; double[] vertices = new double[totalCount*2]; if (_vertices != null) Array.Copy(_vertices, 0, vertices, 0, _vertices.Length); if(shape.Vertices != null) Array.Copy(shape.Vertices, start * 2, vertices, count * 2, num * 2); if(_m != null || shape.M != null) { double[] m = new double[totalCount]; if (_m != null) Array.Copy(_m, 0, m, 0, _m.Length); if (shape.M != null) Array.Copy(shape.Vertices, start, m, count, num); _m = m; } if(_z != null || shape.Z != null) { double[] z = new double[totalCount]; if (_z != null) Array.Copy(_z, 0, z, 0, _z.Length); if (shape.Z != null) Array.Copy(shape.Vertices, start, z, count, num); _z = z; } shape.Range.StartIndex = count; ShapeIndices.Add(shape.Range); Vertex = vertices; shape.Vertices = vertices; if (Extent == null) Extent = new Extent(); Extent.ExpandToInclude(shape.Range.Extent); Envelope = Extent.ToEnvelope(); if(!_indexMode) { // Just add a new feature addedFeature = new Feature(shape); Features.Add(addedFeature); addedFeature.DataRow = AddAttributes(shape); if(Extent == null)Extent = new Extent(); if (!shape.Range.Extent.IsEmpty()) Extent.ExpandToInclude(new Extent(addedFeature.Envelope)); Envelope = Extent.ToEnvelope(); } }
// X Y Poly Lines: Total Length = 28 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 3 Integer 1 Little // Byte 12 Xmin Double 1 Little // Byte 20 Ymin Double 1 Little // Byte 28 Xmax Double 1 Little // Byte 36 Ymax Double 1 Little // Byte 44 NumParts Integer 1 Little // Byte 48 NumPoints Integer 1 Little // Byte 52 Parts Integer NumParts Little // Byte X Points Point NumPoints Little // X Y M Poly Lines: Total Length = 34 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 23 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumParts Integer 1 Little // Byte 48 NumPoints Integer 1 Little // Byte 52 Parts Integer NumParts Little // Byte X Points Point NumPoints Little // Byte Y* Mmin Double 1 Little // Byte Y + 8* Mmax Double 1 Little // Byte Y + 16* Marray Double NumPoints Little // X Y Z M Poly Lines: Total Length = 44 Bytes // --------------------------------------------------------- // Position Value Type Number Byte Order // --------------------------------------------------------- // Byte 0 Record Number Integer 1 Big // Byte 4 Content Length Integer 1 Big // Byte 8 Shape Type 13 Integer 1 Little // Byte 12 Box Double 4 Little // Byte 44 NumParts Integer 1 Little // Byte 48 NumPoints Integer 1 Little // Byte 52 Parts Integer NumParts Little // Byte X Points Point NumPoints Little // Byte Y Zmin Double 1 Little // Byte Y + 8 Zmax Double 1 Little // Byte Y + 16 Zarray Double NumPoints Little // Byte Z* Mmin Double 1 Little // Byte Z+8* Mmax Double 1 Little // Byte Z+16* Marray Double NumPoints Little private void FillPolygons(string filename, IProgressHandler progressHandler) { // Check to ensure the filename is not null if (filename == null) { throw new NullReferenceException(MessageStrings.ArgumentNull_S.Replace("%S", filename)); } if (File.Exists(filename) == false) { throw new FileNotFoundException(MessageStrings.FileNotFound_S.Replace("%S", filename)); } // Get the basic header information. ShapefileHeader header = new ShapefileHeader(filename); Extent = new Extent(new[]{header.Xmin, header.Ymin, header.Xmax, header.Ymax}); // Check to ensure that the filename is the correct shape type if (header.ShapeType != ShapeTypes.Polygon && header.ShapeType != ShapeTypes.PolygonM && header.ShapeType != ShapeTypes.PolygonZ) { throw new ArgumentException(MessageStrings.FileNotLines_S.Replace("%S", filename)); } // Reading the headers gives us an easier way to track the number of shapes and their overall length etc. List<ShapeHeader> shapeHeaders = ReadIndexFile(filename); // This will set up a reader so that we can read values in huge chunks, which is much faster than one value at a time. IO.BufferedBinaryReader bbReader = new IO.BufferedBinaryReader(filename, progressHandler); if (bbReader.FileLength == 100) { // The shapefile is empty so we can simply return here bbReader.Close(); return; } // Skip the shapefile header by skipping the first 100 bytes in the shapefile bbReader.Seek(100, SeekOrigin.Begin); int numShapes = shapeHeaders.Count; int [] partOffsets = new int[numShapes]; byte[] bigEndians = new byte[numShapes * 8]; byte[] allBounds = new byte[numShapes * 32]; ByteBlock allParts = new ByteBlock(BLOCKSIZE); // probably all will be in one block, but use a byteBlock just in case. ByteBlock allCoords = new ByteBlock(BLOCKSIZE); bool isM = (header.ShapeType == ShapeTypes.PolyLineM || header.ShapeType == ShapeTypes.PolyLineZ); bool isZ = (header.ShapeType == ShapeTypes.PolyLineZ); ByteBlock allZ = null; ByteBlock allM = null; if (isZ) { allZ = new ByteBlock(BLOCKSIZE); } if (isM) { allM = new ByteBlock(BLOCKSIZE); } int pointOffset = 0; for (int shp = 0; shp < numShapes; shp++) { // Read from the index file because some deleted records // might still exist in the .shp file. long offset = (shapeHeaders[shp].ByteOffset); bbReader.Seek(offset, SeekOrigin.Begin); // Position Value Type Number Byte Order ShapeRange shape = new ShapeRange(FeatureTypes.Polygon); //-------------------------------------------------------------------- shape.RecordNumber = bbReader.ReadInt32(false); // Byte 0 Record Number Integer 1 Big shape.ContentLength = bbReader.ReadInt32(false); // Byte 4 Content Length Integer 1 Big shape.ShapeType = (ShapeTypes)bbReader.ReadInt32(); // Byte 8 Shape Type Integer 1 Little shape.StartIndex = pointOffset; if (shape.ShapeType == ShapeTypes.NullShape) { continue; } bbReader.Read(allBounds, shp*32, 32); //double xMin = bbReader.ReadDouble(); // Byte 12 Xmin Double 1 Little // double yMin = bbReader.ReadDouble(); // Byte 20 Ymin Double 1 Little //double xMax = bbReader.ReadDouble(); // Byte 28 Xmax Double 1 Little //double yMax = bbReader.ReadDouble(); // Byte 36 Ymax Double 1 Little shape.NumParts = bbReader.ReadInt32(); // Byte 44 NumParts Integer 1 Little //feature.NumPoints = bbReader.ReadInt32(); // Byte 48 NumPoints Integer 1 Little shape.NumPoints = bbReader.ReadInt32(); // Create an envelope from the extents box in the file. //feature.Envelope = new Envelope(xMin, xMax, yMin, yMax); partOffsets[shp] = allParts.IntOffset(); allParts.Read(shape.NumParts * 4, bbReader); allCoords.Read(shape.NumPoints * 16, bbReader); pointOffset += shape.NumPoints; if (header.ShapeType == ShapeTypes.PolygonM) { // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (shape.ContentLength * 2 > 44 + 4 * shape.NumParts + 16 * shape.NumPoints) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); if(allM != null)allM.Read(shape.NumPoints * 8, bbReader); } } if (header.ShapeType == ShapeTypes.PolygonZ) { bool hasM = shape.ContentLength * 2 > 60 + 4 * shape.NumParts + 24 * shape.NumPoints; double zMin = bbReader.ReadDouble(); double zMax = bbReader.ReadDouble(); // For Z shapefiles, the Z part is not optional. if (allZ != null) allZ.Read(shape.NumPoints * 8, bbReader); // These are listed as "optional" but there isn't a good indicator of how to determine if they were added. // To handle the "optional" M values, check the contentLength for the feature. // The content length does not include the 8-byte record header and is listed in 16-bit words. if (hasM) { double mMin = bbReader.ReadDouble(); double mMax = bbReader.ReadDouble(); if (allM != null) allM.Read(shape.NumPoints * 8, bbReader); } } ShapeIndices.Add(shape); } double[] vert = allCoords.ToDoubleArray(); Vertex = vert; if (isM) M = allM.ToDoubleArray(); if (isZ) Z = allZ.ToDoubleArray(); List<ShapeRange> shapes = ShapeIndices; double[] bounds = new double[numShapes * 4]; Buffer.BlockCopy(allBounds, 0, bounds, 0, allBounds.Length); int[] parts = allParts.ToIntArray(); ProgressMeter = new ProgressMeter(ProgressHandler, "Testing Parts and Holes", numShapes); for (int shp = 0; shp < numShapes; shp++) { ShapeRange shape = shapes[shp]; shape.Extent = new Extent(bounds, shp * 4); for (int part = 0; part < shape.NumParts; part++) { int offset = partOffsets[shp]; int endIndex = shape.NumPoints + shape.StartIndex; int startIndex = parts[offset + part] + shape.StartIndex; if (part < shape.NumParts - 1) endIndex = parts[offset + part + 1] + shape.StartIndex; int count = endIndex - startIndex; PartRange partR = new PartRange(vert, shape.StartIndex, parts[offset + part], FeatureTypes.Polygon); partR.NumVertices = count; shape.Parts.Add(partR); } ProgressMeter.CurrentValue = shp; } ProgressMeter.Reset(); GC.Collect(); }