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();
}

Java Compile and Run in Notepad++

Notepad++ is an excellent text editor. It supports syntax highlighting of many programming languages besides Java. Unlike Eclipse and Netbeans, it is light-weight and it can be launched from Windows Explorer easily by right-clicking on the file you wish to edit.

The following steps describe how you can extend the capabilities of Notepad++ to include compiling and executing Java programs from Notepad++ itself.

First, create a file called JavaCompileRun.bat. You can do this by right-clicking on your Windows' Desktop and then click New and Text Document. Then, key in "JavaCompileRun.bat" as the new file name. Right-click on this file and click Edit. Put in the following codes and save the file.
cd /d "%1"
javac %2
if errorlevel 1 goto finish
java %3
:finish
pause
Next, copy the file JavaCompileRun.bat to your Notepad++ program folder, usually at C:\Program Files\Notepad++.

Open Notepad++. Select the Run menu and click on Run.... A dialog box will appears. Key in the following command.
$(NPP_DIRECTORY)\JavaCompileRun.bat "$(CURRENT_DIRECTORY)" "$(FILE_NAME)" "$(NAME_PART)"
After keying in the above command, the dialog box should look something like this...


Click on the Save... button. In the Name: text box, key in "Java Compile and Save" as shown in the figure below.



Click OK and then click on the Run! button. Your Java program should compile and run if there is no error.

Now to compile the current Java program, make sure you have saved the file. Then, in the Run menu, click Java Compile and Run as shown.


A Command Prompt window will appear and the results of your Java program will be displayed.


There is however one serious limitation with this method. Only Java programs with default package will compile and run.

App_Code Folder in ASP .NET 3.5

In Visual Studio Professional 2008, you can still create the App_Code folder by right-clicking on the Web Project and selecting Add, then Add Folder. Rename the new folder to App_Code.

Contrary to some recommendations on the Web that you should not use the App_Code folder because you cannot place common Web UI codes or classes in this folder, you can do so by setting Build Action of each class to Compile. Suppose this step is not done, the classes defined in this folder will not be visible to your other codes. This explains why people recommended against the use of App_Code folder in VS 2008.

Suppose you have a class called WebCommon.cs in this folder. All you have to do is to right-click on this file and select Properties. A properties window will appear. Set Build Action to Compile. Viola! The class can be accessed exactly like what you have seen in VS 2005.