ThinkGeo.com    |     Documentation    |     Premium Support

MapShape features

Greetings,


I need to have some of the features of the old 2.0 MapShapes in my application.  Specifically, I want to have each "MapShape" contain its own style.  I saw your post on MapShapes, but the sample code doesn't seem to have anything for styling, so I went about trying to create it on my own.


My first idea was to inherit from Feature and store the style information in that, however the Feature class is sealed.  What I ended up doing was creating classes that inherit from Polygon, LineShape, etc. that contains the style, and then add those to the feature, and create a custom layer that draws the features according to their own style.  However, when I call feature.GetShape(), the objects returned are no longer of my created type, so I've lost all the style data.


How should I go about achieving this.  I need all my "MapShapes" on a single layer, each with their own style.  I also need the ability to do spatial queries on them (that is why I keep them in features rather than creating a custom layer that just stores MapShapes).


Any help would be appreaciated.  Thank you for your time.


.Ryan.



Ryan,


 That is a good and interesting question. I believe there are a couple of ways to achieve this based on the flexibility of MapSuite component.
 
Following is a simple way to achieve this and also some sample codes, I used the latest build(3.0.307 RC1 Desktop)

public class StyledFeatureLayer : Layer
    {
        private Dictionary<Feature, Style> features;

        public StyledFeatureLayer()
            :this(new Dictionary<Feature,Style>())
        { }

        public StyledFeatureLayer(Dictionary<Feature, Style> features)
            :base()
        {
            this.features = features;
        }

        public Dictionary<Feature, Style> Features
        {
            get { return features; }
        }

        protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            Collection<SimpleCandidate> labelInThisLayer = new Collection<SimpleCandidate>();
            Collection<SimpleCandidate> labelInAllLayers = new Collection<SimpleCandidate>();

            foreach (Feature feature in features.Keys)
            {
                Style style = features[feature];
                style.Draw(new Feature[] { feature }, canvas, labelInThisLayer, labelInAllLayers);
            }
        }
    }

Test codes:
winformsMap1.MapUnit = GeographyUnit.Meter;

            winformsMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.GeographicColors.ShallowOcean);
            winformsMap1.ZoomLevelSnapping = ZoomLevelSnappingMode.None;

            StyledFeatureLayer styledFeatureLayer = new StyledFeatureLayer();
            BaseShape rectangle1 = new RectangleShape(0, 45, 45, 0);
            Feature feature1 = new Feature(rectangle1);
            BaseShape rectangle2 = new EllipseShape(new PointShape(45, 45), 5);
            Feature feature2 = new Feature(rectangle2);
            LineShape line1 = new LineShape();
            line1.Vertices.Add(new Vertex(0, 0));
            line1.Vertices.Add(new Vertex(45, 0));
            line1.Vertices.Add(new Vertex(0, 45));
            Feature feature3 = new Feature(line1);

            styledFeatureLayer.Features.Add(feature1, AreaStyles.Country1);
            styledFeatureLayer.Features.Add(feature2, AreaStyles.Grass1);
            styledFeatureLayer.Features.Add(feature3, LineStyles.Canal1);

            LayerOverlay staticOverlay = new LayerOverlay();
            staticOverlay.Layers.Add("WorldLayer", styledFeatureLayer);
            winformsMap1.Overlays.Add(staticOverlay);
            winformsMap1.Refresh();

Let me know if any more questions.


 Thanks.
Yale

 



Thank you for your reply.  I would also like the ability to perform spatial queries on my features.  Specifically, I would like to determine if the user clicks on (or within a few pixels) of a feature.  I was able to get this working using an InMemoryFeatureLayer, but that requires the ability to find the nearest feature to a point, which is made available by FeatureLayer.  Maybe there is another way to do this that I am not aware of, or is there a way to add the ability to do spatial queries to the StyledFeatureLayer? 
  
 Thank you for your help and time, 
  
 .Ryan.

Ryan, you are welcome. 


Try the following update one:

public class StyledFeatureLayer : FeatureLayer
    {
        private Dictionary<Feature, Style> features;

        public StyledFeatureLayer()
            :this(new Dictionary<Feature,Style>())
        { }

        public StyledFeatureLayer(Dictionary<Feature, Style> features)
            :base()
        {
            this.features = features;

            FeatureSource = new InMemoryFeatureSource(new Collection<FeatureSourceColumn>(), features.Keys);
        }

        public Dictionary<Feature, Style> Features
        {
            get { return features; }
        }

        protected override bool IsOpenCore
        {
            get
            {
                return true;
            }
        }

        protected override void SetupToolsCore()
        {
            base.SetupToolsCore();

            FeatureSource = new InMemoryFeatureSource(new Collection<FeatureSourceColumn>(), features.Keys);
        }

        protected override void DrawCore(GeoCanvas canvas, Collection<SimpleCandidate> labelsInAllLayers)
        {
            Collection<SimpleCandidate> labelInThisLayer = new Collection<SimpleCandidate>();
            Collection<SimpleCandidate> labelInAllLayers = new Collection<SimpleCandidate>();

            foreach (Feature feature in features.Keys)
            {
                Style style = features[feature];
                style.Draw(new Feature[] { feature }, canvas, labelInThisLayer, labelInAllLayers);
            }
        }
    }

Test codes:

private void Form1_Load(object sender, EventArgs e)
        {
            winformsMap1.MapUnit = GeographyUnit.Meter;

            winformsMap1.BackgroundOverlay.BackgroundBrush = new GeoSolidBrush(GeoColor.GeographicColors.ShallowOcean);
            winformsMap1.ZoomLevelSnapping = ZoomLevelSnappingMode.Default;

            StyledFeatureLayer styledFeatureLayer = new StyledFeatureLayer();
            BaseShape rectangle1 = new RectangleShape(0, 45, 45, 0);
            Feature feature1 = new Feature(rectangle1);
            BaseShape rectangle2 = new EllipseShape(new PointShape(45, 45), 5);
            Feature feature2 = new Feature(rectangle2);
            LineShape line1 = new LineShape();
            line1.Vertices.Add(new Vertex(0, 0));
            line1.Vertices.Add(new Vertex(45, 0));
            line1.Vertices.Add(new Vertex(0, 45));
            Feature feature3 = new Feature(line1);

            styledFeatureLayer.Features.Add(feature1, AreaStyles.Country1);
            styledFeatureLayer.Features.Add(feature2, AreaStyles.Grass1);
            styledFeatureLayer.Features.Add(feature3, LineStyles.Canal1);


            InMemoryFeatureLayer rectangleLayer = new InMemoryFeatureLayer();
            rectangleLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = new AreaStyle(new GeoSolidBrush(new GeoColor(50, 100, 100, 200)));
            rectangleLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.OutlinePen.Color = GeoColor.StandardColors.DarkBlue;
            rectangleLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;
            rectangleLayer.InternalFeatures.Add("Rectangle", new Feature("POLYGON((-100 -20,-100 200,50 200,50 -20,-100 -20))", "Rectangle"));

            InMemoryFeatureLayer spatialQueryResultLayer = new InMemoryFeatureLayer();
            spatialQueryResultLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle = new AreaStyle(new GeoSolidBrush(GeoColor.FromArgb(200, GeoColor.SimpleColors.PastelRed)));
            spatialQueryResultLayer.ZoomLevelSet.ZoomLevel01.DefaultAreaStyle.OutlinePen.Color = GeoColor.StandardColors.Red;
            spatialQueryResultLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            LayerOverlay staticOverlay = new LayerOverlay();
            staticOverlay.Layers.Add("WorldLayer", styledFeatureLayer);
            staticOverlay.Layers.Add("RectangleLayer", rectangleLayer);

            LayerOverlay spatialQueryResultOverlay = new LayerOverlay();
            spatialQueryResultOverlay.Layers.Add("SpatialQueryResultLayer", spatialQueryResultLayer);

            winformsMap1.Overlays.Add(staticOverlay);
            winformsMap1.Overlays.Add("SpatialQueryResultOverlay", spatialQueryResultOverlay);
            winformsMap1.Refresh();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            StyledFeatureLayer worldLayer = (StyledFeatureLayer)winformsMap1.FindFeatureLayer("WorldLayer");
            InMemoryFeatureLayer rectangleLayer = (InMemoryFeatureLayer)winformsMap1.FindFeatureLayer("RectangleLayer");
            InMemoryFeatureLayer spatialQueryResultLayer = (InMemoryFeatureLayer)winformsMap1.FindFeatureLayer("SpatialQueryResultLayer");

            Feature rectangleFeature = rectangleLayer.InternalFeatures["Rectangle"];
            Collection<Feature> spatialQueryResults;
            worldLayer.Open();
            string text = "Intersecting";
            switch (text)
            {
                case "Within":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesWithin(rectangleFeature, new string[0]);
                    break;
                case "Containing":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesContaining(rectangleFeature, new string[0]);
                    break;
                case "Disjointed":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesDisjointed(rectangleFeature, new string[0]);
                    break;
                case "Intersecting":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesIntersecting(rectangleFeature, new string[0]);
                    break;
                case "Overlapping":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesOverlapping(rectangleFeature, new string[0]);
                    break;
                case "TopologicalEqual":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesTopologicalEqual(rectangleFeature, new string[0]);
                    break;
                case "Touching":
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesTouching(rectangleFeature, new string[0]);
                    break;
                default:
                    spatialQueryResults = worldLayer.QueryTools.GetFeaturesWithin(rectangleFeature, new string[0]);
                    break;
            }
            worldLayer.Close();

            winformsMap1.Overlays["SpatialQueryResultOverlay"].Lock.EnterWriteLock();
            try
            {
                spatialQueryResultLayer.InternalFeatures.Clear();
                foreach (Feature feature in spatialQueryResults)
                {
                    spatialQueryResultLayer.InternalFeatures.Add(feature.Id, feature);
                }
            }
            finally
            {
                winformsMap1.Overlays["SpatialQueryResultOverlay"].Lock.ExitWriteLock();
            }

            winformsMap1.Refresh();
        }

Hope this can gives you any help. Let me know if any more queries.
Thanks.
 
Yale

Is there a way to use the above sample to have column values assigned to a feature in addition to a unique style?  I've tried implementing this sample by overriding InMemoryFeatureLayer instead of FeatureLayer so I could specify custom columns.  I then add features to the Features collection with ColumnValues data and a style.  When I do a spatial query the features are returned but the ColumnValues data is not.  It seems as though you need to add features to the InternalFeatures collection in order to have data returned in a query, but then I cannot specify a style for each feature.  It would be nice if I could override the Feature object to add a Style property to it, but it seems to be a sealed class so it's not allowed.  I've read that there is going to be a MapShapeLayer type of class in a future release, which I assume will support having data associated with the featues in the layer as well.  Does anyone know of a workaround until this feature is released? Thanks in advance.



Chad,


Thanks for your post!
 
Which version of Desktop Edition you are trying to use? And can you send me you code showing your problem? I am a bit confused what is going on.
 
Besides, we have a demonstration sample in following to show MapShapes:
code.thinkgeo.com/projects/show/mapshapes
 
Feel free let me know if any more questions.
 
Thanks.
 
Yale
 

I attached my code in the file.  I grouped the pertinent classes together in the single file.


I'm trying to extend he MapShapes sample project code by attaching data to the shapes as well as unique styles.


In my code you should see from my comments that I'm now able to get column data right after populating the custom layer.  But, when I try to get the data later outside of the initial layer setup I don't get data back.


Hopefully you can understand what I'm doing from the code.  Thanks,


Chad



Chad,


I think there are some very minor mistakes in your layer. Please try the new codes in attachment.
 
Let me know if any more questions.
 
Thanks.
 
Yale

1101-MyMemoryLayer.cs (6.21 KB)

Yale,


Sorry for the late reply.  Your code fixed me!  Thanks for your help.


Chad



Chad, it is my pleasure! 
  
 Any more questions please feel free to let me know. 
  
 Thanks. 
  
 Yale