[GIS] Making Key Navigation Work in Openlayers 4 (ES6)

javascriptopenlayersweb-mappingwms

So this last weekend, I decided to re-write my GIS portal, I'm using the new DotNet Core C# stuff, and the Aurelia (Aurelia.io) web application framework.

Everything works perfectly, except for one thing.

No matter how hard I try I just cannot get Keyboard panning in Open layers 4 to work.

Now just so where on the same page here, I'm NOT using OL in a traditional script tag, I'm doing this the new ES6 (JavaScript 2015/2016) way, and using a transpiler (In my case Typescript) to output a webpack bundle, that is then included using a script tag in the root page (The bundle includes everything needed including OL to run the app)

Ok, so…. Iv'e read pretty much every tutorial I can find, not one of them have worked, Iv'e even tried including the single OL script using the old script loading method, Iv'e also tried wiring up my own even handler, not a single thing works.

My Current Map code looks as follows:

import HttpService from "../../libs/HttpService"
import { inject } from "aurelia-framework";
import Proj4 from 'proj4';
import OlMap from "ol/map";
import Tile from "ol/layer/tile";
import TileWMS from "ol/source/tilewms";
import View from "ol/View";
import Proj from "ol/proj";
import ScaleLine from "ol/control/scaleline";
import Control from "ol/control";
import MousePosition from "ol/control/mouseposition";
import Coordinate from "ol/coordinate";
import Interaction from "ol/interaction";

@inject(HttpService)
export class Showmap {

  private mapName: string = "";
  private mapInfo: MapInfo = null;
  private mapInfoLoaded: boolean = false;
  private httpService: HttpService = null;
  private map: OlMap = null;

  constructor(http: HttpService) {
    this.httpService = http;
  }

  public activate(params) {
    this.mapName = params.mapname;

    this.mapInfoLoaded = false;
    this.mapInfo = null;

    this.httpService.get<MapInfo>(`/api/MapData/MapInfo/${this.mapName}`)
      .then(data => {
        console.log(data);
        this.mapInfo = data;
        this.mapInfoLoaded = true;
        this.loadMap();
      });

  }

  public loadMap() {

    debugger;

    let proj4 = Proj4;
    let proj = Proj;

    proj.setProj4(proj4);

    proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' +
      '+x_0=400000 +y_0=-100000 +ellps=airy ' +
      '+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 ' +
      '+units=m +no_defs');
    var proj27700 = proj.get('EPSG:27700');

    proj27700.setExtent([0, 0, 700000, 1300000]);

    //let keyboardPan = new Interaction.KeyboardPan({
    //  duration: 90,
    //  pixelDelta: 256
    //});

    //let keyboardZoom = new Interaction.KeyboardZoom({
    //  duration: 90
    //});

    this.map = new OlMap({
      target: "theMap",
      layers: [
        new Tile({
          source: new TileWMS({
            params: {
              "LAYERS": "tiles",
              "FORMAT": "image/png"
            },
            url: `http:////MYMAPSERVERURL/${this.mapInfo.wmsUrl}`,
            projection: "EPSG:27700"
          }),
        })
      ],
      view: new View({
        projection: "EPSG:27700",
        center: [this.mapInfo.centerX, this.mapInfo.centerY],
        zoom: this.mapInfo.zoomLevel
      }),
      interactions: Interaction.defaults({ keyboard: true })
    });

    this.map.getInteractions().forEach(function (interaction) {
        interaction.setActive(true);
    }, this);

    this.map.addControl(
      new ScaleLine()
    );

    let coordinate = Coordinate;
    let coordFormat = coordinate.createStringXY(4);
    let coordElement = document.getElementById('coords');

    let mousePosition = new MousePosition({
      coordinateFormat: coordFormat,
      projection: 'EPSG:27700',
      target: coordElement,
      undefinedHTML: '&nbsp;',
      className: 'mork'
    });

    this.map.addControl(
      mousePosition
    );

    //this.map.events.register("click", this, this.mapClick, true);

    this.map.on("click", this.mapClick);
    this.map.on("keypress", this.mapKey);

  }

  private mapKey(event)
  {
    console.log("KEY");
    console.log(event);
  }

  private mapClick(event)
  {
    alert("Map Click");
    console.log(event);
  }

  private mapStart()
  {
    alert("Map Start");
  }

  private mapEnd() {
    alert("Map End");
  }

}

You can see some of the commented out parts where Iv'e been trying to get other things working.

My current version (If you remove the attempt at a keypress handler event) is the same as the official example:

https://openlayers.org/en/latest/examples/accessible.html

An no joy.

If I break point the run, and look at

this.map.getInteractions()

I get back an array of interactions, that shows map pan, scroll zoom , all the interactions I would expect, including the keyboard ones, and ALL of them work as I'd expect except for the keyboard handler.

Anyone here got any ideas as to what I might be missing or what I could try next?

Best Answer

So after what seems like forever, I finally figured this one out.

And it was deceptively simple.

The DIV element holding my map is not a native HTML Input control, and as a result does not partake in the browsers input queue, however like any HTML element you can add it to the input queue simply by giving it a tab order.

<div id="theMap" style="width: 500px; height: 500px; border: 2px solid black; background-color: white" tabindex="-1"></div>

Notice the tabindex="-1" attribute. Simply by adding this, the browser will then consider the DIV element holding the map to be a contender for keyboard events.

For the record, this is not just an OpenLayers thing either, it applies in general to any HTML element you wish to be a target and allow the interception of Keyboard events.

Related Question