Kim Rudolph

Highmaps map filled with random data

Highmaps and WebSockets Using Spring Boot

The goal of this tutorial is to build a simple integration of a service sending random data to connected subscribers:

WebApplication Configuration

First thing is to setup a web app. Spring Boot makes that part easy. Check the Websocket option at http://start.spring.io/ and hit the download button.

src/main/java/.../HighmapsWebsocketApplication.java

package de.kimrudolph.tutorials;
...
@SpringBootApplication
@EnableScheduling
public class HighmapsWebsocketApplication {

    public static void main(String[] args) {
        SpringApplication.run(HighmapsWebsocketApplication.class, args);
    }

    ...
}

Scheduling needs to be enabled via @EnableScheduling annotation. Using poor man’s JSON serialization simple objects are sent to the endpoints /bubbles and /map. Random updates contain payloads like {"hc-key": "de-sh-01054000", "z": 11} as a new bubble every second and every 5 seconds whole map area values like [{"hc-key": "...", "value": 11}, ...]. The hc-key values define the location definitions taken from the Highmaps data for Germany.

src/main/java/.../HighmapsWebsocketApplication.java

...
@EnableScheduling
public class HighmapsWebsocketApplication {

    ...

    @Autowired
    private MessageSendingOperations<String> messagingTemplate;

    @Scheduled(fixedDelay = 1000)
    public void sendBubbleUpdates() {

        this.messagingTemplate.convertAndSend("/bubbles",
            "{\"hc-key\": \"" + ids.get(new Random().nextInt(ids.size() - 1)) +
                "\", \"z\": " + new Random().nextInt(100) + "}");

    }

    @Scheduled(fixedDelay = 5000)
    public void sendMapUpdates() {

        StringBuilder updates = new StringBuilder("[");

        for (String id : ids) {
            updates.append("{\"hc-key\": \"").append(id)
                .append("\", \"value\": ").append(new Random().nextInt(100))
                .append("}").append(",");
        }
        updates.deleteCharAt(updates.length() - 1);
        updates.append("]");

        this.messagingTemplate.convertAndSend("/map", updates.toString());

    }

    private final List<String> ids = Arrays
        .asList("de-sh-01054000", "de-mv-13074000", "de-mv-13073000",
            "de-mv-13003000", "de-ni-03457000", "de-by-09277000",
            "de-mv-13076000", "de-ni-03356000", "de-by-09184000",
            ...
            "de-ni-03257000", "de-ni-03351000", "de-by-09464000");
}

The start-class property in the pom.xml file has to be modified to point to the right main HighmapsWebsocketApplication class. That enables the java -jar *.jar execution.

pom.xml

...
  <properties>
    ...
    <start-class>de.kimrudolph.tutorials.HighmapsWebsocketApplication</start-class>
  </properties>
...

A simple message broker is registered, enabling connections at the /random endpoint.

src/main/java/.../WebSocketConfiguration.java

package de.kimrudolph.tutorials;
...
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration extends
    AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/random").withSockJS();
    }

}

Map Display

The HTML view is pretty straightforward as it only needs to define a container placeholder for the map and include the javascript files. Spring serves that HTML file as a static resource.

public/index.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">

<head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>WebSocket map example</title>
</head>

<body>
  <div id="container"></div>
</body>

<script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
<script src="//code.highcharts.com/maps/highmaps.js"></script>
<script src="//code.highcharts.com/mapdata/countries/de/de-all-all.js"></script>
<script src="//cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script src="application.js"></script>

</html>

Application Javascript

Besides the needed frameworks mentioned above, there are two javascript parts to actually show the map. First goes a standard map definition with the bubbleData and mapDataUpdates variables linked to the map series. Changes to that variables will trigger an event to update the map. The actual map data countries/de/de-all-all is taken from the script definition .../de-all-all.js referenced in the index.html. Series zIndex configurations are needed to prevent that the bubbles are drawn behind the actual map. joinBy definitions link the variables content to the mapData.

public/application.js

var bubbleData;
var mapDataUpdates;

$(function () {

  var mapData = Highcharts.maps['countries/de/de-all-all'];

  $('#container').highcharts('Map', {

    title: {
      text : false
    },

    legend: {
      enabled: false
    },

    chart : {
      events : {
        load : function() {
          bubbleData = this.series[0];
          mapDataUpdates = this.series[1];
        }
      }
    },

    colorAxis: {
      min: 0,
      max: 100,
      minColor: '#eeeeee',
      maxColor: '#000000'
    },

    series:
            [
              {
                type: 'mapbubble',
                mapData: mapData,
                joinBy: 'hc-key',
                zIndex: 11,
                data : []
              },
              {
                mapData: mapData,
                joinBy: 'hc-key',
                zIndex: 10,
                data :[{}]
               }
            ]
  });

});
...

And secondly the webocket handling. A connection is established and the client subscribes to the /bubbles endpoint. Any messages received at that subscription will add a data point to bubbleData. If there are more than 10 data points, the oldest one will be removed. A subscription to the /map endpoint replaces the whole map data.

public/application.js

...
var socket = new SockJS('/random');
var client = Stomp.over(socket);

client.connect('user', 'password', function(frame) {

  client.subscribe("/bubbles", function(message) {
    var shift = bubbleData.data.length > 10;
    bubbleData.addPoint(JSON.parse(message.body), true, shift, true);
  });

  client.subscribe("/map", function(message) {
    mapDataUpdates.setData(JSON.parse(message.body));
  });

});

Run the Example

mvn package && java -jar target/*.jar starts the example at http://localhost:8080/index.html.

Source

The full application can be found at the spring-boot-websockets-highmaps repository.