Sunday, August 23, 2009

Still working on issues

I am still working on the issues for World Wind 1.4.1. There hasn't been a lot of traction but at least one other dev was using my custom wiki bug tracker.

I did check in my Download code and Atmosphere plug-in fixes into the main trunk of World Wind code.

Now, I will start watching the WW project on ohloh for updates. Since I checked in to sourceforge svn using my ammianus id, my ohloh profile should reflect these commits to the WW project. Since it is my first check-in to trunk I don't know if it will work. They seem to refresh their statistics from SVN every 2 days or so.

Sunday, August 2, 2009

Fire up the bug tracker

I feel like I have had a pretty productive weekend in terms of WorldWind accomplishments. This entry is actually on time and not stale at the time of writing which is also a good sign.

First of all I finally got my act together and created a complete bug tracking wiki-article on the World Wind site. This page has a table containing general information and priority to bugs, then for each bug I created a section in the bottom half of the page for more detailed description and comments. It probably took me an hour or two to develop this, it's not secure and has a lot of manual editing to update anything, but its something. Its the first time I've been on this project we have been able concretely state what we know needs to get done and to say who is doing what. I have not gotten a whole lot of response, but so far one other active developer has stepped up and begun fixing items from that list. I've picked up another item as well.

Setting up that page was one thing, the second task was to populate it with data. I spent most of this morning reading and replying to threads in the WW Bug Reports Forum. From this I tried to glean the data to see what issues had been found by users but never addressed. The sad thing was a number of threads more than a year old with no replies where I could see no fix was ever made. Ok, I've been at it for 6 months and have not fixed many bugs myself, so I can understand it. I don't like it too much though.

Maybe I enjoy "process", but I feel I am not alone when finding things like proper defect tracking help software development rather than are a burden. I have taken the initiative to provide this data and calling it out. I am not quite sure what the reaction will be from more senior developers, hopefully there will be more positive responses so I will continue.


On the front of fixing bugs, I took a look at two Issues I came across with "Plug-in" code. The first I looked at was the "Starfield" plugin. The issue with this plugin is that it loads by default on start up. However, if you try to uncheck it from the Layer Manager, it will not unload the visible background stars. I would like to go into the Plug-in architecture more later. But basically there is a single Stars3d.cs file containing the Plugin code plus some initialization and data files that get stored in a Plug-ins directory in the application folder. After some trial and error I got this to work by a simple line change in the Stars3DLayer class's Render(DrawArgs) method, approx inserted after line 151

if (!this.IsOn)
return;

IsOn is an inherited property of the RenderableObject base class for the layer. It gets set true or false by the LayerManager when the check/uncheck happens, there seems to be no other hook to the plugin itself when that event is handled in the LayerManager UI.

I made a further enhancement so that the SaveSettings(),ReadSettings(),Dispose() methods would actually save the sate of that check, either On or Off so that if you check it off, the next time you start WorldWind it will stay off (but still loaded).

Open Questions: I'm not sure if there is a some other way that the WW is supposed to know not to render a layer if IsOn == false?
Is there a "business" reason why they wouldn't want the plugin's last state (checked or unchecked) to be persistent?


The second issue I stumbled upon when trying to understand how the LayerManager worked by looking at other plugin code. I tried one called Atmosphere.cs which didn't seem to do anything but repeatedly cause System.NullReferenceException. I traced through this and found a fix pretty quickly. A summary of what I found and my fix can be found in this Bug Report thread


PS: Thanks to The Wandering Geek Blog for the tip on how to configure the blogger styles to render the code blocks nicely with a background. e.g.(Atmosphere.cs):
// Set new one (to avoid being clipped) - probably better ways of doing this?
float aspectRatio = (float)device.Viewport.Width / device.Viewport.Height;
device.Transform.Projection = Matrix.PerspectiveFovRH((float)camera.Fov.Radians, aspectRatio, 1, (float)(2*world.EquatorialRadius) );
device.TextureState[0].ColorOperation = TextureOperation.BlendCurrentAlpha;

Download Code checked in

This is also a few weeks late, I finished testing on the last weeks of my vacation in mid-July while still in Korea. The folks at WorldWindCentral were kind enough to provide me commit access to SVN, so I have started my own branch to check in the changes. So far this has not been commited to /trunk yet.

