Pages

JavaFX and Google Maps

Recently, I have taken a very keen interest in JavaFX. The declarative nature of the language and its powerful data binding capabilities, coupled with the fact that there are so many existing Java programs will accelerate its adoption very quickly.

My exploration with JavaFX has been very fruitful. It is really easy to pick up and to be productive with it in a matter of hours. When I started to learn how Google Maps can be used with JavaFX, I found very little useful information in the Web. Googling the keywords "JavaFX Google Maps" was less than satisfactory. Then, I stumbled upon Google Maps can be used statically without Javascript. For example, the following map on Eiffel Tower is displayed using a static html link. Notice the latitude and longitude are specified in the URL.
http://maps.google.com/staticmap?center=48.8531,2.3691&markers=48.8531,2.3691,rede&zoom=14&size=320x240

Paris Eiffel Tower
It then dawned upon me how easy it is to incorporate Google Maps into JavaFX, all we need are the JavaFX Image and ImageView objects. For instance, the following codes will display the above map in a JavaFX application.
def stage = Stage {
title : "JavaFX Google Maps Demonstration"
scene: Scene {
 width: 500
 height: 400
 content: [
   ImageView {
     image: Image {
       url: "http://maps.google.com/staticmap?center=48.8531,2.3691&markers=48.8531,2.3691,rede&zoom=12&size=320x240"
     }
   }
 ]
}
}
Next, I discovered the use of Google Geocoding web service. Given an address, you can get the latitude and longitude of that address. For example, to find the latitude and longitude of New York USA, enter the following url.
http://maps.google.com/maps/geo?q=NEW+YORK+USA&output=xml
The Google Geocoding web service will return the following XML. Notice the latitude and longitude of New York, USA are returned in the coordinates element in line 25.

  
    NEW YORK USA
    
      200
      geocode
    
    
      
New York, NY, USA
US USA NY New York -73.9869510,40.7560540,0

Hence, using static Google Maps and GeoCoding web service, I ventured to develop this demonstration application using the latest JavaFX 1.2. A screen shot of the application is given here.


This simple application allows the user to enter an address. If Google recognises it, the map of the address will be shown. The user can also pan the map by dragging the mouse or zoom in and out of the map using the slider. The application comprises three files Main.fx, GMapUtils.fx and GmapGeoCoding.fx.

Click on the Launch button to try the application.



The full source codes of these three files are given as follows.
// Main.fx
package jfxgm;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.Cursor;
import javafx.scene.image.ImageView;
import javafx.scene.image.Image;
import javafx.scene.input.MouseEvent;
import javafx.scene.control.TextBox;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.control.Slider;

var lng:Number = 103.8476410;
var lat:Number = 1.3717300;
var zoom:Integer = bind javafx.util.Math.round(sZoom.value);

bound function getMapImage(lt, lg, zm):Image {
  var mapurl:String = "http://maps.google.com/staticmap?center={lt},{lg}&markers={lt},{lg},reda&zoom={zm}&size=480x320";
  var map:Image = Image {
    url: mapurl;
  }
return map;
}

function setLatLng(lt:Number, lg:Number):Void {
  lat = lt;
  lng = lg;
}

var mapview:ImageView = ImageView {
  layoutX:10 layoutY:75;
  image: bind getMapImage(lat, lng, zoom);
  cursor : Cursor.MOVE;
  var anchorx:Number;
  var anchory:Number;
  onMousePressed: function( e: MouseEvent ):Void {
    anchorx = e.x;
    anchory = e.y;
  }
  onMouseReleased: function( e: MouseEvent ):Void {
    var diffx = anchorx - e.x;
    var diffy = anchory - e.y;
    lat = GMapUtils.adjustLatByPixels(lat, diffy, zoom);
    lng = GMapUtils.adjustLngByPixels(lng, diffx, zoom);
  }
}

def lblAddress = Label {
  layoutX:10 layoutY:15;
  text: "Address :"
}

