tsc_JobMap

This class interfaces to the Survey Core Map of the current job, displayed by default and available to the user at all times.

Note: This class was previously used to interface to the old 2D map, and has now been connected up to the new 3D map. It doesn't behave quite the same as it did and may have some issues. Please bear this in mind when using tsc_JobMap.

Unlike most SCAPI interfaces, tsc_JobMap connects to a single global subsystem (the Survey Core Map) which exists for the lifetime of the Trimble Access application.

Because of the global nature of the map and the possibility of user interaction at any time, the tsc_JobMap class must be correspondingly global and always available.  To use this class it must be subclassed and event handler functions overidden as appropriate.  The best place to instantiate the tsc_JobMap subclass is in the plugin's tsc_AppMainMenu subclass.  Note that you will not be able to attach to the map until the OnCreate method is called.

Terminology

Related pages, classes, and enumerations

tsc_JobMap sample code
tsc_JobMapAutopanEventArgs
tsc_JobMapBeforeSelectionPopupEventArgs
tsc_JobMapBounds
tsc_JobMapExtentsEventArgs
tsc_JobMapLayer
tsc_JobMapPaintEventArgs
tsc_JobMapPaintReasons
tsc_JobMapPoint
tsc_JobMapStateEventArgs
tsc_MarqueeMode

Processing Map events

Map events are raised by Survey Core calling an event handler in the tsc_JobMap subclass.  Because these events will prevent the job map from performing any further processing while the are being handled by the plugin, it is important for event handlers to process the event quickly and return.  Performing any action which may cause a wait or UI (such as a form) will prevent the map from continuing and it may become unresponsive.

If it is necessary to perform lengthy operations or interact with the user, then a UITask or a thread should be launched to deal with this.

Most event handlers allow the default action to be prevented.  In general, if the default action has been prevented, then some kind of indication should be given to the user to show that the action has been processed - perhaps by changing the color of an item on the map or drawing it in a different way.

Unlike tsc_Form these event overrides should respond (where appropriate) with true when the map can continue processing as it would normally.  Return false to indicate that the plugin has handled this event.

The map runs within its own thread environment, and event handlers will be called on the map's thread.  This means that an event handler should not interact with a form or other UI objects in any other UiTask without taking due care.  Other objects (such as tsc_Image) are generally thread-safe, since Survey Core will serialize access to them.  Take care with objects shared between the tsc_JobMap event handlers and other event handlers in the plugin, since a thread switch could occur at any point.  Vulnerable objects should be protected using a tsc_Lock, but be careful to avoid locked sections of code that span any kind of wait or long-running operation.

Public Methods

tsc_JobMap();
Constructs the object.  The constructor does very little; the class only becomes active after AttachToMap() is called.
Important: The lifetime of the tsc_JobMap derived class should be about as long as the map which is not easy to determine. As it happens, the tsc_MainMenu object is a good (if slightly strange) place to put your subclass. This requires the plugin to store the main menu pointer when it is created in the tsc_Application::MakeMainMenu() method, so that the job map instance can be accessed. 

void AttachToMap ();
Attaches this instance to the Map of the current job. Subsequently, any time the user interacts with the map within this plugin's application the event handlers in this class instance will be called. The class is only active while the plugin is the current application.  If the user switches to another application such as General Survey or another plugin, that application becomes the controller for the map until such time as the user switches back.  During the time that another application is current, no events (such as OnPaint or OnClick) will be raised.

If a different instance of tsc_JobMap within the same plugin subsequently calls AttachToMap, then the previous caller is detached.  There is no interaction between different plugins; only the current application interacts with the map at a given time, even when multiple plugins are active and attached to the map.

void DetachFromMap ();
Detaches this plugin from the job map.  This method will apply to every subclass instance of tsc_JobMap that called AttachToMap() within the plugin, but does not affect other applications or plugins.  If no instance is attached to the map, this call does nothing.

virtual void OnMapOpened();
Called when the Map window is started.  If you attach to an already open map this event will not occur. When this method was originally added, it was possible for the user to open and close the map window. With the 3D map it is always open, so this event has lost its usefulness.

virtual void OnMapClosed();
Called when the Map is closed. Note this is not called when detaching from the map or when the map window loses focus. With the advent of the always-open 3D map, this event has lost its usefulness.

virtual void RequestMapClose();
Request that the Map UITask be shut down. Since the always-on 3D map, this method has no effect.

tsc_EntityList GetSelections ();
Returns a list of the entities that are currently selected in the map.  This method can be used at any time, and returns the same information as tsc_Database::MapSelections().  If nothing is selected or if no current job is open, an empty list is returned.

bool MapIsOnTop();
Call to see if the Map window is at the front as the active UI Task.

