Modes are another important concept in Terra Draw, they encapsulate specific logic for creating, selecting and rendering Features on the map. Modes can be categorised into three types:
Once added to Terra Draw upon instantiation, they and can be enabled by calling the setMode
method on the Terra Draw instance and providing the Mode Name.
The mode
property, set on all classes extending TerraDrawBaseMode
, is used to identify the Mode by a name when enabling or switching Modes. For example, the TerraDrawPolygonMode
has the Mode Name polygon
.
All Modes have built-in Mode Names, with the exception of TerraDrawRenderMode
which is provided a custom name upon instantiation. For example:
const draw = new TerraDraw({
adapter: new TerraDrawLeafletAdapter({ lib, map }),
modes: [
// Polygon Mode has the built-in name "polygon"
new TerraDrawPolygonMode(),
// Render Modes are given custom names
new TerraDrawRenderMode({
modeName: "ourmodename",
}),
],
});
draw.start();
The Mode Name is also added to the properties
object of the GeoJSON Feature that is created when a Mode is used to draw a feature.
For example, if you draw a polygon using the TerraDrawPolygonMode
the mode
property on the GeoJSON Feature will be set to polygon
like so:
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"mode": "polygon"
},
"geometry": {
"type": "Polygon",
"coordinates": [
[
[-1.82645, 51.17888],
[-1.826208, 51.179025],
[-1.825859, 51.178867],
[-1.82609, 51.178682],
[-1.82645, 51.17888]
]
]
}
}
]
}
Note
When Loading Data into Terra Draw, the mode
property is used to determine which Mode to add the Feature to.
You can swap to any given mode using it's name once we have instantiated and started our Terra Draw instance. For example, let's say we want to start in 'polygon' mode then later on switch to point mode, we could do that like so:
draw.setMode('polygon')
// Later on...
draw.setMode('point')
Modes can have one of four types. Drawing modes are associated with drawing new geometries onto the map; select modes are associated with selecting existing geometries on the map; static mode is a built-in inert mode that just renders the geometries to the map, and a render mode is a 'view only' mode that is just for rendering geometries to the map.
export enum ModeTypes {
Drawing = "drawing",
Select = "select",
Static = "static",
Render = "render",
}
Note
Currently, you may only have one select
mode instantiated in anyone Terra Draw instance.
Terra Draw comes with the following built-in Drawing Modes out of the box:
Mode | Class | Name |
---|---|---|
Circle | TerraDrawCircleMode | circle |
Freehand | TerraDrawFreehandMode | freehand |
Line | TerraDrawLineStringMode | linestring |
Point | TerraDrawPointMode | point |
Polygon | TerraDrawPolygonMode | polygon |
Rectangle | TerraDrawRectangleMode | rectangle |
Angled Rectangle | TerraDrawRectangleMode | angled-rectangle |
Sector | TerraDrawSector | sector |
All built in drawing modes have a base level of validation that runs when a feature is added programmatically i.e. addFeatures (see the store guide for more details). This attempts to prohibit adding corrupt or invalid data to the mode. Terra Draw works on the assumption that features created on the mode are correct to the validation built-in standard. As an end developer we can also take this a step further, by using the validation
property available on all built in modes. validation
simply takes a function that returns true
if the Feature is valid or false
if it is not. You can write any logic you require to validate the geometry. For example, let's say we wanted to ensure all drawn polygons did not self intersect, we could something like this:
polygonMode = new TerraDrawPolygonMode({
validation: (feature, { updateType }) => {
if (updateType === "finish" || updateType === "commit") {
return ValidateNotSelfIntersecting(feature);
}
return true
}
});
This would stop the user from being able to create a polygon that is self intersecting. Here we use ValidateNotSelfIntersecting
which is exposed as a Validation from Terra Draw. At the moment Terra Draw exposes 3 built in validations:
ValidateMinAreaSquareMeters
- Ensures that a draw Polygon is a minimum size in square metersValidateMaxAreaSquareMeters
- Ensures that a draw Polygon is a maximum size in square metersValidateNotSelfIntersecting
- Ensures that a draw Polygon or LineString is does not self intersect
You can combine these validations if you so wish. Any validation
function will be run as part of validateFeature
which is called by addFeatures
on the public Terra Draw API/.
You'll notice there are two arguments to the validation
function, the first being the feature, the second being the context object. This has useful properties which can help with peforming the validation. One of the most useful properties is the updateType
property which tells you what type of update the feature is receiving, where the options are finish
, commit
or provisional
.
finish
- when the drawing of the feature is being finishedcommit
- when a coordinate has been added or removed from the featureprovisonal
- when the geometry has been update, but the coordinate has not been fully committed to the geometry
Using these can help you write more customised behaviours, for example you may only want to run the validation when the update is a finish
or commit
type, ensuring that validation is not prematurely preventing user interactions to update the feature.
As we move forward Terra Draw will work on supporting Web Mercator maps out the box with the ability to support Globes (i.e. 3D spherical representations of the earth with no projection) as a secondary option. This is made slightly more complicated by the fact we know sometimes users want to draw geodesic geometries on a web mercator map, for example a geodesic circle or a great circle line. In future we will better align by assuming developers want web mercator first behaviours, with secondary support for globes via the projection
property for built in modes.
- Circle mode currently supports both web mercator and geodesic circles, using the
projection
property, which can beglobe
orweb-mercator
(default isweb-mercator
) - Select mode currently supports both web mercator and geodesic editing (scaling, rotating), although resizeable property currently only supports
web-mercator
asprojection
(default isweb-mercator
)
Note: If you want to draw great circle lines on a web mercator map, this is possible. Historically there was a specific mode called 'TerraGreatCircleMode' however this was deprecated in favour of supporting it directly in TerraDrawLineStringMode
. You can achieve the same effect, by using the projection
property and setting it to globe
and using the insertCoordinates
property in conjunction with it, like so:
new TerraDrawLineStringMode({
projection: 'globe',
insertCoordinates: {
strategy: 'amount',
value: 10
}
}),
All modes work with keyboard and mouse interface.
There are varying degrees of support for touch devices. Currently on touch devices Select, Point, Line and Polygon Modes are fully supported. Circle, Rectangle and Angled Rectangle Modes work with the caveat with the UX is not ideal. Freehand is not currently suported. Currently on terradraw.io Circle, Rectange and Freehand modes are disabled on smaller devices.
If you want to experiment you can use the local development environment, as described at Development
The Selection Mode is used to select Features that have been drawn on the map.
Mode | Class | Name |
---|---|---|
Select | TerraDrawSelectMode | select |
The Selection Mode allows for the manipulation of selected Features. The editing of Features is enabled on a per-Mode basis through the flags
property upon instantiation.
For example, to enable editing of Features in the TerraDrawPolygonMode
:
const selectMode = new TerraDrawSelectMode({
flags: {
polygon: {
feature: {
// The entire Feature can be moved
draggable: true,
// Individual coordinates that make up the Feature...
coordinates: {
// Can be added
midpoints: true,
// Can be moved
draggable: true,
// Allow resizing of the geometry from a given origin.
// center will allow resizing of the aspect ratio from the center
// and opposite allows resizing from the opposite corner of the
// bounding box of the geometry.
resizeable: 'center', // can also be 'opposite', 'center-fixed', 'opposite-fixed'
// Can be deleted
deletable: true,
// Provide a custom validation that will run when we attempt to edit the geometry
validation: (feature, context) => {
// context has the methods project and unproject and be used to go from screen space
// to geographic space and vice versa
// ValidateMinAreaSquareMeters can be imported from Terra Draw
return feature.geometry.type !== "Polygon" && ValidateMinAreaSquareMeters(feature.geometry, 1000);
}
},
},
},
},
});
The following code sample shows the different Selection Mode flags available when instantiating the TerraDrawSelectMode
:
new TerraDrawSelectMode({
// Allow manual deselection of features
allowManualDeselection: true, // this defaults to true - allows users to deselect by clicking on the map
// Enable editing tools by Feature
flags: {
// Point
point: {
feature: {
draggable: true,
},
},
// Polygon
polygon: {
feature: {
draggable: true,
coordinates: {
midpoints: true,
draggable: true,
deletable: true,
},
},
},
// Line
linestring: {
feature: {
draggable: true,
coordinates: {
midpoints: true,
draggable: true,
deletable: true,
},
},
},
// Freehand
freehand: {
feature: {
draggable: true,
coordinates: {
midpoints: true,
draggable: true,
deletable: true,
},
},
},
// Circle
circle: {
feature: {
draggable: true,
coordinates: {
midpoints: true,
draggable: true,
deletable: true,
},
},
},
// Rectangle
rectangle: {
feature: {
draggable: true,
coordinates: {
midpoints: true,
draggable: true,
deletable: true,
},
},
},
},
// Styles go here...
styles: {
// See Styling Guide for more information
},
});
Note
It is possible to create and use your own selection mode if you so wish. You do not have to use the built in select mode (TerraDrawSelectMode
).
You can get selected features from the selection mode in one of two ways. The first is to listen for the select
event:
draw.on('select', (id: string) => {
const snapshot = draw.getSnapshot()
// Search the snapshot for the selected polygon
const polygon = snapshot.find((feature) => feature.id === id)
})
Alternatively, if you need access to the specific mouse/map event alongside the selected geometry you can achieve this in almost all map libraries by creating an event on the map itself object itself like so:
// Register an on click event onto the map itself
map.on('click', (event) => {
const snapshot = draw.getSnapshot()
// Search the snapshot for the selected polygon
const polygon = snapshot.find((feature) => feature.properties.selected === true && feature.geometry.type === 'Polygon')
// If there is not a selected polygon, don't do anything
if (!polygon) {
return
}
// Else create or update the popup location to be the cursor position!
if (popup) {
popup.setLngLat([event.lngLat.lng, event.lngLat.lat])
} else {
popup = new maplibregl.Popup({ closeOnClick: false })
.setLngLat([event.lngLat.lng, event.lngLat.lat])
.setHTML('<h1>Example Popup</h1>')
.addTo(map);
}
})
It is possible to select and deselect a feature via the draw instance, which uses the provided select mode under the hood. Here is an example of how you can use the draw instance methods to perform selection and deselection.
const draw = new TerraDraw({
adapter,
modes: [
new TerraDrawPointMode(),
new TerraDrawSelectMode({
flags: {
point: {
feature: { draggable: true },
},
},
}),
],
});
draw.start();
// Add feature programmatically
draw.addFeatures([
{
id: "f8e5a38d-ecfa-4294-8461-d9cff0e0d7f8",
type: "Feature",
geometry: {
type: "Point",
coordinates: [-25.431289673, 34.355907891],
},
properties: {
mode: "point",
},
},
]);
// Select a given feature
draw.selectFeature("f8e5a38d-ecfa-4294-8461-d9cff0e0d7f8");
// Deslect the given feature
draw.deselectFeature("f8e5a38d-ecfa-4294-8461-d9cff0e0d7f8");
The Render Mode is used to render Features that have been drawn on the map, but are not editable.
Mode | Class | Name |
---|---|---|
Render | TerraDrawRenderMode | Unique name provided on instantiation through the modeName property |
Render Modes are instantiated like so:
const renderMode = new TerraDrawRenderMode({
// Unique Mode Name used to identify the Mode (required)
modeName: "ourmodename",
});
Tip
Multiple Render Modes can be added to the Terra Draw instance. This allows you to style individual Render Modes differently. See the Styling guide for more information.
Modes are added to Terra Draw upon instantiation through the modes
property like so:
const draw = new TerraDraw({
adapter: new TerraDrawLeafletAdapter({ lib, map }),
modes: [
// Polygon Mode has the built-in name "polygon"
new TerraDrawPolygonMode(),
// Freehand Mode has the built-in name "freehand"
new TerraDrawFreehandMode(),
],
});
draw.start();
Once added, Modes can be enabled by calling the setMode
method on the Terra Draw instance and providing the Mode Name:
// Once we have started Terra Draw
draw.start();
// Enable the TerraDrawPolygonMode
draw.setMode("polygon");
// Switch to the TerraDrawFreehandMode
draw.setMode("freehand");
Tip
Render Modes are enabled by default and do not need to be enabled using the setMode
method.
For guidance around adding pre-existing data to a specific mode, please see the Store guide on adding features.
See the Development guide for more information on creating custom Modes.
Guides