def txtAddress = TextBox {
  layoutX:70 layoutY:10;
  text: ""
  columns: 28
  selectOnFocus: true
}

def lblLat = Label {
layoutX:39 layoutY:45;
text: "Lat :"
}
def txtLatitude = TextBox {
layoutX:70 layoutY:40;
text: bind (lat.toString());
columns: 8
editable: false;
}
def lblLng = Label {
layoutX:175 layoutY:45;
text: "Lng :"
}
def txtLongitude = TextBox {
layoutX:210 layoutY:40;
text: bind (lng.toString());
columns: 8
editable: false;
}

def btnUpdateMap = Button {
layoutX:320 layoutY:10;
text: "Get Latitude and Longitude"
action: function() {
  var addr:String = txtAddress.text;
  GMapGeoCoding.getLatLng(addr, setLatLng);
}
}

def lblLevel = Label {
layoutX:310 layoutY:45;
text: "Level :"
}

def sZoom:Slider = Slider {
layoutX:355 layoutY:45;
min: 1
max: 20
value:15
vertical: false
}

def stage = Stage {
title : "JavaFX Google Maps Demonstration"
scene: Scene {
  width: 500
  height: 400
  content: [
    lblAddress, txtAddress,
    lblLat, txtLatitude,
    lblLng, txtLongitude,
    btnUpdateMap,
    lblLevel,sZoom,
    mapview,
  ]
}
}
// GMapUtils.fx
package jfxgm;

import javafx.util.Math.*;

def GMAPOFFSET = 268435456;
def RADIUS = GMAPOFFSET / PI;
def GMAPMAXZOOM = 21;

function lng2x(lng):Integer {
  return round(GMAPOFFSET + RADIUS * lng * PI / 180);      
}

function lat2y(lat) {
return round(GMAPOFFSET -
               RADIUS *
               log((1 + sin(lat * PI / 180)) /
               (1 - sin(lat * PI / 180))) / 2);
}

function x2Lng(x:Number):Number {
return ((round(x) - GMAPOFFSET) / RADIUS) * 180/ PI;
}

function y2Lat(y:Number) {
return (PI / 2 - 2 * atan(exp((round(y) - GMAPOFFSET) / RADIUS))) * 180 / PI;
}

public function adjustLngByPixels(lng:Number, delta:Number, zoom:Number):Number {
return x2Lng(lng2x(lng) + (delta * pow(2, (GMAPMAXZOOM - zoom))));
}

public function adjustLatByPixels(lat:Number, delta:Number, zoom:Number):Number {
return y2Lat(lat2y(lat) + (delta * pow(2, (GMAPMAXZOOM - zoom))));
}
// GMapGeoCoding.fx
package jfxgm;

import javafx.io.http.*;
import javafx.data.pull.PullParser;
import javafx.data.pull.Event;
import javafx.io.http.URLConverter;

public function getLatLng(address:String, setLatLng:function(lat:Number, lng:Number)):Void {
  def getRequest: HttpRequest = HttpRequest {
    var addr = URLConverter{}.encodeString(address);
    location: "http://maps.google.com/maps/geo?q={addr}&output=xml";
    onInput: function(is: java.io.InputStream) {
    def parser = PullParser {
      documentType: PullParser.XML;
      input: is;
      onEvent: function(event: Event) {
        if (event.type == PullParser.END_ELEMENT) {
          if (event.qname.name == "code" and event.text == "602") {
            setLatLng(0.0, 0.0);
          }
          else if (event.qname.name == "coordinates") {
            var pts = event.text.split(",");
            setLatLng(java.lang.Float.parseFloat(pts[1]),
                      java.lang.Float.parseFloat(pts[0]));
          }
        }
      }
    }
    parser.parse();
    parser.input.close();
  }
}
getRequest.start();
}

2 comments:

  1. Hi, How to go on using earth api, instead of static map api? Is it possible? Thanks ^_^

    ReplyDelete
  2. oooooH! Interesante Voy a Provarlo!
    en cuanto tenga resultados. comento de Nuevo!.

    ReplyDelete