void SetViewport (tsc_JobMapBounds& boundingBox, bool visualMargin = true);
Sets the display region of the map in terms of grid coordinates.  The map will be panned and/or zoomed such that all of the bounding box is displayed in the map window at the highest possible zoom. Any number of points may be added to the bounding box by using its Extend() method.  If only one point is added (i.e. the bounding box is of zero size), then the map will pan the point to the centre but leave the zoom level unchanged. If panning to the current position is required, it is better to override the OnAutopan event handler, which is called for new positions and also allows the autopanning system to be turned off and on by the user, from the map options form.

Prior to version 20.20, visualMargin had a different meaning;  it was called snapScale and controlled whether the scale snapped to a round number.
The visualMargin argument adds a 10% margin around the box (i.e. zoomed-out slightly) to make points at the edges more visible.

Important: The amount of zooming and panning should be minimized by only calling this method when really necessary. Also, calling SetViewport without user-initated interaction can be disconcerting and possibly prevent users from zooming or panning where they wish.

void Repaint ();
Requests a repaint of the map window. This only repaints the SC image layers and does not rebuild them, however the plugin may rebuild any of its own layers that require it.  This method can be called from any thread.  Multiple calls to Repaint in quick succession (that is, with no intervening waits or UI) will only generate one OnPaint event. See OnPaint() below for more information.

Calling Repaint will turn on the tsc_JobMapPaintRequested bit in the reasons passed to the next call to the OnPaint handler, and there is no other way that this bit can be set.

tsc_Image CreateCompatibleImage();
Creates an image suitable for passing into tsc_JobMapPaintEventArgs::SetLayerImage(). If you are drawing on multiple  layers, then create an image for each layer. It is more efficient to reuse images rather than creating new ones on every paint operation. 

Note that CreateCompatibleImage() will create a tsc_Image sized to fit the current application window. This means that if you wish to deal with the tsc_JobMapPaintResize paint reason, you will need to delete your current map image (a tsc_Image) and create a new one, using CreateCompatibleImage() again, to have a correctly-sized image.

 To achieve this you could write something like this (but note this example is incomplete, the other paint reasons would have to be handled also):

// Somewhere accessible:

tsc_Image mapImage;


// In the constructor:

mapImage = CreateCompatibleImage(); // Creates the initial correctly sized image.


// In the ::OnPaint override:

if (reasons & tsc_JobMapPaintResize)

{

  // Creates an image sized according to the resized application window

  mapImage = CreateCompatibleImage(); 

  MyDrawEverything();

}

void OnEntityPopup (tsc_JobMapBeforeSelectionPopupEventArgs& eventArgs);
When the user pops up a menu on the map, typically with a tap-and-hold action, this event handler is called.  This occurs after the popup menu has been built but before it is displayed. Menu items may be added, changed, or deleted, using methods in the eventArgs parameter.  Note that this event is also raised when no selectable item is close to the mouse position (in which case the menu is considerably smaller).  The tsc_JobMap::GetSelections method can be used to obtain a list of selected items, which may be empty if nothing was selected at the time.  The actual position of the tap and hold is not relevant in the map UI.

x_Code OnEntityPopupSelection (tsc_JobMapStateEventArgs& eventArgs, x_Code menuSelection);
This event handler is called when a menu item on the selected entities popup menu has been selected by the user. The GetSelections method may be used to get a list of the items selected on the map to which this menu applies. Respond to the selection in one of the following ways:

If the selection has been handled in some manner by the plugin, return X_NULL to prevent the map from performing any further action.

If an x-code value is returned that is recognised by the map then unexpected results may occur if the currently selected items do not match what the action expects.  For example, X_MenuSubdivideALine should only be returned if a single line is selected.

void InvalidateTitle();
Ask the map to regenerate the window title.

virtual tsc_String OnDrawMapTitle(const tsc_String& suggestedTitle);
Override the map title with text of your own - it is suggested that you keep the word Map as a prefix as this is how the Map UI is named in other parts of SurveyCore (e.g. switch to).

void InvalidateEnterSoftkeyText();
Ask the map to regenerate the enter softkey text.

virtual x_Code OnEnterSoftkeyText (x_Code suggestedEnterKeyText);
This event handler is called to determine the map's Enter softkey x_Code.  Either return the suggested x_Code or an x_Code of your own choice.

Note that the x_Code will not necessarily change the behaviour of the map's default response to the enter key,  you may wish to override OnEnterSoftkey as well...

bool OnEnterSoftkey (tsc_JobMapStateEventArgs& eventArgs, x_Code enterKeyText);
This event handler is called when the map's Enter softkey has been pressed by the user.  The GetSelections method can be used to return a list of the currently selected entities. Respond to the press by one of the following:

