public List <List <Point2d <T> > > Geometry <T>( uint?clipBuffer = null , float?scale = null ) { // parameters passed to this method override parameters passed to the constructor if (_clipBuffer.HasValue && !clipBuffer.HasValue) { clipBuffer = _clipBuffer; } if (_scale.HasValue && !scale.HasValue) { scale = _scale; } // TODO: how to cache 'finalGeom' without making whole class generic??? // and without using an object (boxing) ??? List <List <Point2d <T> > > finalGeom = _cachedGeometry as List <List <Point2d <T> > >; if (null != finalGeom && scale == _previousScale) { return(finalGeom); } //decode commands and coordinates List <List <Point2d <long> > > geom = DecodeGeometry.GetGeometry( _layer.Extent , GeometryType , GeometryCommands , scale.Value ); if (clipBuffer.HasValue) { geom = UtilGeom.ClipGeometries(geom, GeometryType, (long)_layer.Extent, clipBuffer.Value, scale.Value); } //HACK: use 'Scale' to convert to <T> too finalGeom = DecodeGeometry.Scale <T>(geom, scale.Value); //set field needed for next iteration _previousScale = scale; _cachedGeometry = finalGeom; return(finalGeom); }
/// <summary> /// Get a feature of the <see cref="VectorTileLayer"/> /// </summary> /// <param name="layer"><see cref="VectorTileLayer"/> containing the feature</param> /// <param name="data">Raw byte data of the feature</param> /// <param name="validate">If true, run checks if the tile contains valid data. Decreases decoding speed.</param> /// <param name="clippBuffer"> /// <para>'null': returns the geometries unaltered as they are in the vector tile. </para> /// <para>Any value >=0 clips a border with the size around the tile. </para> /// <para>These are not pixels but the same units as the 'extent' of the layer. </para> /// </param> /// <returns></returns> public static VectorTileFeature GetFeature( VectorTileLayer layer , byte[] data , bool validate = true , uint?clippBuffer = null ) { PbfReader featureReader = new PbfReader(data); VectorTileFeature feat = new VectorTileFeature(layer); bool geomTypeSet = false; while (featureReader.NextByte()) { int featureType = featureReader.Tag; if (validate) { if (!ConstantsAsDictionary.FeatureType.ContainsKey(featureType)) { throw new System.Exception(string.Format("Layer [{0}] has unknown feature type: {1}", layer.Name, featureType)); } } switch ((FeatureType)featureType) { case FeatureType.Id: feat.Id = (ulong)featureReader.Varint(); break; case FeatureType.Tags: #if NET20 List <int> tags = featureReader.GetPackedUnit32().ConvertAll <int>(ui => (int)ui); #else List <int> tags = featureReader.GetPackedUnit32().Select(t => (int)t).ToList(); #endif feat.Tags = tags; break; case FeatureType.Type: int geomType = (int)featureReader.Varint(); if (validate) { if (!ConstantsAsDictionary.GeomType.ContainsKey(geomType)) { throw new System.Exception(string.Format("Layer [{0}] has unknown geometry type tag: {1}", layer.Name, geomType)); } } feat.GeometryType = (GeomType)geomType; geomTypeSet = true; break; case FeatureType.Geometry: if (null != feat.Geometry) { throw new System.Exception(string.Format("Layer [{0}], feature already has a geometry", layer.Name)); } //get raw array of commands and coordinates List <uint> geometryCommands = featureReader.GetPackedUnit32(); //decode commands and coordinates List <List <Point2d> > geom = DecodeGeometry.GetGeometry( layer.Extent , feat.GeometryType , geometryCommands ); if (clippBuffer.HasValue) { geom = clipGeometries(geom, feat.GeometryType, (long)layer.Extent, clippBuffer.Value); } feat.Geometry = geom; break; default: featureReader.Skip(); break; } } if (validate) { if (!geomTypeSet) { throw new System.Exception(string.Format("Layer [{0}]: feature missing geometry type", layer.Name)); } if (null == feat.Geometry) { throw new System.Exception(string.Format("Layer [{0}]: feature has no geometry", layer.Name)); } if (0 != feat.Tags.Count % 2) { throw new System.Exception(string.Format("Layer [{0}]: uneven number of feature tag ids", layer.Name)); } if (feat.Tags.Count > 0) { #if NET20 int maxKeyIndex = -9999; for (int i = 0; i < feat.Tags.Count; i += 2) { if (feat.Tags[i] > maxKeyIndex) { maxKeyIndex = feat.Tags[i]; } } int maxValueIndex = -9999; for (int i = 1; i < feat.Tags.Count; i += 2) { if (feat.Tags[i] > maxValueIndex) { maxValueIndex = feat.Tags[i]; } } #else int maxKeyIndex = feat.Tags.Where((key, idx) => idx % 2 == 0).Max(); int maxValueIndex = feat.Tags.Where((key, idx) => (idx + 1) % 2 == 0).Max(); #endif if (maxKeyIndex >= layer.Keys.Count) { throw new System.Exception(string.Format("Layer [{0}]: maximum key index equal or greater number of key elements", layer.Name)); } if (maxValueIndex >= layer.Values.Count) { throw new System.Exception(string.Format("Layer [{0}]: maximum value index equal or greater number of value elements", layer.Name)); } } } return(feat); }
public List <List <Vector2 <T> > > Geometry <T>( uint?clipBuffer = null , float?scale = null ) { // parameters passed to this method override parameters passed to the constructor if (_clipBuffer.HasValue && !clipBuffer.HasValue) { clipBuffer = _clipBuffer; } if (_scale.HasValue && !scale.HasValue) { scale = _scale; } // TODO: how to cache 'finalGeom' without making whole class generic??? // and without using an object (boxing) ??? var finalGeom = _cachedGeometry as List <List <Vector2 <T> > >; if (null != finalGeom && scale == _previousScale) { return(finalGeom); } //decode commands and coordinates List <List <Vector2 <long> > > geom = DecodeGeometry.GetGeometry( Layer.Extent , GeometryType , GeometryCommands , scale.Value ); if (clipBuffer.HasValue) { // HACK !!! // work around a 'feature' of clipper where the ring order gets mixed up // with multipolygons containing holes if (geom.Count < 2 || GeometryType != GeomType.POLYGON) { // work on points, lines and single part polygons as before geom = UtilGeom.ClipGeometries(geom, GeometryType, (long)Layer.Extent, clipBuffer.Value, scale.Value); } else { // process every ring of a polygon in a separate loop var newGeom = new List <List <Vector2 <long> > >(); int geomCount = geom.Count; for (int i = 0; i < geomCount; i++) { List <Vector2 <long> > part = geom[i]; var tmp = new List <List <Vector2 <long> > >(); // flip order of inner rings to look like outer rings bool isInner = signedPolygonArea(part) >= 0; if (isInner) { part.Reverse(); } tmp.Add(part); tmp = UtilGeom.ClipGeometries(tmp, GeometryType, (long)Layer.Extent, clipBuffer.Value, scale.Value); // ring was completely outside of clip border if (0 == tmp.Count) { continue; } // one part might result in several geoms after clipping, eg 'u'-shape where the // lower part is completely beyond the actual tile border but still within the buffer foreach (List <Vector2 <long> > item in tmp) { // flip winding order of inner rings back if (isInner) { item.Reverse(); } newGeom.Add(item); } } geom = newGeom; } } //HACK: use 'Scale' to convert to <T> too finalGeom = DecodeGeometry.Scale <T>(geom, scale.Value); //set field needed for next iteration _previousScale = scale; _cachedGeometry = finalGeom; return(finalGeom); }