Here is the the URL for my shiny new SVN branch:
https://nasa-exp.svn.sourceforge.net/svnroot/nasa-exp/branches/ammianus

As far as the Download code I also I posted a detail list of the fixes I have done here:
http://worldwindcentral.com/wiki/1_4_1_Issues#Download_code_error_response_handling

Logical changes include new error response handling:
* 4xx / Client error are cached as failures and WW will not retry to download them during that session (cache is in memory only and cleared when WW is exited).

  • 400 Bad Request

  • 401 Unauthorized

  • 403 Forbidden

  • 404 Not Found

  • 206 Partial Content

  • 200 OK && Content length == 0


* 5xx / Server Error are recorded and a timeout period will begin before requests are allowed to this server again. A message is displayed in the top right of the UI stating the timeout length. If server response contains "retry-after" header, that period is used, otherwise default is 2 minutes.

  • 500 Internal Server Error

  • 501 Not Implemented

  • 502 Bad Gateway

  • 503 Service Unavailable


Changes made to source files:

* WebDownload/WebDownload.cs
* PluginSDK/QuadTileSet.cs
* PluginSDK/Renderable/GeoSpatialDownloadRequest.cs

Sunday, July 26, 2009

Creating Local Tile Servlet

I meant to write my updates when I got back from my trip a week ago, but there are a lot of things at home to take care of after a three week vacation. As promised I did some work on the WorldWind while I was away.

Creating Local Tile Server
With an unsteady internet connection I needed to be able to continue to test WorldWind changes that related to the download code. If I could set up a local server, and point my WorldWind program to check that for tiles I could still check the download code if it works. A side benefit is that in theory if my local tile server is something I coded myself, I'd be able to insert dummy error response codes like 404 or 500 errors in order to test the behavior for those errors in the new download code.

I tried using existing scripts and code that was posted on the WorldWindCentral wiki. But I either don't have the right application server software to run them (PHP, Perl,Python) or the know-how to set it up and use it (IIS 7/aspx.cs). There is also a WorldWind server, written in java posted on the World Wind forums (NASA World Wind WMS Server Config), I tried that too, this would allow me to set up a local server but because it was a self-contained application, but wouldn't let me achieve my second goal of inserting my own custom errors for testing. Even so, it has dependencies on all or some of WorldWind Java SDK. Now on my own, but understanding better what needs to get done, I thought "Why not just write this in a language I know, Java and set up Tomcat with a simple web app?"

Taking the .aspx.cs example, I ported that to a Java Servlet class.

package org.wwc.ammianus.tomcat.tileserver;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.HashMap;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
*

This servlet handles requests from the World Wind.NET application and returns
* tile images or error codes as appropriate. This was a port to Java of a ASP.NET file posted
* at http://worldwindcentral.com/wiki/Data_serving_scripts


