Back to contents pageHow to create your own Map Model

How to create your own Map Model

Summary

This article shows you how to create your own map model so that you can add objects to your visualization.





Prerequisites

You should know the information in the following article:



Example material

Save this link to get a zip file of the example material


Guide

Before you start, remember to:

Initial set-up

Add using statements

Add using statements for Ubisense.UBase and Ubisense.UVis to all your files:


using System;
using System.Collections.Generic;
using System.Text;

using Ubisense.UBase;
using Ubisense.UVis;



Enumerate your object types

In order to define the types of objects that you can add to your model, create an enumeration of the types. In this example, we have 2 possible additions to the map: Sensor and Zone. We will make sensors clickable by the user, but zones not clickable.

The default Nil type is also required, to be used when the the mouse pointer moves over somewhere other than one of our objects on the map.


    // Enumeration of all possible additions to the MapModel.
    public enum MapModelObjectType
    {
        // Default value, will not apply to an object that is
        // actually in any of the mappings.
        Nil,

        Sensor, // A single sensor. Clickable.
        Zone    // A 3D region. Not clickable.
    }



Identify the map objects

In order to tell which object the mouse is over on the visualization, we need to give each object a unique ID.

Each object's unique ID can be made up of its type and an ID number. This means you don't need to create a unique number for all objects, only those of the same type.

In our example, the class TypeId provides the unique ID for each object in the model. It contains the MapModelObjectType along with a long integer, which will be unique to each object of the specified type.

The public members allow the user to get the type or id number as required. The available constructors create instances of either the Nil type or the specified MapModelObjectType and id number.

Finally, in this example, the class needs to inherit from IComparable. This is because it is going to be used as the key in a mapping from TypeId to UObject. The CompareTo method is implemented with precedence type followed by id number.


    // The unique ID of a map model object.
    // This contains a Type and Id, together forming
    // the overall unique ID.
    public class TypeId : IComparable<TypeId>
    {
        private MapModelObjectType type;
        private long id;

        // The MapModelObjectType.
        public MapModelObjectType Type
        {
            get { return this.type; }
        }

        // The id, unique to this type.
        public long Id
        {
            get { return this.id; }
        }

        // Create a new TypeId with the Nil MapModelObjectType.
        public TypeId()
        {
            this.type = MapModelObjectType.Nil;
            this.id = -1;
        }

        // Create a new TypeId with the specified MapModelObjectType and id.
        public TypeId(MapModelObjectType _type, long _id)
        {
            this.type = _type;
            this.id = _id;
        }

        public int CompareTo(TypeId other)
        {
            int type_result = this.type.CompareTo(other.type);

            if (type_result != 0)
            {
                return type_result;
            }

            return this.id.CompareTo(other.id);
        }

    }
}




Create a base class for your representations

Our representations should all have a unique id, so we can determine which one the mouse is over at any point.
Therefore, you should define a base class for the representations that contains our TypeId class as follows:


    // Base class for the other Rep classes.
    abstract class MyMapModelRep : Rep
    {
        // The unique id and type of the representation.
        protected TypeId type_id = new TypeId();
        public TypeId TypeID
        {
            get { return this.type_id; }
        }
    }





Define the Map Model

Inherit from MapModel and IModel