The enterKeyText parameter contains the x-code of the text that was shown on the Enter key when it was pressed, and therefore indicates what action the user is expecting from the enter key, and it is unwise to do something different.

bool OnClick (tsc_JobMapStateEventArgs& eventArgs, tsc_JobMapPoint& mousePosition);
This event handler is called when the user has clicked (mouse down and up) anywhere on the map window.  A mouse-up which is part of a marquee drag or panning drag will not generate this event, however the first click of a double-click will.  Respond to the press in one of the following ways:

bool OnEntityDoubleClick (tsc_JobMapStateEventArgs& eventArgs, tsc_Entity entityClicked);
This event handler is called when the user has double-clicked some entity that is displayed on the map.  Typically this performs some default action such as performing stakeout on the entity.  Respond to the event in one of these ways:


void OnEntityPreselection (tsc_JobMapEntityPreselectionEventArgs& eventArgs);
Available in version 24.00 and above.
Use this event handler to examine or modify the selections made by the user in the map. It is called after the selection action has been made by the user, but before any processing has been done by the map.
The eventArgs parameter contains a method to get a list of the selected entities, and another to clear these selections so that the map does not process them. To modify the selections, the plugin should use the current map selections and the newly selected items to form a new selection which is then set by calling the tsc_Database::SetMapSelections() method.
See the bottom of this page for an example.

virtual void OnExtents (tsc_JobMapExtentsEventArgs& eventArgs);
This event handler is called whenever the job map needs to know the outer bounds of all objects displayed on the map. Typically this occurs when the user clicks the Zoom To Extents button, and also when the map is first displayed, and on some other occasions.

The four grid values supplied in the eventArgs contain the extents already computed for the map, and their values may be modified as desired. It is recommended that North and West only be reduced in value, and South and East only be increased.

The zoom is performed when the OnExtents method returns.

Important: The current map state is passed to the plugin inside the eventArgs parameter.  The state reflects the map before the zoom is performed; take care since the coordinate conversion functions will return values that do not match the upcoming zoom.

virtual void OnAutopan (tsc_JobMapAutopanEventArgs& eventArgs);
Available in SC version 2.30 and above.
This event handler is called approximately once per second to adjust the map in response to a changing current position.  The current position, if known, is passed to the handler.  If the map Autopan option has been turned off by the user, the handler will not be called.

The supplied eventArgs parameter contains information and functions that may be used to control the autopan system.  By default the map does not zoom as a result of autopan, however a plugin may control both panning and zooming.

For a full description of how this event handler works, see tsc_JobMapAutopanEventArgs.

Paint event handler

void OnPaint (tsc_JobMapPaintEventArgs& eventArgs, tsc_JobMapPaintReasons reasons);
This event handler is called whenever a window paint occurs, and allows layers to be added to the map display.  A layer is inserted by calling eventArgs.SetLayerImage().   See the tsc_JobMapPaintEventArgs class for details, and also see below.

Important: OnPaint performance implications

Because the OnPaint handler may be called many times per second, it is important to understand how to write an efficient implementation, particularly if it is to run well on any of Trimble's smaller handheld devices.  Also see the map sample plugin.

A map repaint may occur for many different reasons; some may require the complete redrawing of some or all layers, others may cause a small alteration to just one layer.  Some layers (such as points) may take some time - up to several seconds - to completely redraw, and may be drawn in bursts over several continuation repaints. With the improvements in hardware performance since this interface was designed, the idea of continuation repaints is largely obsolete.

A plugin could completely re-create it's layer(s) on every repaint, however this can consume excessive CPU time which is often unnecessary.  An OnPaint handler should test the reasons parameter that has been passed in, and decide whether it should redraw the image from scratch, or just reuse the previously drawn image.  The reasons parameter contains bit-flags, so an AND operation (&) can be used to test for reasons that would cause a complete redraw.  Note that multiple reasons may be present in a single call to OnPaint. There are several reasons that would usually require a redraw:

For example, to test for these conditions one could write:

if (reasons & 

        (tsc_JobMapPaintInitial |

         tsc_JobMapPaintPan     |

         tsc_JobMapPaintZoom ) != 0) 

{

     layerImage.Clear();     // Full redraw of our layerImage required... 

}

else if (reasons & tsc_JobMapPaintResize) != 0) 

{

     layerImage = CreateCompatibleImage(); 

}

SetMapLayer (tsc_JobMapLayerBackdropImage, layerImage);

