@@ -28,6 +28,31 @@ function eastingNorthingToLatLong({ easting, northing }) {
2828 return { lat : latLong . latitude , long : latLong . longitude }
2929}
3030
31+ /**
32+ * Converts lat long to an ordnance survey grid reference
33+ * @param {object } param
34+ * @param {number } param.lat
35+ * @param {number } param.long
36+ * @returns {string }
37+ */
38+ function latLongToOsGridRef ( { lat, long } ) {
39+ const point = new LatLon ( lat , long )
40+
41+ return point . toOsGrid ( ) . toString ( )
42+ }
43+
44+ /**
45+ * Converts an ordnance survey grid reference to lat long
46+ * @param {string } osGridRef
47+ * @returns {{ lat: number, long: number } }
48+ */
49+ function osGridRefToLatLong ( osGridRef ) {
50+ const point = OsGridRef . parse ( osGridRef )
51+ const latLong = point . toLatLon ( )
52+
53+ return { lat : latLong . latitude , long : latLong . longitude }
54+ }
55+
3156// Center of UK
3257const DEFAULT_LAT = 53.825564
3358const DEFAULT_LONG = - 2.421975
@@ -145,7 +170,11 @@ function processLocation(config, location, index) {
145170 const locationType = location . dataset . locationtype
146171
147172 // Check for support
148- const supportedLocations = [ 'latlongfield' , 'eastingnorthingfield' ]
173+ const supportedLocations = [
174+ 'latlongfield' ,
175+ 'eastingnorthingfield' ,
176+ 'osgridreffield'
177+ ]
149178 if ( ! locationType || ! supportedLocations . includes ( locationType ) ) {
150179 return
151180 }
@@ -177,6 +206,9 @@ function processLocation(config, location, index) {
177206 case 'eastingnorthingfield' :
178207 bindEastingNorthingField ( location , map , e . map )
179208 break
209+ case 'osgridreffield' :
210+ bindOsGridRefField ( location , map , e . map )
211+ break
180212 default :
181213 throw new Error ( 'Not implemented' )
182214 }
@@ -302,6 +334,8 @@ function getInitMapConfig(locationField) {
302334 return getInitLatLongMapConfig ( locationField )
303335 case 'eastingnorthingfield' :
304336 return getInitEastingNorthingMapConfig ( locationField )
337+ case 'osgridreffield' :
338+ return getInitOsGridRefMapConfig ( locationField )
305339 default :
306340 throw new Error ( 'Not implemented' )
307341 }
@@ -366,7 +400,29 @@ function validateEastingNorthing(strEasting, strNorthing) {
366400}
367401
368402/**
369- * Gets initial map config for a latlong location field
403+ * Validates OS grid reference is correct
404+ * @param {string } osGridRef - the OsGridRef
405+ * @returns {{ valid: false } | { valid: true, value: string } }
406+ */
407+ function validateOsGridRef ( osGridRef ) {
408+ if ( ! osGridRef ) {
409+ return { valid : false }
410+ }
411+
412+ const pattern =
413+ / ^ ( ( ( [ s S ] | [ n N ] ) [ a - h A - H j - z J - Z ] ) | ( ( [ t T ] | [ o O ] ) [ a b f g l m q r v w A B F G L M Q R V W ] ) | ( [ h H ] [ l - z L - Z ] ) | ( [ j J ] [ l m q r v w L M Q R V W ] ) ) \s ? ( ( [ 0 - 9 ] { 3 } ) \s ? ( [ 0 - 9 ] { 3 } ) | ( [ 0 - 9 ] { 4 } ) \s ? ( [ 0 - 9 ] { 4 } ) | ( [ 0 - 9 ] { 5 } ) \s ? ( [ 0 - 9 ] { 5 } ) ) $ /
414+
415+ const match = pattern . exec ( osGridRef )
416+
417+ if ( match === null ) {
418+ return { valid : false }
419+ }
420+
421+ return { valid : true , value : match [ 0 ] }
422+ }
423+
424+ /**
425+ * Gets the inputs for a latlong location field
370426 * @param {HTMLDivElement } locationField - the latlong location field element
371427 */
372428function getLatLongInputs ( locationField ) {
@@ -383,7 +439,7 @@ function getLatLongInputs(locationField) {
383439}
384440
385441/**
386- * Gets initial map config for a easting/northing location field
442+ * Gets the inputs for a easting/northing location field
387443 * @param {HTMLDivElement } locationField - the eastingnorthing location field element
388444 */
389445function getEastingNorthingInputs ( locationField ) {
@@ -399,6 +455,20 @@ function getEastingNorthingInputs(locationField) {
399455 return { eastingInput, northingInput }
400456}
401457
458+ /**
459+ * Gets the input for a OS grid reference location field
460+ * @param {HTMLDivElement } locationField - the osgridref location field element
461+ */
462+ function getOsGridRefInput ( locationField ) {
463+ const input = locationField . querySelector ( 'input.govuk-input' )
464+
465+ if ( input === null ) {
466+ throw new Error ( 'Expected 1 input for osgridref' )
467+ }
468+
469+ return /** @type {HTMLInputElement } */ ( input )
470+ }
471+
402472/**
403473 * Gets initial map config for a latlong location field
404474 * @param {HTMLDivElement } locationField - the latlong location field element
@@ -461,6 +531,36 @@ function getInitEastingNorthingMapConfig(locationField) {
461531 }
462532}
463533
534+ /**
535+ * Gets initial map config for an OS grid reference location field
536+ * @param {HTMLDivElement } locationField - the osgridref location field element
537+ * @returns {DefraMapInitConfig | undefined }
538+ */
539+ function getInitOsGridRefMapConfig ( locationField ) {
540+ const osGridRefInput = getOsGridRefInput ( locationField )
541+ const result = validateOsGridRef ( osGridRefInput . value )
542+
543+ if ( ! result . valid ) {
544+ return undefined
545+ }
546+
547+ const latlong = osGridRefToLatLong ( result . value )
548+
549+ /** @type {MapCenter } */
550+ const center = [ latlong . long , latlong . lat ]
551+
552+ return {
553+ zoom : '16' ,
554+ center,
555+ markers : [
556+ {
557+ id : 'location' ,
558+ coords : center
559+ }
560+ ]
561+ }
562+ }
563+
464564/**
465565 * Bind a latlong field to the map
466566 * @param {HTMLDivElement } locationField - the latlong location field
@@ -572,6 +672,60 @@ function bindEastingNorthingField(locationField, map, mapProvider) {
572672 northingInput . addEventListener ( 'change' , onUpdateInputs , false )
573673}
574674
675+ /**
676+ * Bind an OS grid reference field to the map
677+ * @param {HTMLDivElement } locationField - the osgridref location field
678+ * @param {DefraMap } map - the map component instance (of DefraMap)
679+ * @param {MapLibreMap } mapProvider - the map provider instance (of MapLibreMap)
680+ */
681+ function bindOsGridRefField ( locationField , map , mapProvider ) {
682+ const osGridRefInput = getOsGridRefInput ( locationField )
683+
684+ map . on (
685+ 'interact:markerchange' ,
686+ /**
687+ * Callback function which fires when the map marker changes
688+ * @param {object } e - the event
689+ * @param {[number, number] } e.coords - the map marker coordinates
690+ */
691+ function onInteractMarkerChange ( e ) {
692+ const point = latLongToOsGridRef ( {
693+ lat : e . coords [ 1 ] ,
694+ long : e . coords [ 0 ]
695+ } )
696+
697+ osGridRefInput . value = point
698+ }
699+ )
700+
701+ /**
702+ * OS grid reference input change event listener
703+ * Update the map view location when the input is changed
704+ */
705+ function onUpdateInput ( ) {
706+ const result = validateOsGridRef ( osGridRefInput . value )
707+
708+ if ( result . valid ) {
709+ const latlong = osGridRefToLatLong ( result . value )
710+
711+ /** @type {MapCenter } */
712+ const center = [ latlong . long , latlong . lat ]
713+
714+ // Move the 'location' marker to the new point
715+ map . addMarker ( 'location' , center )
716+
717+ // Pan & zoom the map to the new valid location
718+ mapProvider . flyTo ( {
719+ center,
720+ zoom : 14 ,
721+ essential : true
722+ } )
723+ }
724+ }
725+
726+ osGridRefInput . addEventListener ( 'change' , onUpdateInput , false )
727+ }
728+
575729/**
576730 * @typedef {object } DefraMap - an instance of a DefraMap
577731 * @property {Function } on - register callback listeners to map events
0 commit comments