*
* @author ammianus
* @version 0.1
*
*
*/
public class TileRequestHandler extends HttpServlet {

/**
* Generated by Eclipse
*/
private static final long serialVersionUID = -6596187549520730077L;
/**
* settings Map which theoretically could be extended for any layer used in WW
* for each layer, use the <DataSetName></DataSetName> tag value from @Images.xml file
* create three map entries <DataSetName>, <DataSetName>_EXT, <DataSetName>_MIME
*/
private HashMap _settings;

private int _requestNumber;

/**
* @see HttpServlet#HttpServlet()
*/
public TileRequestHandler() {
super();
//use these setting which theoretically could be extended for any layer used in WW
//for each layer, use the tag value from @Images.xml file
//create three map entries , _EXT, _MIME
_settings = new HashMap();
_settings.put("geocover2000", "C:\\Users\\Public\\WorldWindCache\\Cache\\Earth\\Images\\NASA Landsat Imagery\\Geocover 2000");
_settings.put("geocover2000_EXT", ".jpg");
_settings.put("geocover2000_MIME", "image/jpeg");

_requestNumber = 0;
}

/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//increment counter
_requestNumber++;

//Un-comment this to test whether WW handles 503 return code with Retry-After header
//
//first request we will send an error with a retry-after header
//
/*if(_requestNumber == 1){
response.setStatus(503);
//this appears to be case insensitive for WorldWind, I just picked a random retry-after period
response.setHeader("retry-after", "38");
System.out.println(_requestNumber+":Response: 503, with retry-after: 38");
return;
}*/

try{


//column
int X = Integer.parseInt(request.getParameter("X").toString());
//row
int Y = Integer.parseInt(request.getParameter("Y").toString());
//level
int L = Integer.parseInt(request.getParameter("L").toString());
//DataSet name
String T = request.getParameter("T").toString(); //T = Dataset in WorldWind parlance


//Un-comment this to test whether WW handles 500 return code
//
//if(T.equals("geocover2000")){
// throw new Exception("Force return of 500 - Server Error");
//}

//Arguments are in this format URL sent from world wind
//e.g. http://localhost:8080/TomcatTileServer/TileRequestHandler?T=geocover2000&L=0&X=47&Y=21
String filePath = _settings.get(T);//WebConfigurationManager.AppSettings[T];

String fileExt = _settings.get(T + "_EXT");
if(L < 3){
DecimalFormat df = new DecimalFormat("0000");
filePath += "\\"+L + "\\" + df.format(Y) + "\\" + df.format(Y) + "_" + df.format(X) + fileExt;
}else{
filePath += "\\level"+(L+1)+fileExt;
}
//request.get
System.out.println(_requestNumber+":Requested File: "+filePath);
File file = new File(filePath);

//if file (image for requested tile) is not found on server, return 404
if (!file.exists())
{
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Not Found");
}
else
{
//set content length to file size
response.setContentLength((int) file.length());
//get mime type for this layer
response.setContentType(_settings.get(T + "_MIME"));
//get the output stream for the response, this will have the file written to it
ServletOutputStream stream = response.getOutputStream();

//read the file contents and write them to the outputstream
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024];
int counter = 0;
while(counter < file.length()){
fis.read(bytes);
counter += 1024;
stream.write(bytes);
}
fis.close();
stream.flush();
stream.close();

//done with response
}
}catch(Exception e){
//uncaught Exception return 500 error
response.sendError(500,e.getMessage());
System.out.println(_requestNumber+":Response: send 500 error");
}

}

/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}

}

As you can see, not much there. In my WEB-INF/web.xml, I included the new servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" schemalocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>TomcatTileServer</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<description></description>
<display-name>TileRequestHandler</display-name>
<servlet-name>TileRequestHandler</servlet-name>
<servlet-class>org.wwc.ammianus.tomcat.tileserver.TileRequestHandler</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>TileRequestHandler</servlet-name>
<url-pattern>/TileRequestHandler</url-pattern>
</servlet-mapping>
</web-app>
Now I was typically running this from my Eclipse IDE (Eclipse J2EE 3.4.1, Apache Tomcat 6), but I also could export the project in Eclipse as a war and just drop it into Tomcat's webapps folder.

To access this server there is the format that the URL would take:
http://localhost:8080/TomcatTileServer/TileRequestHandler?T=geocover2000&L=0&X=48&Y=21

To break this down:
  • T = the name of the layer, or DataSet name ( tag value from @Images.xml)
  • L = level, (0 - x) where 0 is the highest level
  • X = column
  • Y = row
From this the TileServer, based on its implementation and organization of the images, will try to look up locally a file that matches that. In my case it was something like C:\Users\Public\WorldWindCache\Cache\Earth\Images\NASA Landsat Imagery\Geocover 2000\0\0021\0021_0049.jpg".

Integrating Local Server with WorldWind
Thanks to WorldWind forums for giving me this information. Now to make WorldWind point to my local tile servlet instead of the default NASA WMS server, you need to edit the @Images.xml file in your WorldWind's Config\Earth directory. There is probably an @Images.tmp, copy this and rename it to @Images.xml.

Edit the file, and for the DataSetName your local server will handle, update the tag such as:
<ServerUrl>http://localhost:8080/TomcatTileServer/TileRequestHandler</ServerUrl>
And then restart World Wind you should be good to go

Saturday, June 27, 2009

Vacation

On my way to South Korea! Will be spending my vacation there for the next three weeks.







On the flight I hope to set up my world wind with a local tile server for doing further development on download code. Maybe some more updates soon.

Saturday, April 18, 2009

Java HashMap vs. C# Hashtable