If some external event - that is within the plugin and the TA map is unaware of - has caused the plugin's layer to become out of date, it should immediately call Repaint().  This will request a repaint of the map - the OnPaint event will be raised with the tsc_JobMapPaintRequested bit set in the reasons parameter.  The OnPaint handler could then recognise tsc_JobMapPaintRequested as a reason to do a redraw of the parts invalidated by the event.

Virtual entities (points, lines, ...)

The TA Map supports the creation of points and lines which do not exist outside of the map. These entities can be created and deleted on-the-fly, and are shown on the map of the current job. These virtual entities only exist while the current job is open; they are not permanent in any way (unless the plugin itself saves and restores them). There is no way to "save" a virtual entity other than by saving the information used to create it, and recreating the entity at a later time.

Virtual entities are saved in the instance of the tsc_JobMap object, and will disappear if that instance is destroyed. Note that only one instance of tsc_JobMap may exist at any one time. Normally, the effects of a plugin app are not shown when the plugin is not active (i.e. the user switches to a different plugin). However virtual entities will remain displayed on the map regardless of which plugin is currently in use.

tsc_Point AddVirtualPoint (
const char* namePrefix,
const tsc_Grid& coordinate,
tsc_Color color,
bool selectable, bool selected);

tsc_Line  AddVirtualLine (
const char* namePrefix,
const tsc_LineDefinition& line,
tsc_Color color,
int thickness,
bool selectable, bool selected);

These methods add a point or a line to the map. Apart from the entity type they function the same.
The returned value is a virtual database object which can be used like any other database entity, for calculations, selections, stakeout and so forth. If the Add call fails (unlikely) then the returned entity's Exists() method will return false.
It is worth remembering that the returned entity is transient and may easily disappear. The Exists() method on the entity can be used to check if it still exists.

The namePrefix parameter is used to name the entity. The supplied string has a sequential 4-digit number appended to ensure its uniqueness.
The coordinate parameter is a grid coordinate for the point in the current job's coordinate system.
The line parameter is a line definition which has a variety of methods of creation. See tsc_LineDefinition.
selectable determines whether the user will be able to select the item on the map.
selected allows the initial selection state of the entity to be set.
color parameter is the color used to display the item on the map.
thickness parameter is the width of the line.

 x_Code ClearVirtualEntities ();
This method clears all entities from this instance of tsc_JobMap. The returned value is X_NULL for success or some descriptive value if it fails.

OnEntityPreselection example

The event handler, tsc_JobMap::OnEntityPreselection() is called after the user's map selections have been determined but before they are processed. The plugin can get the list and modify the current map selections using the existing selection list and the list of those selected by the user. Calling the ClearPreselection() function in the eventArgs prevents the map from processing the pre-selections when the handler returns.

The following sample code shows a handler that only allows one point to be selected at any time, but allows other types as usual.

class MapClass : public tsc_JobMap

{

    void OnEntityPreselection (tsc_JobMapEntityPreselectionEventArgs& eventArgs) override

    {

        tsc_EntityList currentSel = GetSelections();  // The existing selections.

        tsc_Point      lastCurrentPoint;


        tsc_EntityList preSel = eventArgs.GetPreselection();  // The items selected by the user.

        tsc_Point      lastNewPoint;


        tsc_EntityList newSel;   // The new selection list we will set at the end.



        for (int i = 0; i < currentSel.Count(); i++)

        {

            tsc_Entity entity = currentSel[i];

            if (entity.IsPoint())

            {

                lastCurrentPoint = entity;

            }

            else

            {

                // Some other thing already selected, keep it in the new selection (except

                // if it's in the preselection - then it needs to be deselected).

                if (preSel.Find (entity.Name()) < 0)

                {

                    newSel.Append (entity);    

                }

            }

        }


        for (int i = 0; i < preSel.Count(); i++)

        {

            tsc_Entity entity = preSel[i];


            if (entity.IsPoint())

            {

                lastNewPoint = entity;

            }

            else if (currentSel.Find (entity.Name()) < 0)

            {

                newSel.Append (entity);   // Some other entity type was selected. Allow it (Road?)

            }

            // Else it's a line, arc, road, or some other entity type that's already selected.

            // These are already in the new selection.

        }


        if (lastNewPoint.Exists())

        {

            if (currentSel.Find(lastNewPoint.Name()) < 0)   // If being de-selected, just leave it out of newSel.

            {

                newSel.Append (lastNewPoint);        // Select the point chosen by user.

            }

        }

        else if (lastCurrentPoint.Exists())          // No new selected point, keep the old one if we found one.

        {

            newSel.Append (lastCurrentPoint);

        }


        tsc_CurrentJob::Get().Database().SetMapSelections (newSel);  // Make these the new selections.


        eventArgs.ClearPreselection();      // Prevent the map from processing the pre-selections.

    }

};