GWT: TrafficMap – GWT & Leaflet Map Example

Here is a nice GWT and Leaflet map application I wrote to ease myself back into GWT again after not having written a line of GWT code since 2009. It is a classic map and list type site which displays some sample data I downloaded from the UK Highways Agency traffic RSS feed.

Trafficmap

Not content to knock something together just to get it working I have as usual done it the hard way by embarking on learning about GWT UIBinder and the MVP design pattern which Google are promoting in the latest version of GWT. My intention is to eventually write something bigger after getting up to speed on GWT again. The result is a simple MVP application which utilises composite binder xml views and the GWT Leaflet map component. All mavenized for an easy(ish) build and deploy process.

You can download the project from here.

Highlights

Data

The data comes from the Highways Agency RSS feed here. This was downloaded and processed by my RSS parser which you can find here.

I used the Google JSON library to convert one of the feeds to a JSON file which was my sample data.

Map

The map is the GWT Leaflet component. The component attaches itself to an g:HTMLPanel defined in the MapView.xml UIBinder template.

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
<ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
	xmlns:g="urn:import:com.google.gwt.user.client.ui">
	<ui:style>
	.mapViewPanel {
		padding-left: 10px;
		padding-right: 10px;
	}
	</ui:style>
	<g:HTMLPanel ui:field="mainPanel" addStyleNames="{style.mapViewPanel}" >
	</g:HTMLPanel>
</ui:UiBinder> 

You cannot instantiate the map control directly in the constructor of the binder. You won’t see anything. It has to be created and attached in the onLoad method of the composite view i.e.

@Override
protected void onLoad()
{
	super.onLoad();

	// ---------------------------------------------------------------
	// Map Widget
	// ---------------------------------------------------------------

	MapWidget mapWidget = new MapWidget(MAP_DIV);
	mainPanel.add(mapWidget);
	mapWidget.setHeight("100%");
	mapWidget.setWidth("100%");

	// ---------------------------------------------------------------
	// Map Configuration
	// ---------------------------------------------------------------

	// Create Map instance
	MapOptions loptions = new MapOptions();
	loptions.setCenter(new LatLng(55.864237000000000000, -4.251805999999988000));
	loptions.setZoom(13);

	Options tileOptions = new Options();
	tileOptions.setProperty("attribution", MAP_ATTRIBUTION);
	TileLayer tileLayer = new TileLayer(MAP_URL, tileOptions);

	myMap = new Map(MAP_DIV, loptions);

	myMap.addLayer(tileLayer);

	myMap.invalidateSize(true);
}

A few things to point out.

  • I couldn’t get the the ‘fitBounds’ method to work which I believe should set the view bounds and zoom to the data set.
  • You must call ‘invalidateSize’ to force the map to redraw otherwise the map is kind of half-drawn.
  • The whole view is actually a composite of the left, middle and right panels. I.e.

    <!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">
    <ui:UiBinder xmlns:ui="urn:ui:com.google.gwt.uibinder"
    	xmlns:g="urn:import:com.google.gwt.user.client.ui" xmlns:my="urn:import:com.netthreads.gwt.client.view">
    	<ui:style>
    	</ui:style>
    	<g:HTMLPanel ui:field="testLabel">
    		<my:TopView ui:field="topView" width="100%" height="100%"/>
    		<g:DockLayoutPanel width="100%" height="650px">
    			<g:west size="300.0">
    				<my:ListViewImpl ui:field="listView" width="100%" height="100%"/>
    			</g:west>
    			<g:center size="80.0">
    				<my:MapViewImpl ui:field="mapView" width="100%" height="100%" />
    			</g:center>
    			<g:east size="220.0">
    				<my:PropertiesViewImpl ui:field="propertiesView" width="100%" height="100%" />
    			</g:east>
    		</g:DockLayoutPanel>
    
    	</g:HTMLPanel>
    </ui:UiBinder>
    

    I had bit of trouble trying to get the data table to do a single selection when a row was clicked. After a fair bit of Googling and hunting around stackoverflow I found that you cannot define this in the binder template CSS. The only was to get this to work was to define a CSS resource and pass it into the constructor of the DataGrid.

    The css.

    .dataGridSelectedRowCell {
      border: selectionBorderWidth solid #ffffff; /*#628cd5;*/
    }
    
    /**
     * The keyboard selected cell is visible over selection.
     */
    .dataGridKeyboardSelectedCell {
      border: selectionBorderWidth solid #ffffff; /* #d7dde8;*/
    }
    

    The resource.

    package com.netthreads.gwt.client.view;
    
    import com.google.gwt.user.cellview.client.DataGrid;
    
    /**
     * Override the datagrid style resources.
     * 
     * http://stackoverflow.com/questions/7394151/datagrid-celltable-styling-frustration-overriding-row-styles
     *
     */
    public interface DataGridResource extends DataGrid.Resources
    {
    	@Source(
    	{
    	        DataGrid.Style.DEFAULT_CSS, "DataGridOverride.css"
    	})
    	DataGrid.Style dataGridStyle();
    };
    

    And the usage:

    	private DataGridResource resource = GWT.create(DataGridResource.class);
    	
    	@UiField(provided = true)
    	protected DataGrid<TrafficData> dataGrid = new DataGrid<TrafficData>(50, resource);
    

    I am going to write more GWT stuff again as I am heartily sick of javascript, css and html.

    Apache License 2.0

    Comments

    Leave a Reply