Hi everyone,
I just downloaded the nutiteq libs to see how it works in my blackberry project and i'm having issues with the sample blackberry app. The tiles come in OK from openstreetmap for a bit, but then seem to just stop. I enabled the download monitor and I saw it get up to about 500kb and then the download monitor disappeared.
If I restart the app it seems to work just fine again. I wonder if maybe the http connection timeout flag is not being set. I've found before that I need to do a little trick when opening connections:
while (true) {
try {
streamConnection = (StreamConnection) Connector.open(url.toString() + ";deviceside=true", Connector.READ_WRITE, true);
break;
} catch (IOException e) {
Logger.log("connection timed out:" + url);
}
}
My code is pretty much identical to the sample app, but i'll post it anyways:
public MapDemoScreen() {
final DefaultDownloadStreamOpener opener = new DefaultDownloadStreamOpener(";deviceside=true");
mapComponent = new MapComponent("abcdtrial", "Nutiteq", "WrappedRIMUIDemo", SCREEN_WIDTH,
SCREEN_HEIGHT, new WgsPoint(-74.0, 40.717), 5);
final UserDefinedKeysMapping keysMapping = new UserDefinedKeysMapping();
keysMapping.defineKey(ControlKeys.MOVE_UP_KEY, 84);
keysMapping.defineKey(ControlKeys.MOVE_UP_KEY, 69);
keysMapping.defineKey(ControlKeys.MOVE_DOWN_KEY, 66);
keysMapping.defineKey(ControlKeys.MOVE_DOWN_KEY, 88);
keysMapping.defineKey(ControlKeys.MOVE_LEFT_KEY, 83);
keysMapping.defineKey(ControlKeys.MOVE_RIGHT_KEY, 70);
keysMapping.defineKey(ControlKeys.MOVE_RIGHT_KEY, 74);
keysMapping.defineKey(ControlKeys.ZOOM_OUT_KEY, 20);
keysMapping.defineKey(ControlKeys.ZOOM_OUT_KEY, 65);
keysMapping.defineKey(ControlKeys.ZOOM_OUT_KEY, 79);
keysMapping.defineKey(ControlKeys.ZOOM_IN_KEY, 261);
keysMapping.defineKey(ControlKeys.ZOOM_IN_KEY, 81);
keysMapping.defineKey(ControlKeys.ZOOM_IN_KEY, 73);
mapComponent.enableDownloadCounter();
mapComponent.enableDownloadDisplay();
mapComponent.showZoomLevelIndicator(true);
mapComponent.showDefaultOnScreenZoomControls();
mapComponent.setControlKeysHandler(keysMapping);
mapComponent.setDownloadStreamOpener(opener);
mapComponent.setMapListener(this);
mapComponent.showZoomLevelIndicator(true);
// mapComponent.addKmlService(new KmlUrlReader(
// "http://www.panoramio.com/panoramio.kml?LANG=en_US.utf8", true));
// mapComponent.addKmlService(new KmlUrlReader(
// "http://lbs.nutiteq.ee/mps/kmlpf.php?friend=555111,555222,555333,", false));
mapComponent.startMapping();
testPlace = new Place(0, "Test Place", new TestPlacemark(), new WgsPoint(-74.0, 40.717));
mapComponent.addPlace(testPlace);
}
public void paint(final Graphics g) {
//wrap the graphics object inside library Graphics
if (wrapped != g) {
wrapped = g;
graphicsWrapper = new com.nutiteq.wrappers.rimui.Graphics(g);
}
//paint on wrapper (in effect painting on native graphics)
mapComponent.paint(graphicsWrapper);
//remove pushContext() done inside library
graphicsWrapper.popAll();
}
protected boolean navigationMovement(final int dx, final int dy, final int status, final int time) {
mapComponent.panMap(dx * 10, dy * 10);
invalidate();
return true;
}
I'd appreciate any suggestions. I am really interesting in moving our app over to this library as it should make it easier to port in the future.
Yes, OSM tile server is not really meant or suitable for commercial services. It has been unstable, it can be down for couple of days. See also http://wiki.openstreetmap.org/wiki/Tile_usage_policy
I take that back, it's not really solved using cloudmade. I am still having the problem where it there are no tiles coming in for minutes on end.
I've tried using the cloudmade server directly in my browser like http://b.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/7056/256/15/17600/10746.png and the most it ever takes is a few seconds. Is there a way to force the downloader to recognize timeouts?
What is your connection mode (MDS, direct TCP, Wifi) ? According to http://www.blackberry.com/knowledgecenterpublic/livelink.exe/fetch/2000/348583/800451/800563/How_To_-_Control_the_connection_timeout_for_TCP_connections_through_BlackBerry_Mobile_Data_System_Connection_Service.html?nodeid=1235131&vernum=0 I would suggest to try something like that:
map.setDownloadStreamOpener(new DefaultDownloadStreamOpener(";ConnectionTimeout=2000;deviceside=false"));
Technically you can also implement your own DownloadStreamOpener, but it is quite complex (HTTP forwardings etc) and it bases on HTTPConnection, not StreamConnection.
I'm running in the simulator, so technically the connection is broadband. I have been using ';deviceside=true'. I tried your suggestion of setting it false but it doesn't work as I don't have an MDS server running. I'd prefer to use direct TCP anyways as it is much more straightforward.
The 'freezing' does seem to last right around 2 minutes, which makes me think it must be timing out. that article also says: "Only TCP connections, made through the BlackBerry MDS Connection Service, support this parameter."
I'd really like to be able to just change the call to 'Connector.open' to be :
streamConnection = (StreamConnection) Connector.open(url.toString() + ";deviceside=true", Connector.READ_WRITE, true);
Is it possible to do that without writing my own streamer?
The issue here is that library uses HTTPConnection, not StreamConnection. If you want to use StreamConnection, then it probably needs not only new ConnectionOpener, but it could need to rewrite other parts of downloader, and currently there is no public API for it.
However, for modified DefaultDownloadStreamOpener.java you can use existing one as template, maybe some basic changes here would work for you:
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import com.nutiteq.log.Log;
import com.nutiteq.utils.IOUtils;
/**
* Default stream opener used inside library. Handles cleanup for resources
* opened by it.
*
* Status codes 200 (OK) and 304 (not modified) are handled the same way - with
* data read.
*
* This implementation tries to follow up to 3 redirects (HTTP status codes 301,
* 302, 307). If it is not successful, then an error notification will be sent
* to stream waiter.
*/
public class DefaultDownloadStreamOpener implements DownloadStreamOpener {
private static final String HTTP_REDIRECT_LOCATION_HEADER = "Location";
private final String urlExtension;
private final Hashtable properties = new Hashtable();
private static final int MAX_FOLLOWED_REDIRECTS = 3;
/**
* @param urlExtension
* Optional extension for http URLs, for instance ";deviceside=true".
* Used on some Blackberry devices.
*/
public DefaultDownloadStreamOpener(final String urlExtension) {
this.urlExtension = urlExtension;
}
public DefaultDownloadStreamOpener() {
urlExtension = "";
}
/**
* Add request properties, that will be added added to every request (for
* example User-Agent).
*
* @param propertyName
* request property name
* @param propertyValue
* request property value
*/
public void addRequestProperty(final String propertyName, final String propertyValue) {
properties.put(propertyName, propertyValue);
}
public void openInputStream(final DownloadStreamWaiter streamWaiter, final String url) {
openInputStream(streamWaiter, url, 0, null);
}
public void openInputStream(final DownloadStreamWaiter streamWaiter,
final DataPostingDownloadable postingDownloadable) {
openInputStream(streamWaiter, postingDownloadable.getUrl(), 0, postingDownloadable);
}
private void openInputStream(final DownloadStreamWaiter streamWaiter, final String url,
final int redirects, final DataPostingDownloadable downloadable) {
final String downloadableUrl = url + urlExtension;
final DataInputStream dis = null;
HttpConnection connection = null;
InputStream is = null;
String redirectUrl = null;
try {
final long startTime = System.currentTimeMillis();
Log.info("Downloading " + downloadableUrl);
connection = (HttpConnection) Connector.open(downloadableUrl, Connector.READ_WRITE, true);
connection.setRequestMethod(HttpConnection.GET);
connection.setRequestProperty("Cache-Control", "No-Transform");
if (properties.size() > 0) {
final Enumeration keysEnum = properties.keys();
while (keysEnum.hasMoreElements()) {
final String key = (String) keysEnum.nextElement();
final String value = (String) properties.get(key);
connection.setRequestProperty(key, value);
}
}
if (downloadable != null) {
final byte[] dataBytes = downloadable.getPostContent().getBytes("iso-8859-1");
connection.setRequestProperty("Content-Length", Integer.toString(dataBytes.length));
connection.setRequestProperty("content-type", downloadable.getContentType());
final OutputStream dos = connection.openOutputStream();
dos.write(dataBytes);
}
is = connection.openInputStream();
final int responseCode = connection.getResponseCode();
Log.debug("Connection opened in " + (System.currentTimeMillis() - startTime));
if (responseCode == HttpConnection.HTTP_OK
|| responseCode == HttpConnection.HTTP_NOT_MODIFIED) {
final long processStart = System.currentTimeMillis();
streamWaiter.streamOpened(is);
Log.debug("Response read in " + (System.currentTimeMillis() - processStart));
} else if (responseCode == HttpConnection.HTTP_TEMP_REDIRECT
|| responseCode == HttpConnection.HTTP_MOVED_PERM
|| responseCode == HttpConnection.HTTP_MOVED_TEMP) {
redirectUrl = connection.getHeaderField(HTTP_REDIRECT_LOCATION_HEADER);
Log.debug("Redirect to " + redirectUrl);
} else {
streamWaiter.error(RESPONCE_NOT_OK, "");
}
} catch (final IOException e) {
Log.error("Downloader: " + e.getMessage());
streamWaiter.error(NETWORK_ERROR, e.getMessage());
} catch (final SecurityException e) {
Log.error("Downloader security: " + e.getMessage());
streamWaiter.error(SECURITY_EXCEPTION, e.getMessage());
} finally {
IOUtils.closeStream(dis);
IOUtils.closeStream(is);
IOUtils.closeConnection(connection);
}
if (redirects == MAX_FOLLOWED_REDIRECTS && redirectUrl != null) {
streamWaiter.error(TOO_MANY_REDIRECTS, "Too manu redirects created!");
return;
}
if (redirectUrl != null) {
openInputStream(streamWaiter, redirectUrl, redirects + 1, downloadable);
}
}
}
The problem seems to be that larger downloads (over 40KB or so per tile) block sometimes reading in java.io.InputStream.read , until it is timed out. Timeout is 2 minutes. These blockings seems to be most likely Blackberry-specific. One, and in several reasons good, solution would be to avoid so big tiles. Compress them, use less colors (e.g. 16 indexed colors), this way I have usually tiles below 20 KB. Also 128-pixel tile may help, but this has also penalty: creating new http connections in BB could be quite slow.
Latest library pre-relase 1.0.2 (get it from nutiteq.com/beta/lib) has now configurable TCP read timeout for data reads. Set it to for example 15 seconds:
...
final DefaultDownloadStreamOpener opener = new DefaultDownloadStreamOpener(";deviceside=true",15000);
mapComponent.setDownloadStreamOpener(opener);
...
I switched to cloudmade and the problem seems resolved. I guess it is openstreetmap that is just not responding at times.