Skip to main content

Display a map

A lot of VIKTOR applications (especially in the infra sector) are strongly related to locations on a map. In those cases it can be helpful to display locations in a map visualization. This guide explains how to add an OpenStreetMap view to a VIKTOR entity. There are 2 different VIKTOR objects to realize this:

  • If you want to build map elements using simple map geometry objects use the MapView object
  • If you want to use exports from external software to generate a geojson format (e.g. GIS), please see if the GeoJSONView is a better fit.

Map view

note

All features in the MapView and GeoJSONView expect coordinates in the WGS system. In case you are using the RD-system you can make use of the RDWGSConverter to obtain the corresponding WGS coordinates:

lat, lon = RDWGSConverter.from_rd_to_wgs((x, y))

MapView

In the MapView map primitives (features) can be used to construct markers, (poly)lines and surfaces on this map, to add user relevant information.

Example implementation

from viktor.views import MapLinefrom viktor.views import MapPointfrom viktor.views import MapResultfrom viktor.views import MapViewclass Controller(ViktorController):    ...    @MapView('Map view', duration_guess=1)    def get_map_view(self, params, **kwargs):        marker = MapPoint(51.99311570849245, 4.385752379894256)        line = MapLine(            MapPoint(51.99311570849245, 4.385752379894256),            MapPoint(52.40912125231122, 5.031738281255681)        )        features = [marker, line]        return MapResult(features)

A map view can be combined with data using a MapAndDataView

Styling the map elements

VIKTOR supports the following arguments for styling the map elements:

  • title: Title of a clickable map feature.
  • description: Description of a clickable map feature. Supports styling with Markdown.
  • color: Specifies the color of the map feature.
  • entity_links: When clicking on the map feature, links towards multiple entities can be shown.
  • icon: Icon to be shown (MapPoint only). See MapPoint for all available icons (>= v12.11.0).
from viktor import Colorfrom viktor.views import MapEntityLinklink_1 = MapEntityLink('First entity', first_entity_id)link_2 = MapEntityLink('Second entity', second_entity_id)MapPoint(51.99311570849245, 4.385752379894256, title='Location', description='I am blue', icon="pin",         color=Color(90, 148, 230), entity_links=[link_1, link_2])

Linking geo-fields to the view

When geo-fields (GeoPointField, GeoPolylineField, etc.) are used in the parametrization of an entity, corresponding geo-objects (GeoPoint, GeoPolyline, etc.) will be directly available in the params. These can easily be used in a MapView, by converting to the corresponding map feature (e.g. GeoPointField -> GeoPoint -> MapPoint).

Assume the following params:

params = {    'geo_point': <GeoPoint>,    'geo_polyline': <GeoPolyline>,    'geo_polygon': <GeoPolygon>}
@MapView(...)def get_map_view(self, params, **kwargs):    features = []    if params.geo_point:        features.append(MapPoint.from_geo_point(params.geo_point))    if params.geo_polyline:        features.append(MapPolyline.from_geo_polyline(params.geo_polyline))    if params.geo_polygon:        features.append(MapPolygon.from_geo_polygon(params.geo_polygon))    return MapResult(features)

Additional styling as explained in the previous section can be added via the class methods as well:

MapPoint.from_geo_point(params.geo_point, color=Color(90, 148, 230))

GeoJSONView

In the GeoJSONView GeoJSON primitives can be used to construct markers, lines and surfaces on this map, to add user relevant information. Another possibility is an export from an existing GIS program which you want to show to the user.

note

GeoJSON is a standard format for encoding geographic data structures such as points, lines and polygons (geojson.org). geojson.io is a good tool for quick experiments with geojson.

Example implementation

from viktor.views import GeoJSONResultfrom viktor.views import GeoJSONViewclass Controller(ViktorController):    ...    @GeoJSONView('GeoJSON view', duration_guess=1)    def get_geojson_view(self, params, **kwargs):        geojson = {          "type": "FeatureCollection",          "features": [            {              "type": "Feature",              "properties": {},              "geometry": {                "type": "Point",                "coordinates": [                  4.385747015476227,                  51.993107450558156                ]              }            }          ]        }        return GeoJSONResult(geojson)

A GeoJSON view can be combined with data using a GeoJSONAndDataView

Styling the map elements - simplestyle-spec GeoJSON properties

