Skip to content

Latest commit

 

History

History
472 lines (361 loc) · 17.6 KB

4.MODES.md

File metadata and controls

472 lines (361 loc) · 17.6 KB

Modes

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.

Mode Names

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.

Switching Modes

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')

Mode Types

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.

Drawing Modes

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

Validation in Drawing Modes

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 meters
  • ValidateMaxAreaSquareMeters - Ensures that a draw Polygon is a maximum size in square meters
  • ValidateNotSelfIntersecting - 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 finished
  • commit - when a coordinate has been added or removed from the feature
  • provisonal - 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.

Projections in Drawing Modes

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 be globe or web-mercator (default is web-mercator)
  • Select mode currently supports both web mercator and geodesic editing (scaling, rotating), although resizeable property currently only supports web-mercator as projection (default is web-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
			}
		}),

Touch Device Support

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

Selection Mode

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).

Getting Selected Features

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);
	}
})

Selecting and Deselecting Features Programmatically

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");
 

Render Mode

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.

Adding Modes

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();

Enabling Modes

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.

Adding Data to a Mode

For guidance around adding pre-existing data to a specific mode, please see the Store guide on adding features.

Creating Custom Modes

See the Development guide for more information on creating custom Modes.


Guides

  1. Getting Started
  2. Store
  3. Adapters
  4. Modes
  5. Styling
  6. Events
  7. Development
  8. Examples