Search code examples
androidgsongeojsonosmdroid

Draw route on osmdroid map with osmbonuspack library


I have created osmdroid map view on which I display custom WMS, that's works. Next I need draw dinamically on this map route between 2 points which I get from custom service in GeoJSON format. GeoJSON looks like this: http://pastebin.com/GJWYNkAq

Calling service over OkHttp Client:

Request request = new Request.Builder()
                .url("http://xxx.xxx.xxx.x:7915/GeoService.svc/GetRoute?" + "source=" + encodedSourceAddress + "&target=" + encodedTargetAddress)
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(Call call, Response response) throws IOException {
                if (!response.isSuccessful())
                    throw new IOException("Unexpected code " + response);

                String result = response.body().string();
                Log.d("RESULT", result);
                viewRoute(result); //json is received ok, I debugged it

            }

        });

I used for this Osmbonuspack library in this method:

public void viewRoute(String geoJson) {
    KmlDocument kmlDocument = new KmlDocument();
    kmlDocument.parseGeoJSON(geoJson); //application is crashed here
    FolderOverlay myOverLay = (FolderOverlay) kmlDocument.mKmlRoot.buildOverlay(map, null, null, kmlDocument);
    map.getOverlays().add(myOverLay);
    map.invalidate();
}

When I run app, after launching is crashed with this error:

03-09 13:01:51.521 3968-3992/bachelor.vsb.martin.osmdroidclient E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher Process: bachelor.vsb.martin.osmdroidclient, PID: 3968 java.lang.ClassCastException: com.google.gson.JsonNull cannot be cast to com.google.gson.JsonObject at com.google.gson.JsonObject.getAsJsonObject(JsonObject.java:191) at org.osmdroid.bonuspack.kml.KmlPlacemark.(KmlPlacemark.java:89) at org.osmdroid.bonuspack.kml.KmlFeature.parseGeoJSON(KmlFeature.java:237) at org.osmdroid.bonuspack.kml.KmlFolder.(KmlFolder.java:62) at org.osmdroid.bonuspack.kml.KmlFeature.parseGeoJSON(KmlFeature.java:235) at org.osmdroid.bonuspack.kml.KmlDocument.parseGeoJSON(KmlDocument.java:1097) at org.osmdroid.bonuspack.kml.KmlDocument.parseGeoJSON(KmlDocument.java:1112) at bachelor.vsb.martin.osmdroidclient.MainActivity.viewRoute(MainActivity.java:137) at bachelor.vsb.martin.osmdroidclient.MainActivity$1.onResponse(MainActivity.java:127) at okhttp3.RealCall$AsyncCall.execute(RealCall.java:135) at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) at java.lang.Thread.run(Thread.java:761)

I am using osmdroid 5.6.4, and osmbonuspack 6.2


Solution

  • It looks like a bug in that library. The current implementation at the master branch is implemented like this:

    89: JsonObject geometry = json.getAsJsonObject("geometry");
    90: if (geometry != null) {
    

    Yes, getAsJsonObject can return null for not existing keys, but Gson uses JsonNull as the null value marker (and it makes sense) if the given JSON path value is known to be null. The only path that has the null in your JSON is $.features[6].geometry, so I'm assuming this one is the only cause. What you can do here is raising a new issue at their GitHub issues desk to add an appropriate JsonNull check in order not to fail at the type cast. A simulated case:

    final JsonObject geometry = (JsonObject) geoJsonObject.getAsJsonObject()
            .get("features")
            .getAsJsonArray()
            .get(6)
            .getAsJsonObject()
            .get("geometry"); // actually JsonNull for $.features[6].geometry
    

    What you can do here so far is:

    • Either remove the that path from the tree (I didn't analyze if that library can work with empty/incomplete geometry JsonPath elements, so probably just replacing that path value won't work).
    • Or remove the entire 7th feature object (this probably would be more appropriate, but it would cause some data loss).

    An example for the first option:

    final JsonObject geoJsonObject = gson.fromJson(geoJson, JsonObject.class);
    fixGeoJsonObject(geoJsonObject);
    kmlDocument.parseGeoJSON(geoJsonObject); // passing the "fixed" object
    

    where fixGeoJsonObject is as follows:

    private static void fixGeoJsonObject(final JsonObject geoJsonObject) {
        final JsonArray features = geoJsonObject
                .get("features")
                .getAsJsonArray();
        final int length = features.size();
        for ( int i = 0; i < length; i++ ) {
            final JsonObject feature = features.get(i)
                    .getAsJsonObject();
            final JsonElement geometry = feature.get("geometry");
            if ( geometry.isJsonNull() ) {
                feature.remove("geometry"); // losing some data...
            }
        }
    }
    

    or for the second option:

    private static void fixGeoJsonObject(final JsonObject geoJsonObject) {
        final JsonArray features = geoJsonObject
                .get("features")
                .getAsJsonArray();
        for ( int i = 0; i < features.size(); i++ ) {
            final JsonObject feature = features.get(i)
                    .getAsJsonObject();
            final JsonElement geometry = feature.get("geometry");
            if ( geometry.isJsonNull() ) {
                features.remove(i); // losing even more data...
            }
        }
    }
    

    This should fix the ClassCastException you're getting, but I have no clue how it would affect the library you're using.

    Edit 1: