ThinkGeo.com    |     Documentation    |     Premium Support

Adding features to a map

So i've been poking around a bit... and i'm slowly getting to grips with how things work when adding features to a map (i'm thinking dynamic shapes here... like a marker).


Each layer can have styles applied to it... which will in turn be applied to the features in the layer. 


How do I add a feature with its own style to a layer? I have a layer where i'd like to give some shapes a slightly different style to the rest in the layer. In MapSuite 2.0 I would find (for example) the PointMapShape... and modify the symbols (changing the PointSymbol or whatever). How do we do such things in 3.0?


 


Any advice welcome:P



Man, i’m still banging my head over this one. 
  
 I don’t seem to be able to set the style of an individual feature:/

Brendan, 



 


You can make it with ValueStyle: adding one column for classify the features and render them in different ways. Or you can get the “Special Shape” out and add them to an InMemoryFeatureLayer, set the style there. The equivalent method for adding a mapshape in 3.0 is adding a feature in an InmemoryFeatureLayer. Get and render one shape seems simpler in 2.0 but we thought it’s clearer to only allow layer be added to map and there do not have many scenarios to have many independent mapShapes. Can you show us your whole scenario so we can have an idea what’s the best way for you with 3.0?





Ben. 

 



Thanks for the response Ben. We definately appreciate it.


Perhaps I should give you an idea of what we're trying to achieve, and are able to do with MapSuite 2.0. You might want to get a cup of coffee and take a bathroom break before you read any further:P


In our application, the display of mapping data (e.g. shape files) is only a very small (but important) part of what we are doing. Applying the style to the layer is great for say Roads, Railways, Towns etc where they all look the same. We are however, far more interested in the ability to interact with objects on the map that are added by users.


The bulk of our application is centered around the interactive display of data to the user (Dynamic Shapes). Let's take a route as an example. If you want to plot a route on the map, it consists of a set of points, joined by a set of lines (or one line with many points). This can all be styled by applying a default line style and a default point style to the layer.


Now if you want to hightlight a point in a route, or add several "symbols" around a point in the route that give the user more information about that point, you would need to access the style information for that point, or at least be able to give that point a style different to the others on the layer. In MapSuite 2.0, I would find the PointMapShape, then access the symbols. To add additional icons around the primary icon, I would just add more sybols and apply an offset to each one.


Another example off the top of my head would be placing a set of markers on a map.. (perhaps different vehicles, or even runners etc). To distinguish between them, I would need to make each one look different. If I had to add a new layer each time I added a different type of marker, I might end up with hundreds of layers in an overlay.


I'm not sure if I have missed the boat on this one, and there is an easy way to do this.


If I could make this:


var feature = new Feature(x, y);

layer.InternalFeatures.Add("key", feature);


become something like this:


var feature = new Feature(x, y);

feature.Symbols.Add(new PointSymbol(PrimaryBitmap, XOffset, YOffset))

feature.Symbols.Add(new PointSymbol(OffsetBitmap, XOffset, YOffset))

layer.InternalFeatures.Add("key", feature);


I'd be quite happy.



Brendan,


Sorry for the late reply. Very busy these days and always failed to find a cup of coffee when I began to read this. :)
 
I now see your issue very well. The big problem I think is in 3.0, we don’t have MapShape. We know there are 2 critical elements for drawing a shape, one is data, ---- represents what to draw and the other is a style, ---- represents how to draw. In 2.0, MapShape is the lightest unit which contains both data and style, that’s why it can be drawn independently. While in 3.0, the lightest independent unit is a layer, which means you always have to create a layer even you want to just draw one record.
 
The main reason for this change is to keep things simple. As one MapShape is like a DynamicLayer with one record, we just unify them so we only have a layer and users will never be confused what a mapshape is and what a shape is.
 
For your 2nd scenario, I think maybe we can have a RandomStyle or DistinguishedStyle, we add features to an inMemoryLayer and apply the DistinguishedStyle, which makes sure every feature in the layer is rendered differently.
 
For your 1st scenario, MapShape seems ideally. Using 3.0, we have to add the “symbols” to an inMemoryLayer and render them separately. It’s definitely not convenient compared with using the offset of a mapShape in 2.0.  
 
I will report the 2 scenarios to our discussions and we will have a look what’s the best solution using 3.0. Thank you very much for pointing it out and if there is any solution about this, I will let you know here.
 
Ben. 

Thanks Ben. This one’s quite important for us. I appreciate the response.

Brendan, 
  
   I think the MapShapes model we had in our 2.0 system would be fairly simple for us to duplicate.  We wanted to initially stay away from it as it really limited the way people though of dynamic shapes.  It made it really easy to put one or two shapes on the map however when the user started to get into advanced sceneries it fell down a bit.  We might have overshot the solution with the InMemoryLayer and forcing all features to be the same. In retrospect I think there is room for both.  I am going to try and throw some code together for you on this.  In my mind right now it is clear how I can reuse some of our larger classes and make the MapShapeLayer pretty easily.  I will post to the code to show you either how easy it is to build it or I will answer back in a few hours telling you of my failure. :) 
  
 David

Brendan,


Success! This was about as easy as I thought it would be. All of the heavy lifting was done by existing classes and all I needed to do was a little glue code. In retrospect I would had liked to do this inheriting from the FeatureLayer and FeatureSource because then it would have made the layer query-able by spatial queries. In the interests of time for the forum I inherited from Layer which is really simple. You can see the comments and my post here take up more room than the code. 


