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

    Flex: WMS with Google Map Flash Control

    Javier De La Torre has some really impressive examples of using a GeoServer WMS feed to generate overlays for Flash Google Maps. The custom WMS provider used in the WMS demos from this site is based originally on code very generously posted by Javier onto the UMap forum.

    WMS overlay for Google Map for Flash here.

    Heat maps over Google Map for Flash here.

    I have had a play around with the Flash Google Map API although I haven’t looked at it recently. It is certainly something I would use though. The UMap component is free to download but requires a license to use so the addition of WMS support for the Google map solution is a fantastic addition.

    Flex: Update – UMap WMS Provider UMap Traffic demo

    I have now updated the original WMS provider Traffic demo for AFComponents UMap Flex map control.

    IFrame Map App

    This uses the latest version as of today 2nd August 2008, version 1.1.

    Right click to view/download the source code.

    Flex: Update – UMap WMS Custom Provider demo

    I have updated the original WMS provider demo for AFComponents UMap Flex map control.

    IFrame Map App

    This uses the latest version as of today 2nd August 2008, version 1.1.

    There are a few changes to the demo.

    Right click to view/download the source code.

    GWT: gTraffic Usage

    I have Google stats activated for some of my websites including gTraffic the UK traffic Google map mashup. The site ticks over with fairly low stats of about 20 people a day (I salute you fellow traffic junkies). Occasionally when there is a bank weekend or a public holiday the stats jump to some fairly respectable figures. For example, there is a monday holiday coming up in the UK this weekend, check out the stats. Both spikes are holiday weekends:

    gTraffic usage

    This is interesting in all sorts of ways if you are into road traffic websites. Basically (and I can back this up from the commercial sites I have worked on), noone gives a hoot about the traffic on the road until they have to make a journey. Then they want to know if they are likely to get held up as the pack the kids into the 4×4 and make the journey down to Dover or north to the Lake District. For those of you who use gTraffic I thank you and have good holiday.

    Flex: UMap TerraServer Demo

    Yet another UMap demo. This is just a stripped version of the some of the code from a previous WMS demo. I have modified the WMS settings to point to the TerraServer WMS feed.

    IFrame Map App

    Right-click to view/download the source code.

    I am having a bit of a rest from developing for the moment. I will get round to peoples requests for updates in a week or so.

    GWT: gTraffic Timeline

    I occasionally have a look at my GWT traffic mashup to make sure it’s still working. I was looking at it again tonight and was struck by how many good ideas it seems to have. Good ideas that no one else seems to have bothered to pick up on. As an example take a look at the view (as of tonight) for the Berkshire area.

    gTraffic

    I was clicking around to make sure that the data seemed up to date and kept clicking on long running Roadworks. If you only had that view then it would be a chore to determine what was new and what was long running except that you have the ability to flick over to the time-line. Looking at that I could see right away what the latest events were. The time-line view is not perfect but I am still amazed no one else has copied this idea cos I think this is genuinely useful.

    gTraffic timeline

    Another one is the zoom-in/zoom-out buttons on the infowindow. My implementation jumps down to the street view and then back up to the last selected zoom level. I know this has been used elsewhere in sites connected to me but I would have thought this would be more widespread as, again, it’s a useful functionality.

    gTraffic zoom

    Maybe it’s just me but I think there are some good ideas here which are not represented in current traffic web sites.

    Flex: UMap WMS Custom Provider Traffic Demo

    Here is another UMap WMS Provider demo

    This is the Glasgow traffic data from here and applied to a WMS feed from here.

    This uses the UMap component from AFComponents.

    IFrame Map App

    Right-click to view the source.

    The WMS feed is defined in the ‘DataModel.as’ class.

    public static var WMS_TILE_URL:String = “http://www2.demis.nl/wms/wms.ashx”;

    public static var WMS_TILE_LAYERS:String =
    “Topography,Builtup%20areas,Coastlines,Waterbodies,Inundated,Rivers,Streams,
    Railroads,Highways,Roads,Trails,Borders,Cities,Settlements,
    Spot%20elevations,Airports,Ocean%20features”;

    public static var WMS_LAYER_FORMAT:String = “Image/png”;

    This doesn’t use the OSGR conversions of the previous demo and instead uses the straight WGS84 projection “EPSG:4326”.

    Update #1

    Updated to new version of UMap control 1.1 02/08/08. See here.

    Flex: UMap WMS Custom Provider Demo

    Aint it the way. You wait for one map demo and three come along at once. I have been trying out the UMap component from AFComponents. Here is the latest demo.

    IFrame Map App

    The first thing to note is that this is not using Google or Microsoft to provide the map tiles. I have implemented a custom tile provider class ‘WMSTileProvider’ which talks to a mapping server using the WMS protocol.

    The map is supplied from here.

    This tiles are from here and have been generated by scanning out-of-copyright Ordnance Survey maps and stitching them together. Very clever.

    The AS3 UMap component provides a means to hook your own custom tile provider into the underlying map mechanism. There is a partial explanation of this on the forum here and here.

    The forum posts are more of a signpost as to how you could go about plugging in a Custom provider for the map. There was no downloadable code, until now.

    I have spent a fair bit of time trying to get this working and I have adopted one technique. There may be others. The main trick is to override all the overridable methods in your ‘Provider’ subclass.


    public class WMSTileProvider extends Provider
    {
    public function WMSTileProvider(defaultData:Boolean = false,
    settings:URLRequest = null,
    language:URLRequest = null,
    copyright:URLRequest = null)
    {
    super(defaultData, settings, language, copyright);
    }

    override public function getDefaultLanguage():String
    {
    return “EN”;
    }

    override public function getDefaultCopyright():String
    {
    return “(c) – OS NPE (1940s) – Public Domain”;
    }

    override protected function parseSettings(data:String):Boolean
    {
    return true;
    }

    override protected function parseLanguage(data:String):Boolean
    {
    return true;
    }

    override protected function parseCopyright(data:String):void
    {
    trace(data);
    }

    }

    The next trick is to supply dummy requests for the language and copyright strings. Normally these would be php requests but I just changed them into xml files. As long as you override all the methods in the Provider subclass you won’t get any annoying exceptions.


    var settings:URLRequest = new URLRequest("settings.xml?rand=" + Math.random());
    var language:URLRequest = new URLRequest("language.xml?rand=" + Math.random())
    var copyright:URLRequest = new URLRequest("copyright.xml?rand=" + Math.random());

    _map.control.provider = new WMSTileProvider(false, settings, language, copyright);

    You have to put something into language.xml and copyright.xml they can’t be empty.

    The settings.xml file only need contain the minumum definition:



    http://www.afcomponents.com/components/umap_as3/GoogleLogo.png

    Not so complicated when you know how but not easy to work out from the UMap forum posts.

    An additional hurdle for me was that the WMS feed required the boundaries of the tile view to be supplied in British National Grid (Easting and Northing) values. I had some old javascript kicking around (which I thought I’d lost) which implements WGS84 to OSGB conversions. I had half created a AS3 version of this a while back and now it became quite handy (this is bundled into the source code for those of you who might need it).

    Truly a pure geeky rush when I saw the map tiles burst into life. It was worth all the hassle. Right-click to view the source.

    There is a pretty long list of other WMS data providers here if you want to try creating a different WMS tile layer. If you do it would be good form to post it up onto the UMap forum.

    Bugs

    – Resizing the map window sometimes makes the tile disappear. No idea as of now what’s going on but I think it’s linked to the resize.

    About the dataset

    The Avebury data is supplied as a KML file. This is fetched and parsed by the application and then displayed on the map and in the right-hand data grid. The data was downloaded from here.

    This is the original drawing William Stukeley made of the Avebury complex.

    IFrame Map App

    His drawing shows the main complex and the two avenues to the left and right before time and other thieves destroyed most of it. You can see most of the same features on the map view.

    For some more information head here.

    Update #1

    Update #2

    Flex: IFrame Map Demo

    As requested here is an IFrame map demo. It took me a bit of time as it’s not that easy to put together. The application looks like this:

    IFrame Map App

    You will need the full project to examine as it isn’t just encapsulated by the Flex code. There is a load of javascript and the magic of FABridge to get your head around.

    This is currently only a FlexBuilder2 project. I haven’t got around to converting it to FB3. The reason for this is that there are hooks into the required javascript from all sorts of places including the ‘index.template.html’ file. Recreating the project from scratch means these are blasted by the new project option in FB3 and it’s a bit of chore to generate all the required includes that the project needs.

    Here is a (rough) diagram of how the javscript is arranged.

    IFrame Map App

    The ‘MapClient.js’ component presents a generalised API to the FABridge layer, this abstracts the implementation details of the map down into the controller component ‘MapOSController.js’. The controller implements the real details of whichever map is plugged in to the client. In this case it is OpenLayers whereas in flexTraffic I have a similar component which implements the map interface for a Google map.

    Flex

    Map communication from the Flex side takes the form of generating events which the ‘MapClient’ has registered listeners for (through FABridge). The advantage of this over using ‘ExternalInterface’ calls is that FABridge marshalls the objects across the Flex/Javascript layer and turns the Plain Old Flex Objects (POFO’s) into javascript objects complete with getters and setters for member variables. This is an extremely powerful mechanism.


    flexApp.addEventListener("ResizeFrameEvent", function(event) {self.controller.resizeFrame(event.getWidth()-self.offset, event.getHeight()-self.offset)});
    flexApp.addEventListener("CentreFrameEvent", function(event) {self.controller.centerOn(event.getLatitude(), event.getLongitude())});

    See the ‘getWidth’ and ‘getHeight’ functions? These are generated automatically by Fabridge from the Flex event which the listener registered.


    public class ResizeFrameEvent extends Event
    {
    public static const EVENT_RESIZE_FRAME:String = "ResizeFrameEvent"

    public var width:Number;
    public var height:Number;

    public function ResizeFrameEvent(width:Number, height:Number):void
    {
    super (EVENT_RESIZE_FRAME, true);

    this.width = width;
    this.height = height;
    }
    }

    Javascript

    The javscript side communicates with the Flex app by calling Flex methods directly. The method function must be declared in the application mxml at the same level as the FABridge controller definition. Note that the functions which perform this direct communication are abstracted further by making them plugin functions to the controller at the client level.

    self.controller.addMapViewChangedHandler(function(a,b,c,d)
    {
    var flexApp = self.frame.FABridge.flash.root();
    flexApp.onMapViewChanged(a,b,c,d);
    });

    This application exhibits all of the tricks I have invented to embed an html page into a Flex application. If you are interested in using the Flex IFrame then I suggest if you understand this you will have mastered all of the techniques required to make something like flexTraffic. The demo doesn’t have that much of a wow factor but all of the magic is in the code. There is no quick dropin code to acheive this kind of thing, it takes a bit of inventiveness and graft.

    Next Page →