One of the things I have found has been more frustrating while picking up C# .NET for the World Wind project is that some of the simple basic programming constructs that are automatic for me in Java don't seem to be directly parallel in C#. On top of that the online documentation at the MSDN site and other places is either not intuitive or I am looking in the wrong areas.

One of the basic data structures I depend on in Java are Maps. One of the simple classes that implement the Map interface in Java is java.util.HashMap.

Like other data structure objects in Java, there is no special syntax to interact with this object, it has methods like other objects where you pass in arguments and get return values.

The most basic usage of the HashMap generally goes like this:

//construct the new map
HashMap javaMap = new HashMap();
//enter a new key value pair
javaMap.put("key","value");
//to get the all values out of the map
Set keys = javaMap.keySet();
Iterator keyIter = keys.iterator();
while(keyIter.hasNext()){
String key = (String) keyIter.next();
String value = (String) javaMap.get(key);
System.out.println(key+" = "+value);
}






So it is pretty straightforward usage (in my mind), the put method "puts" data into the Map and the "get" method retrieves data from the Map given a key. Great

C# on the other hand uses a more elaborate syntax and combination of properties, keywords and methods. Not that this is more difficult but it took some time for me to figure out for example, that there was no equivalent to the "get" method in Hashtable, that you needed to use the "[]" syntax as if this were like an array

For the equivalent C# as the above, you can use the System.Collections.Hashtable class which implements the IDictionary interface:

//construct the Hashtable
Hashtable cSharpTable = new Hashtable();
//enter new key value pairs
cSharpTable.Add("key", "value");
cSharpTable.Add("hello", "world");
//for all Keys, a public property on Hashtable class, print out the key and value
foreach (String key in cSharpTable.Keys)
{
String value = (String) cSharpTable[key];
System.Console.WriteLine(key + " = " + value);
}


So in some ways the C# code is definitely more concise. But for a Java programmer it feels a bit strange, instead of 3 methods in Java(all with clear Javadoc descriptions of use, IDE auto-complete and tool tips, same syntax as all other method calls in Java etc..) for put(),get(),keySet(), C# requires you to use three different language features, a method, Add(), a property on the object Keys (why not just use a method?), and a [] operator. Granted in Visual Studio 2005 typing "cSharpTable." and you would see a list of the public methods and properties including the Add() and Keys, but you would only see the auto-assist tool-tip if you knew to type "cSharpTable[" and then it would give you the basic description of the syntax "object Hashtable[object key]".

I guess it is just something I'll have to learn.

I basically learned Java on the fly on my first job. I learned it fairly easily because once you got it that everything is just a method its pretty easy to find and locate what you need, plus the clear and standardized format of Javadoc based sites makes learning new classes or finding methods fairly simple. Try comparing the ease of use of Sun's Java API site with Microsoft's MSDN .NET Framework Reference(see link to HashMap and Hashtable classes above). Is it really an apples to oranges comparison? Or is the MSDN site just not very readable (for that one class there is no list of methods, just pages of examples in multiple languages.

References:
Holzner, Steven Microsoft Visual C# .NET 2003 Kick Start. Indianapolis: Sams Publishing, 2004. pp243-245

Saturday, April 11, 2009

Contribution Update

I went back and did some further rework for the changes to the "Download code" I submitted to the forum earlier. I posted these on the forum as well (still do not have commit access to SVN). There were no clear directions so I took some suggestions from forum users and combined that with already existing functionality (that was in the code but not used due to a bug I think I fixed).

I spent this day making these updates. Basically there were 3 files updated.

Changes include:
  • Now individual tiles will be put in a HashTable if they get 3 404 errors. They will be prevented from being readded to the download queue. Pressing 'F5' will clear this HashTable as will restarting the program.
  • After 5 failed tiles download attempts (tiles reaching the status above) the current anti-hammer logic kicks in to prevent further requests to that server for that layer. The default timeout period is 2 minutes before it will try to download for this layer again. Pressing 'Pressing F5' will clear this counter, and will stop the timeout and restart the download queue for this layer.
  • When the above condition is reached it will display a message in the top right saying "Problem connecting to server... Trying again in 2 minutes" when that layer is rendered after a timeout is reached. This stays visible for the first 20 seconds of the timeout then will be fade out...this logic was actually in there currently, but I was able to confirm that you can see it now (plus I extended the time it is visible from 15 to 20 seconds).