Now we can define MyMapModel. Firstly, it inherits from MapModel since it will be our visualization map model. Secondly, it inherits from the interface IModel so our objects can be rendered in the visualization.


    // The user-defined map model, including support
    // for adding and removing objects of type MapModelObjectType.
    class MyMapModel : MapModel, IModel
    {



Multi-thread synchronisation

Define an object for multi-thread synchronisation:


        private object sync = new object();



Storing the objects and their representations

Define mappings from UObject to Key, TypeId and Rep:


        // All objects appear in these mappings from UObject to key, type and rep.

        private SortedDictionary<UObject, Key> map_obj_key = new
            SortedDictionary<UObjectKey>();

        private SortedDictionary<UObjectlong> map_obj_id = new
            SortedDictionary<UObjectlong>();

        private SortedDictionary<UObject, MapModelObjectType> map_obj_type = new
            SortedDictionary<UObject, MapModelObjectType>();

        private SortedDictionary<KeyRep> map_key_rep = new
            SortedDictionary<KeyRep>();

        private SortedDictionary<TypeId, UObject> map_type_id_obj = new
            SortedDictionary<TypeId, UObject>();



Identifying the objects

We need a way of determining which object, if any, the mouse is over at any point. Define a public method to return a TypeId given the UObject that we will obtain from the renderer's hit test:


        // Returns the type and id given the UObject,
        // called when the mouse moves over the map.
        public TypeId get_id(UObject obj)
        {
            lock (sync)
            {
                if ((map_obj_type.ContainsKey(obj)) &&
                    (map_obj_id.ContainsKey(obj)))
                {
                    return new TypeId(
                        map_obj_type[obj],
                        map_obj_id[obj]
                    );
                }
                else
                {
                    return new TypeId();
                }
            }
        }



Adding objects

We need a way of adding objects to our map model. Define the following private method to populate all the mappings and tell any renderers that an object has been inserted into the model.
Note: This means that the visualization will always be updated with the new object as soon as it is added - there is no need to call invalidate() on your visualization instance.


        // Private method to add a new UObject to the map model.
        // This populates the mappings as required.
        private void add_obj(Rep rep, TypeId type_id)
        {
            lock (sync)
            {
                // Create the new key and obj.
                Key key = new Key(true);
                UObject obj = new UObject();
                obj.Unique();

                // Add the mappings from UObject
                // to the type, key, rep and id.
                map_obj_type.Add(obj, type_id.Type);
                map_obj_key.Add(obj, key);
                map_key_rep.Add(key, rep);
                map_obj_id.Add(obj, type_id.Id);
                map_type_id_obj.Add(type_id, obj);

                foreach (IRenderer r in Renderers)
                {
                    r.OnInsertObject(obj);
                    r.OnCommit();
                }
            }
        }


Create a templated public method that calls the private add_obj just defined. Any representation that derives from MyMapModelRep can now be added to the model using this method:


        // Add a new object to the map model.
        public void add_obj<T>(T r) where T : MyMapModelRep
        {
            add_obj(r, r.TypeID);
        }



Removing objects

We need a way of removing objects from the map model. Define the following private method to clear the mappings and tell any renderers than an object has been removed from the model.

Note: This means that the visualization will always be updated to remove the object as soon as it is deleted - there is no need to call invalidate() on your visualization instance.


        // Private method to remove all entries from mappings
        // for the given object/id. This also informs renderers
        // of the removal.
        private void clear_mapping(UObject obj, TypeId type_id)
        {
            if (map_obj_type.ContainsKey(obj))
            {
                map_obj_type.Remove(obj);
            }

            if (map_obj_key.ContainsKey(obj))
            {
                if (map_key_rep.ContainsKey(map_obj_key[obj]))
                {
                    map_key_rep.Remove(map_obj_key[obj]);
                }
                map_obj_key.Remove(obj);
            }

            if (map_obj_id.ContainsKey(obj))
            {
                map_obj_id.Remove(obj);
            }

            if (map_type_id_obj.ContainsKey(type_id))
            {
                map_type_id_obj.Remove(type_id);
            }

            foreach (IRenderer r in Renderers)
            {
                r.OnRemoveObject(obj);
                r.OnCommit();
            }
        }


Making use of the clear_mapping method just defined, create a public method to remove a single object from the map model, given the type and id:


        // Remove a specific object from the map model,
        // given the type and id.
        public void remove_object(TypeId type_id)
        {
            lock (sync)
            {
                if (map_type_id_obj.ContainsKey(type_id))
                {
                    clear_mapping(
                        map_type_id_obj[type_id],
                        type_id
                    );
                }
            }
        }


Create a public method to remove all objects of the given types from the map model:


        // Remove all objects of the types in the list from the map model.
        public void remove_objects(List<MapModelObjectType> types)
        {
            lock (sync)
            {
                // Copy of map_obj_type for iterating over.
                SortedDictionary<UObject, MapModelObjectType> search_list =
                    new SortedDictionary<UObject, MapModelObjectType>(map_obj_type);

                // Clear mappings of all pairs with the specified type.
                foreach (KeyValuePair<UObject, MapModelObjectType> kvp in search_list)
                {
                    foreach (MapModelObjectType type in types)
                    {
                        if (kvp.Value == type)
                        {
                            if (map_obj_id.ContainsKey(kvp.Key))
                            {
                                clear_mapping(
                                    kvp.Key,
                                    new TypeId(type, map_obj_id[kvp.Key])
                                );
                            }
                        }
                    }
                }
            }
        }


Create a public method to remove all objects from the map model. After clearing the mappings, use the OnRepEstablish method on all renderers to re-establish renderer state:


        // Remove all objects from the map model.
        public void clear()
        {
            lock (sync)
            {
                map_obj_key = new SortedDictionary<UObjectKey>();
                map_obj_type = new SortedDictionary<UObjectMapModelObjectType>();
                map_key_rep = new SortedDictionary<KeyRep>();
                map_obj_id = new SortedDictionary<UObjectlong>();
                map_type_id_obj = new SortedDictionary<TypeId, UObject>();

                foreach (IRenderer r in Renderers)
                {
                    r.OnRepEstablish();
                }
            }
        }



Define the IModel Interface

Now define the IModel interface. The following members only call the base method:


        void IModel.AddRenderer(IRenderer r)
        {
            base.AddRenderer(r);
        }

        double IModel.GetBaseHeight()
        {
            return base.GetBaseHeight();
        }

        string IModel.GetName(UObject o)
        {
            return base.GetName(o);
        }

        bool IModel.GetPosition(UObject o, out Position pos)
        {
            return base.GetPosition(o, out pos);
        }

        void IModel.RemoveRenderer(IRenderer r)
        {
            base.RemoveRenderer(r);
        }

        bool IModel.RequestBlendRender()
        {
            return base.RequestBlendRender();
        }

        void IModel.StartRender(D3DRenderer r)
        {
            base.StartRender(r);
        }

        void IModel.StopRender(D3DRenderer r)
        {
            base.StopRender(r);
        }

        IDisposable IModel.TakeSceneLock()
        {
            return base.TakeSceneLock();
        }

        void IDisposable.Dispose()
        {
            base.Dispose();
        }


The following members need you to do something with the mappings of your additional objects. Remember to use the local synchonisation object for multi-thread accesses.

GetKey returns the representation key for the given object:


        Key IModel.GetKey(UObject o)
        {
            lock (sync)
            {
                if (map_obj_key.ContainsKey(o))
                {
                    return map_obj_key[o];
                }
                else
                {
                    return base.GetKey(o);
                }
            }
        }


GetRep returns the representation for the given key:


        Rep IModel.GetRep(Key key, View view)
        {
            lock (sync)
            {
                if (map_key_rep.ContainsKey(key))
                {
                    return map_key_rep[key];
                }
                else
                {
                    return base.GetRep(key, view);
                }
            }
        }



RenderEachObject calls the IRenderer.OnRender method on each located object. The context is 0 for the opaque pass, 1 for the transparent pass and 2 for a hit calculation pass.
This is where we need to call OnRender on each of our objects to draw them on the map. Clicking on the map causes hit calculations (context==2). Here, we will not render zones for hit tests so the map can be dragged with the mouse even if the user clicks in a zone. Also, we will not render objects from outside our map model for hit tests, since this example doesn't do anything with these hits.


        void IModel.RenderEachObject(IRenderer r, int context)
        {
            lock (sync)
            {
                foreach (IRenderer renderer in Renderers)
                {
                    foreach (UObject o in map_obj_key.Keys)
                    {
                        // Don't render zones for hit tests so the map
                        // can be dragged with the mouse even if you
                        // click in a zone.
                        if (!((context == 2) &&
                              (map_obj_type[o] == MapModelObjectType.Zone)))
                        {
                            renderer.OnRender(context, o, new Position());
                        }
                    }
                }
                if (context != 2)
                {
                    base.RenderEachObject(r, context);
                }
            }
        }





Using MyMapModel in your Windows Form

Create the map model and apply it to your visualization

Create an instance of MyMapModel. This will need to be visible throughout the Windows Form class so you can call the public methods we defined above when, for example, sensors are added or removed to/from your dataset.


        MyMapModel my_map_model = new MyMapModel();


Assign the instance of MyMapModel in the Windows Form load handler:


        visualization.Model = my_map_model;



Finding objects pointed to by the mouse

In your Windows Form code, define the following method to detect whether the mouse is over an object. This uses the hit test from RenderEachObject, so in this example, we will detect sensors but not zones.


        // Method to return the object that the mouse is over, if any.
        private bool mouse_is_over_object(out UObject obj)
        {
            // Get the System.Drawing.Point that the mouse
            // is over within the visualization area.
            Point p = visualization.PointToClient(
                new Point(MousePosition.X, MousePosition.Y));

            // Get the first hit found.
            foreach (KeyValuePair<UObject, Vector3D> kvp in
                visualization.Renderer.GetHits(p.X, p.Y))
            {
                obj = kvp.Key;
                return true;
            }
            obj = new UObject();

            return false;
        }





Now look at the example application


You are now ready to use your map model. The example program populates the map with all sensors and cell extents found when it loads. To show you how you might use the mouse_is_over_object method defined above, it displays the MAC address of any sensor that the mouse is over on the map. It also has a "View" menu to demonstrate the methods to remove objects from the map.





Back to top