Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
88ad39e
Basics
dannyleech May 13, 2026
a9fdc7d
Touch done and rubberbanding fixes
dannyleech May 14, 2026
f7b9017
Merge main
dannyleech May 14, 2026
a286af8
Touch and keyboard fixes plus styling
dannyleech May 14, 2026
1c216d3
Keyboard rubberband fix
dannyleech May 15, 2026
760df09
Merge conflict resolution
dannyleech May 15, 2026
001dbdd
Sketch marker removal
dannyleech May 15, 2026
ef8e905
Interface change fixs
dannyleech May 15, 2026
fee7f1c
Offset target size chnage basics
dannyleech May 15, 2026
09e9cce
Map size offset marker fix
dannyleech May 18, 2026
e384439
Minor touch/keyboard draw fixes
dannyleech May 18, 2026
ea58952
Medium size active vertext marker fix
dannyleech May 18, 2026
a431208
Light and dark colour support added
dannyleech May 18, 2026
4d3698e
Undo delete vertex bug fix
dannyleech May 18, 2026
6b17b5a
Snap basics added
dannyleech May 18, 2026
4e6f080
Closing coord related bug fixes
dannyleech May 18, 2026
46462d6
Colour props refactored
dannyleech May 18, 2026
b1976f8
Default prop order amend
dannyleech May 18, 2026
3f324f2
Default colours refactor
dannyleech May 19, 2026
3c0f8d0
Code refactoring
dannyleech May 19, 2026
8a27311
Mouse wheel ruberband fix
dannyleech May 19, 2026
e59c74e
Keyboard and touch finish drawing fix
dannyleech May 19, 2026
aed6ef8
Function complexity reduced
dannyleech May 19, 2026
7ea7b7b
Drag vertex delete fix
dannyleech May 19, 2026
f41ad16
Minor consistentcy fix
dannyleech May 19, 2026
c7fbd7d
Demo line spacing fix
dannyleech May 19, 2026
060ac28
Test fix
dannyleech May 19, 2026
e49479a
Lint fix
dannyleech May 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion demo/DemoMapMarkerPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ function MapInner () {
map.addPanel(PANEL_ID, {
focus: false,
label: 'Marker',
html: '<p class="govuk-body">Information about the selected marker</p>',
html: '<p class="govuk-body govuk-!-margin-bottom-0">Information about the selected marker</p>',
mobile: { slot: 'drawer', dismissible: true },
tablet: { slot: 'left-top', dismissible: true, width: '280px' },
desktop: { slot: 'left-top', dismissible: true, width: '280px' }
Expand Down
16 changes: 16 additions & 0 deletions demo/draw-ol.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Draw tools demo (OpenLayers)</title>
<link href="/assets/govuk-frontend.min.css" rel="stylesheet" media="all">
<link href="./index.css" rel="stylesheet" media="all">
</head>
<body style="padding: 20px">
<script>document.body.classList.add('im-is-loading')</script>
<h1>Draw tools demo (OpenLayers)</h1>
<div id="map"></div>
<script src="./draw-ol.js"></script>
</body>
</html>
154 changes: 154 additions & 0 deletions demo/js/draw-ol.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// InteractiveMap with OpenLayers provider and draw-ol plugin
import InteractiveMap from '../../src/index.js'
import { vtsMapStyles27700, ngdMapStyles27700 } from './mapStyles.js'
import { transformGeocodeRequest, transformVtsRequest27700 } from './auth.js'
// Providers
import openLayersProvider from '/providers/beta/openlayers/src/index.js'
import openNamesProvider from '/providers/beta/open-names/src/index.js'
// Plugins
import mapStylesPlugin from '/plugins/beta/map-styles/src/index.js'
import createDrawPlugin from '/plugins/beta/draw-ol/src/index.js'
import searchPlugin from '/plugins/search/src/index.js'

const drawPlugin = createDrawPlugin({
snapLayers: ['OS/TopographicArea_1/Agricultural Land', 'OS/TopographicLine/Building Outline']
})

const interactiveMap = new InteractiveMap('map', {
behaviour: 'hybrid',
mapProvider: openLayersProvider({
zoomAlignment: 'world'
}),
reverseGeocodeProvider: openNamesProvider({
url: process.env.OS_NEAREST_URL,
transformRequest: transformGeocodeRequest
}),
mapLabel: 'Map showing Carlisle (OpenLayers)',
minZoom: 6,
maxZoom: 22,
autoColorScheme: true,
center: [337584, 504538],
zoom: 14,
containerHeight: '650px',
transformRequest: transformVtsRequest27700,
enableZoomControls: true,
// readMapText: true,
plugins: [
mapStylesPlugin({
mapStyles: vtsMapStyles27700 // ngdMapStyles27700
}),
searchPlugin({
transformRequest: transformGeocodeRequest,
osNamesURL: process.env.OS_NAMES_URL,
width: '300px',
showMarker: false,
}),
drawPlugin
]
})

interactiveMap.on('app:ready', function (e) {
// app ready
})

interactiveMap.on('map:ready', function (e) {
interactiveMap.addButton('geometryActions', {
label: 'Draw tools',
mobile: { slot: 'bottom-right', order: 3 },
tablet: { slot: 'top-middle', order: 3 },
desktop: { slot: 'top-middle', order: 3 },
menuItems: [{
id: 'drawPolygon',
label: 'Draw polygon',
iconSvgContent: '<path d="M19.5 7v10M4.5 7v10M7 19.5h10M7 4.5h10"/><path d="M22 18v3a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1zm0-15v3a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1zM7 18v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1zM7 3v3a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1z"/>',
onClick: function (e) {
interactiveMap.toggleButtonState('geometryActions', 'hidden', true)
drawPlugin.newPolygon(crypto.randomUUID(), {
stroke: '#e6c700',
fill: 'rgba(255, 221, 0, 0.1)'
})
}
},{
id: 'drawLine',
label: 'Draw line',
iconSvgContent: '<path d="M5.706 16.294L16.294 5.706"/><path d="M21 2v3c0 .549-.451 1-1 1h-3c-.549 0-1-.451-1-1V2c0-.549.451-1 1-1h3c.549 0 1 .451 1 1zM6 17v3c0 .549-.451 1-1 1H2c-.549 0-1-.451-1-1v-3c0-.549.451-1 1-1h3c.549 0 1 .451 1 1z"/>',
onClick: function (e) {
interactiveMap.toggleButtonState('geometryActions', 'hidden', true)
drawPlugin.newLine(crypto.randomUUID(), {
stroke: { outdoor: '#99704a', dark: '#ffffff' },
strokeWidth: 6
})
}
},{
id: 'editFeature',
label: 'Edit feature',
iconSvgContent: '<path d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"/><path d="m15 5 4 4"/>',
isDisabled: true,
onClick: function (e) {
const editSuccess = drawPlugin.editFeature(selectedFeatureIds[0])
if (!editSuccess) {
return
}
interactiveMap.toggleButtonState('geometryActions', 'hidden', true)
}
},{
id: 'deleteFeature',
label: 'Delete feature',
iconSvgContent: '<path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>',
isDisabled: true,
onClick: function (e) {
interactiveMap.toggleButtonState('geometryActions', 'hidden', false)
drawPlugin.deleteFeature(selectedFeatureIds)
interactiveMap.toggleButtonState('drawPolygon', 'disabled', false)
interactiveMap.toggleButtonState('drawLine', 'disabled', false)
interactiveMap.toggleButtonState('editFeature', 'disabled', true)
interactiveMap.toggleButtonState('deleteFeature', 'disabled', true)
}
}]
})
})

interactiveMap.on('datasets:ready', function () {
// datasets ready
})

let selectedFeatureIds = []

interactiveMap.on('draw:ready', function () {
drawPlugin.addFeature({
id: 'test1234',
type: 'Feature',
geometry: {'type':'Polygon','coordinates':[[[337612,504612],[337592,504595],[337575,504583],[337570,504582],[337560,504582],[337554,504590],[337559,504596],[337568,504604],[337572,504610],[337582,504611],[337585,504610],[337602,504612],[337603,504607],[337605,504605],[337609,504605],[337612,504612]],[[337598,504609],[337587,504605],[337577,504605],[337572,504607],[337573,504610],[337575,504613],[337580,504613],[337586,504612],[337593,504613],[337597,504611],[337598,504609]]]},
stroke: 'rgba(0,112,60,1)',
fill: 'rgba(0,112,60,0.2)',
strokeWidth: 2
})
drawPlugin.editFeature('test1234')
})

interactiveMap.on('draw:started', function (e) {
interactiveMap.toggleButtonState('geometryActions', 'hidden', true)
})

interactiveMap.on('draw:editstart', function (e) {
interactiveMap.toggleButtonState('geometryActions', 'hidden', true)
})

interactiveMap.on('draw:created', function (e) {
console.log('draw:created', e)
interactiveMap.toggleButtonState('geometryActions', 'hidden', false)
})

interactiveMap.on('draw:updated', function (e) {
console.log('draw:updated', e)
})

interactiveMap.on('draw:edited', function (e) {
console.log('draw:edited', e)
interactiveMap.toggleButtonState('geometryActions', 'hidden', false)
})

interactiveMap.on('draw:cancelled', function (e) {
console.log('draw:cancelled', e)
interactiveMap.toggleButtonState('geometryActions', 'hidden', false)
})
5 changes: 5 additions & 0 deletions demo/js/mapStyles.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const vtsMapStyles27700 = [{
id: 'outdoor',
label: 'Outdoor',
url: process.env.VTS_OUTDOOR_URL_27700,
renderMode: 'vector',
thumbnail: OUTDOOR_THUMBNAIL,
logo: OS_LOGO,
logoAltText: OS_LOGO_ALT,
Expand All @@ -97,6 +98,7 @@ const vtsMapStyles27700 = [{
id: 'dark',
label: 'Dark',
url: process.env.VTS_DARK_URL_27700,
renderMode: 'vector',
mapColorScheme: 'dark',
appColorScheme: 'dark',
thumbnail: DARK_THUMBNAIL,
Expand All @@ -107,6 +109,7 @@ const vtsMapStyles27700 = [{
id: BW_ID,
label: BW_LABEL,
url: process.env.VTS_BLACK_AND_WHITE_URL_27700,
renderMode: 'vector',
thumbnail: BW_THUMBNAIL,
logo: OS_LOGO_BLACK,
logoAltText: OS_LOGO_ALT,
Expand All @@ -117,6 +120,7 @@ const ngdMapStyles27700 = [{
id: 'outdoor',
label: 'Outdoor',
type: 'ogc-vt',
renderMode: 'vector',
url: `${process.env.NGD_OUTDOOR_URL_27700}?key=${process.env.OS_CLIENT_ID}`,
thumbnail: OUTDOOR_THUMBNAIL,
logo: OS_LOGO,
Expand All @@ -129,6 +133,7 @@ const ngdMapStyles27700 = [{
id: BW_ID,
label: BW_LABEL,
type: 'ogc-vt',
renderMode: 'vector',
url: `${process.env.NGD_BLACK_AND_WHITE_URL_27700}?key=${process.env.OS_CLIENT_ID}`,
thumbnail: BW_THUMBNAIL,
logo: OS_LOGO_BLACK,
Expand Down
2 changes: 1 addition & 1 deletion demo/js/planning-ol.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const interactiveMap = new InteractiveMap('map', {
hasExitButton: true,
plugins: [
mapStylesPlugin({
mapStyles: ngdMapStyles27700,
mapStyles: vtsMapStyles27700, // ngdMapStyles27700,
manifest: {
buttons: [{ id: 'mapStyles', desktop: { slot: 'right-top', showLabel: false } }],
panels: [{ id: 'mapStyles', desktop: { slot: 'map-styles-button', width: '400px', modal: true } }]
Expand Down
28 changes: 14 additions & 14 deletions plugins/beta/draw-ml/src/defaults.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
export const DEFAULTS = {
editColorsForeground: { light: 'rgba(29,112,184,1)', dark: '#ffffff' },
editColorsBackground: { light: '#ffffff', dark: 'rgba(11,12,12,1)' },
editColorsHalo: { light: 'rgba(11,12,12,1)', dark: '#ffffff' },
splitInvalidColors: { light: 'rgba(29,112,184,1)', dark: 'rgba(29,112,184,1)' },
splitValidColors: { light: 'rgba(29,112,184,1)', dark: 'rgba(29,112,184,1)' },
stroke: 'rgba(212,53,28,1)',
editStroke: { light: 'rgba(29,112,184,1)', dark: '#ffffff' },
editVertex: { light: 'rgba(29,112,184,1)', dark: '#ffffff' },
editMidpoint: { light: 'rgba(29,112,184,1)', dark: '#ffffff' },
editHalo: { light: '#ffffff', dark: 'rgba(11,12,12,1)' },
editActive: { light: 'rgba(11,12,12,1)', dark: '#ffffff' },
splitInvalid: { light: 'rgba(29,112,184,1)', dark: 'rgba(29,112,184,1)' },
splitValid: { light: 'rgba(29,112,184,1)', dark: 'rgba(29,112,184,1)' },
shapeStroke: 'rgba(212,53,28,1)',
shapeFill: 'rgba(212,53,28,0.1)',
strokeWidth: 2,
fill: 'rgba(212,53,28,0.1)',
snapLayers: [],
snapColors: {
vertex: 'rgba(212,53,28,1)',
midpoint: 'rgba(40,161,151,1)',
edge: 'rgba(29,112,184,1)'
},
snapRadius: 10
snapVertex: 'rgba(212,53,28,1)',
snapMidpoint: 'rgba(40,161,151,1)',
snapEdge: 'rgba(29,112,184,1)',
snapRadius: 10,
snapLayers: []
}
5 changes: 5 additions & 0 deletions plugins/beta/draw-ml/src/mapboxDraw.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DrawLineMode } from './modes/drawLineMode.js'
import { createDrawStyles, updateDrawStyles } from './styles.js'
import { initMapLibreSnap } from './mapboxSnap.js'
import { createUndoStack } from './undoStack.js'
import { applyTouchVertexColors } from './modes/editVertex/touchHandlers.js'

/**
* Creates and manages a MapLibre/Mapbox Draw control instance configured for polygon editing.
Expand Down Expand Up @@ -90,6 +91,7 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap

// We need a reference to this
mapProvider.draw = draw
map._drawCurrentMapStyle = mapStyle
// Initialize snap as disabled (matches initialState.snap = false)
mapProvider.snapEnabled = false
// Initialize undo stack (also stored on map for mode access)
Expand All @@ -107,8 +109,11 @@ export const createMapboxDraw = ({ mapStyle, mapProvider, events, eventBus, snap

// --- Update colour scheme ---
const handleSetMapStyle = (e) => {
map._drawCurrentMapStyle = e
map.once('idle', () => {
updateDrawStyles(map, e)
const svg = map._drawEditContainer?.querySelector('[data-touch-vertex-target]')
applyTouchVertexColors(svg, e)
})
}
eventBus.on(events.MAP_SET_STYLE, handleSetMapStyle)
Expand Down
2 changes: 1 addition & 1 deletion plugins/beta/draw-ml/src/mapboxSnap.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ export function initMapLibreSnap (map, draw, snapOptions = {}) {
} = snapOptions

// Apply global patches to MapboxSnap prototype
applyMapboxSnapPatches({ ...DEFAULTS.snapColors, ...colors })
applyMapboxSnapPatches({ vertex: DEFAULTS.snapVertex, midpoint: DEFAULTS.snapMidpoint, edge: DEFAULTS.snapEdge, ...colors })

// Clean up old snap instance's source and layer
function cleanupOldSnap () {
Expand Down
12 changes: 10 additions & 2 deletions plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ import { isOnSVG } from './helpers.js'

const touchVertexTarget = `
<svg width='48' height='48' viewBox='0 0 48 48' fill-rule='evenodd' style='display:none;position:absolute;top:50%;left:50%;margin:24px 0 0 -24px' class='touch-vertex-target' data-touch-vertex-target>
<circle cx='24' cy='24' r='24' fill='currentColor'/>
<path d="M37.543 25H34a1 1 0 1 1 0-2h3.629l-.836-.837a1 1 0 0 1 1.414-1.414l2.5 2.501A1 1 0 0 1 41 24a1 1 0 0 1-.487.858l-2.306 2.306a1 1 0 0 1-1.414-1.414l.75-.75zM23 10.414l-.793.793a1 1 0 0 1-1.414-1.414l2.5-2.5C23.481 7.105 23.734 7 24 7s.519.105.707.293l2.5 2.5a1 1 0 0 1-1.414 1.414L25 10.414V14a1 1 0 1 1-2 0v-3.586zM7 24a1 1 0 0 1 .293-.75l2.5-2.501a1 1 0 0 1 1.414 1.414l-.836.837H14a1 1 0 1 1 0 2h-3.543l.75.75a1 1 0 0 1-1.414 1.414l-2.306-2.306A1 1 0 0 1 7 24zm16.293 16.707l-2.5-2.5a1 1 0 0 1 1.414-1.414l.793.793V34a1 1 0 1 1 2 0v3.586l.793-.793a1 1 0 0 1 1.414 1.414l-2.5 2.5c-.188.188-.441.293-.707.293s-.519-.105-.707-.293zM24 20c2.208 0 4 1.792 4 4s-1.792 4-4 4-4-1.792-4-4 1.792-4 4-4z" fill="#fff"/>
<circle cx='24' cy='24' r='24' fill='var(--touch-fill, #000)'/>
<path d="M37.543 25H34a1 1 0 1 1 0-2h3.629l-.836-.837a1 1 0 0 1 1.414-1.414l2.5 2.501A1 1 0 0 1 41 24a1 1 0 0 1-.487.858l-2.306 2.306a1 1 0 0 1-1.414-1.414l.75-.75zM23 10.414l-.793.793a1 1 0 0 1-1.414-1.414l2.5-2.5C23.481 7.105 23.734 7 24 7s.519.105.707.293l2.5 2.5a1 1 0 0 1-1.414 1.414L25 10.414V14a1 1 0 1 1-2 0v-3.586zM7 24a1 1 0 0 1 .293-.75l2.5-2.501a1 1 0 0 1 1.414 1.414l-.836.837H14a1 1 0 1 1 0 2h-3.543l.75.75a1 1 0 0 1-1.414 1.414l-2.306-2.306A1 1 0 0 1 7 24zm16.293 16.707l-2.5-2.5a1 1 0 0 1 1.414-1.414l.793.793V34a1 1 0 1 1 2 0v3.586l.793-.793a1 1 0 0 1 1.414 1.414l-2.5 2.5c-.188.188-.441.293-.707.293s-.519-.105-.707-.293zM24 20c2.208 0 4 1.792 4 4s-1.792 4-4 4-4-1.792-4-4 1.792-4 4-4z" fill='var(--touch-gfx, #fff)'/>
</svg>
`

export const applyTouchVertexColors = (el, mapStyle) => {
if (!el) { return }
const dark = mapStyle?.mapColorScheme === 'dark'
el.style.setProperty('--touch-fill', dark ? '#ffffff' : '#000000')
el.style.setProperty('--touch-gfx', dark ? '#000000' : '#ffffff')
}

export const touchHandlers = {
addTouchVertexTarget (state) {
let el = state.container.querySelector('[data-touch-vertex-target]')
Expand All @@ -20,6 +27,7 @@ export const touchHandlers = {
el = state.container.querySelector('[data-touch-vertex-target]')
}
state.touchVertexTarget = el
applyTouchVertexColors(el, this.map._drawCurrentMapStyle)
},

updateTouchVertexTarget (state, point) {
Expand Down
2 changes: 2 additions & 0 deletions plugins/beta/draw-ml/src/modes/editVertexMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const EditVertexMode = {
}
this._ctx.store.render()
}
this.map._drawEditContainer = options.container
this.addTouchVertexTarget(state)

// Clear any snap indicator when entering edit mode
Expand Down Expand Up @@ -441,6 +442,7 @@ export const EditVertexMode = {
},

onStop (state) {
this.map._drawEditContainer = null
const h = this.handlers
state.container.removeEventListener('pointerdown', h.pointerdown)
state.container.removeEventListener('pointermove', h.pointermove)
Expand Down
5 changes: 4 additions & 1 deletion plugins/beta/draw-ml/src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ const initialState = {
undoStackLength: 0
}

const DRAW_MODES = new Set(['draw_polygon', 'draw_line'])

const setMode = (state, payload) => {
return {
...state,
mode: payload
mode: payload,
numVertecies: DRAW_MODES.has(payload) ? 0 : state.numVertecies
}
}

Expand Down
Loading
Loading