GeoJSON defines the geometrical properties of the map elements and not the styling. It does however allow to convey such information under the "properties" key of the respective element. Roughly following the simplestyle-spec, VIKTOR supports the following arguments for styling the map elements:

  • icon (geometry type 'Point' only): icon to be shown (default: "pin"). See MapPoint for all available icons (>= v12.11.0).
  • marker-color: the color of a marker *
  • description: text to show when this item is clicked. Supports styling with Markdown
  • stroke: the color of a line as part of a polygon, polyline, or multigeometry *
  • fill: the color of the interior of a polygon *
* color rules; Colors can be in short form "#ace" or long form "#aaccee", and should contain the "#" prefix. Colors are interpreted the same as in CSS, in \#RRGGBB and \#RGB order

Additional map elements

Apart from geometrical features, the map can be enriched by passing the following VIKTOR specific elements in the MapResult or GeoJSONResult:

...from viktor.views import MapLabelfrom viktor.views import MapLegend@MapView(...)def get_map_view(...):    ...    legend = MapLegend(...)    labels = [        MapLabel(...),        ...    ]    return MapResult(features, labels=labels, legend=legend)  # or GeoJSONResult

Interaction

New in v13.2.0

Map view interaction makes it possible to let the user provide input by selecting map features within a MapView, GeoJSONView, MapAndDataView or GeoJSONAndDataView. Map view interaction can be used with any of the action buttons by setting its interaction argument, specifying the view to interact with, a selection scope (optional), along with other interaction dependent parameters. The button method is triggered after the user finishes the interaction. An InteractionEvent is passed along under the method's event parameter.

If selection is omitted, the user can select from all map features that have identifier set. All group names added to selection must have been pre-defined in the view method return value under the interaction_groups argument. An example of a MapView that defines specific interaction_groups is shown below:

class MyController(ViktorController):    ...    parametrization = Parametrization    @MapView('Map', 2)    def get_map_view(self, params, **kwargs):        point_1 = MapPoint(..., identifier="point1")        point_2 = MapPoint(..., identifier="point2")        line_1 = MapLine(..., identifier="line1")        polygon_1 = MapPolygon(..., identifier="polygon1")        my_interaction_groups = {            'points': [point_1, point_2],  # or point to the identifier: ['point1', 'point2']            'lines': [line_1],  # or point to the identifier: ['line1']        }        return MapResult(..., interaction_groups=my_interaction_groups)

Select

New in v13.2.0

The MapSelectInteraction can be used to allow for the selection of map features on a map view. Beside the view method (obligatory) and selection, the following optional arguments can be set:

  • min_select: specify the minimum number of features a user can select (>= 1)
  • max_select: specify the maximum number of features a user can select (>= min_select)

An example parametrization that enables select-interaction on the above defined MapView can look as follows. Note that the interaction can, but does not have to make use of the pre-defined interaction_groups on the view by means of the selection argument:

from viktor.parametrization import MapSelectInteractionclass Parametrization(ViktorParametrization):    # Select from all map features with `identifier` defined, ignoring any `interaction_groups` on MapResult.    # Min. select = 2, max. select = 3.    button_1 = DownloadButton(..., method='select_on_map',                              interaction=MapSelectInteraction('get_map_view', min_select=2, max_select=3))    # Select from the groups 'points' and 'lines' only ("polygon1" cannot be selected), pre-defined on MapResult.    # Min. select = 1 (default), no maximum.    button_2 = DownloadButton(..., method='select_on_map',                              interaction=MapSelectInteraction('get_map_view', selection=['points', 'lines']))

The event parameter passed along in the button's method has the following attributes:

  • event.type: 'map_select'
  • event.value: List[Union[str, int]] = list of identifiers of selected features
from viktor.views import InteractionEvent  # for type-hinting purposesclass MyController(ViktorController):    ...    def select_on_map(self, params, event: Optional[InteractionEvent], **kwargs):        if event:  # event will never be None if this method is only called by button(s) with interaction            print(f"Triggered '{event.type}' interaction event with the following value: {event.value}")            # do something with selected features in event.value, e.g.            report = create_report_of_selected_features(event.value)            return DownloadResult(report, "my_report.pdf")        else:  # None            print("No interaction event triggered.")

Testing

New in v13.3.0

mock_View decorator for easier testing of view methods

Methods decorated with @MapView, @MapAndDataView, @GeoJSONView, or @GeoJSONAndDataView need to be mocked within the context of (automated) testing.

import unittestfrom viktor.testing import mock_Viewfrom app.my_entity_type.controller import MyEntityTypeControllerclass TestMyEntityTypeController(unittest.TestCase):    @mock_View(MyEntityTypeController)    def test_map_view(self):        params = ...        result = MyEntityTypeController().map_view(params=params)        self.assertEqual(result.features, ...)        self.assertEqual(result.labels, ...)