I think you will find this works nearly exactly as the 2.0 map shapes did. It doesn't support projection however this could be easily added I think. We may create a production version of this class in the near future to include with the framework and if so we will support projection and spatial querying.


In a nutshell what I did was to create a MapShape class that housed a zoom level set and a single feature. The zoom level set has all of the zoom levels, styles Api etc on it so that part was nice to reuse. I matched this up with a new kind of Layer. I created the MapShapeLayer by inheriting from Layer. The only required overload was Draw. I added to the class a MapShape's GeoCollection to store all of your MapShapes. In the Draw I simple looped through the MapShapes and for each of them I found the zoom level that would be drawing and called its Draw method. In just a few lines of code it all came together.


I tested it will a little sample code and it seemed to work well. Of course with all things done in an hour I am sure there must be things I forgot or overlooked. If you run into them let me know. Other than that enjoy and feel free to enhance it to meet your needs.


See Code below


 




    // Ideally I would want to make this inherit from the FeatureLayer
    // so you could do spatial queries.  In the interests of time I 
    // inherited from the Layer to make things simple and show the point
    // of how easy it is to extent Map Suite.  When we roll this into
    // the main product we may create a FeatureSource and FeatureLayer
    public class MapShapeLayer : Layer
    {
        private GeoDictionary<string, MapShape> mapShapes;

        public MapShapeLayer()
        {
            mapShapes = new GeoDictionary<string, MapShape>();
        }

        // Here is where you place all of your map shapes.
        public GeoDictionary<string, MapShape> MapShapes
        {
            get { return mapShapes; }
        }

        // This is a required overload of the Layer.  As you can see we simply
        // loop through all of our map shapes and then choose the correct xoom level
        // After that the zoom level class takes care of the heavy lifiting.  You
        // have to love how easy this framework is to re-use.
        protected override void DrawCore(GeoCanvas canvas, GeographyUnit mapUnit, Collection<SimpleCandidate> labelsInAllLayers)
        {
            foreach (string mapShapeKey in mapShapes.Keys)
            {
                MapShape mapShape = mapShapes[mapShapeKey];
                ZoomLevel currentZoomLevel = mapShape.ZoomLevels.GetZoomLevelForDrawing(canvas.CurrentWorldExtent, canvas.Width, mapUnit);
                if (currentZoomLevel != null)
                {
                    if (canvas.CurrentWorldExtent.Intersects(mapShape.Feature.GetBoundingBox()))
                    {
                        currentZoomLevel.Draw(canvas, new Feature[] { mapShape.Feature }, new Collection<SimpleCandidate>(), labelsInAllLayers);
                    }
                }
            }
        }
    }

    // This is our MapShape and it will be the single unit in our layer
    public class MapShape
    {
        private Feature feature;
        private ZoomLevelSet zoomLevelSet;

        public MapShape(): this(new Feature())
        {
        }

        // Let's use this as a handy constructor if you already have
        // a feature or want to create on inline
        public MapShape(Feature feature)
        {
            this.feature = feature;
            zoomLevelSet = new ZoomLevelSet();
        }

        //  This is the feature property, pretty simple.
        public Feature Feature
        {
            get { return feature; }
            set { feature = value; }
        }

        // This is the Zoom Level Set.  This high level object has all of
        // the logic in it for zoom levels, drawing and everything
        public ZoomLevelSet ZoomLevels
        {
            get { return zoomLevelSet; }
            set { zoomLevelSet = value; }
        }
    }

        // This is just a code snippet to show you how the new
        // layer should be used.
        private void TestMapShapes()
        {
            MapShape mapShape1 = new MapShape(new Feature(0, 0));
            mapShape1.ZoomLevels.ZoomLevel01.DefaultPointStyle = PointStyles.CreateSimpleTriangleStyle(GeoColor.StandardColors.Red, 10);
            mapShape1.ZoomLevels.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            MapShape mapShape2 = new MapShape(new Feature(5, 5));
            mapShape2.ZoomLevels.ZoomLevel01.DefaultPointStyle = PointStyles.CreateSimpleTriangleStyle(GeoColor.StandardColors.Green, 10);
            mapShape2.ZoomLevels.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            MapShape mapShape3 = new MapShape(new Feature(new EllipseShape(new PointShape(10,10),2)));
            mapShape3.ZoomLevels.ZoomLevel01.DefaultAreaStyle = AreaStyles.Evergreen1;
            mapShape3.ZoomLevels.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

            MapShapeLayer mapShapeLayer = new MapShapeLayer();
            mapShapeLayer.MapShapes.Add("1", mapShape1);
            mapShapeLayer.MapShapes.Add("2", mapShape2);
            mapShapeLayer.MapShapes.Add("3", mapShape3);

            mapEngine.StaticLayers.Add("MapShapes", mapShapeLayer);
        }



Thanks David, we’ll give this a go today hopefully.

Brendan, 
  
   I found an issue in the code.  I forgot to check if the feature was in the current extent. 
  
                     if (canvas.CurrentWorldExtent.Intersects(mapShape.Feature.GetBoundingBox())) 
                     { 
                         currentZoomLevel.Draw(canvas, new Feature[] { mapShape.Feature }, new Collection<SimpleCandidate>(), labelsInAllLayers); 
                     } 
  
 This is in the Draw method.  I updated the code in the previous post so you can re-copy it.  Also if you have alot of them it might get a bit slow to calculate the bounding box over and over again using the GetBoundingBox.  We could cache it but that introduces the issue if you update the feature.  I think in the production version we will take all of this into account and do some performance testing.  Anyway enjoy. 
  
 David 
  


Thanks for that. We’ll keep a look out for performance as well.