Emanuel Espinoza Emanuel Espinoza - 6 months ago 67
Android Question

Generate .kml file in Android from GoogleMapsApiV2

I'm making a Google Maps Application which one of the functions is to generate maps from user traveled distance. I do this by adding PolyLines to my map fragment so the user can see the path he just traveled. However, after the user is done recording his route I want to export those PolyLines as a .kml file so the user can travel the same path next time, but I don't know if I should use a XML parser to make the file myself or is there any way to actually export all the contents from the MapFragment directly to a .kml file through the GoogleMapsApiV2. Any suggestion? Thanks in advance.

Here is the code where I generate the PolyLines and the one I would want to export in .kml

@Override
public void onLocationChanged(Location location) {
// TODO Auto-generated method stub


//Comienzo de trazado

LatLng curr = new LatLng(location.getLatitude(), location.getLongitude());

if(flag==0) //when the first update comes, we have no previous points,hence this
{
prev=curr;
flag=1;
}
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(curr, 17);
mMap.animateCamera(update);
mMap.addPolyline((new PolylineOptions())
.add(prev, curr).width(6).color(Color.BLUE)
.visible(true));
prev=curr;
curr = null;

// Fin de trazado



}

Answer

Well I came with my own solution, sorry if the code is a little messy, but reading another post with a piece of code that was of some help, and editing my class I came to this solution. With this class you can place a start marker, then record the path, and then place an end marker. Unfortunately I haven't managed to calculate the distance of the walked path, but at least I can generate the .kml file of what I did and then upload it to a server.

public class PathGoogleMapActivity extends FragmentActivity implements OnMapReadyCallback, LocationListener {

private GoogleMap mMap;
private int serverResponseCode = 0;
private ProgressDialog dialog = null;
Marker marker;
private String upLoadServerUri = null;
private String imagepath=null;
private FeedItem feed;
LatLng latlon;
XmlSerializer xmlSerializer;
File file;
private KmlLayer layer,layer1;
Button button,Pausa,Foto;
String kml,id,nombre,tipo,imagen,tiempo,distancia,urlkml;
Criteria criteria;
double lati,loni,lata,lona,latf,lonf;
double speed,distance;
TextView txt1,txt2,txt3,txt4,StopWatch;
SupportMapFragment mapFragment;
private LocationManager locationManager;
private static final int MIN_TIME = 1;
private static final int MIN_DISTANCE = 0;
float res[] =new float[9];
static int elapsedTime=0,flag=0,x=0;
private long startTime = 0L;
static final int REQUEST_IMAGE_CAPTURE = 1;
boolean isPaused = false;
LatLng prev,curr;
FileOutputStream fileos;
StringBuilder sb;
List lats,lons,latfi,lonfi,coorla,coorlo;

private Handler customHandler = new Handler();

long timeInMilliseconds = 0L;
long timeSwapBuff = 0L;
long updatedTime = 0L;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_maps);
    if (android.os.Build.VERSION.SDK_INT > 9) {
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
    }
    // Obtain the SupportMapFragment and get notified when the map is ready to be used.
    mapFragment = (SupportMapFragment) getSupportFragmentManager()
            .findFragmentById(R.id.map);

    locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, MIN_TIME, MIN_DISTANCE, this);
    }
mapFragment.getMapAsync(this);

    Intent inti=getIntent();


    StopWatch=(TextView)findViewById(R.id.textView5);
    button=(Button)findViewById(R.id.btnTerminar);

    Pausa=(Button)findViewById(R.id.btnPausar);
    Foto=(Button)findViewById(R.id.btnFoto);

    txt1=(TextView)findViewById(R.id.textView);
    txt2=(TextView)findViewById(R.id.textView2);
    txt3=(TextView)findViewById(R.id.textView3);
    txt4=(TextView)findViewById(R.id.textView4);

    coorlo= new ArrayList<>();
    coorla= new ArrayList<>();

    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {


            /*
            * When I press this button, I gather all the info from the arraylists that has the
            * coordinates and then I generate the KML file using an xmlserializer to save the 
            * KML file into the app private storage and then later on I upload this to my webserver
            * for storing.
            * */

            Toast.makeText(getBaseContext(),"Generando mapa",Toast.LENGTH_LONG).show();

        try {



        }catch (Exception e) {


            }

            //In here I create the ending marker and store the end coordinates

            marker = mMap.addMarker(new MarkerOptions()
                    .position(latlon)
                    .title("Fin"));

            tiempo=StopWatch.getText().toString();
            distancia=txt2.getText().toString();

            latfi= new ArrayList<>();
            lonfi= new ArrayList<>();

            latfi.add(lata);
            lonfi.add(lona);

            /* In here I create the file and directory I'm going to use to store the 
            * KML data 
            * */

            fileos = null;
            sb = new StringBuilder();
            File root = getFilesDir();

            FileOutputStream fileos = null;
            StringBuilder sb = new StringBuilder();
            try {
                String rootu = Environment.getExternalStorageDirectory().toString();
                File myDir = new File(rootu + "/rutas");
                myDir.mkdirs();
                file = new File (myDir, "Ruta.kml");

                /*The filename for me it's always going to be the same since
                * I'm going to upload it to my server and my server handles
                * the saving and storage*/

                fileos = openFileOutput("Ruta.kml", Context.MODE_PRIVATE);

            } catch (FileNotFoundException e) {
                // Log.e("FileNotFoundException", e.toString());
                e.printStackTrace();
                Toast.makeText(PathGoogleMapActivity.this, "Error en outputstream", Toast.LENGTH_SHORT).show();

            }

            // Here begins the KML creation
            try {

                xmlSerializer = XmlPullParserFactory.newInstance().newSerializer();
            } catch (XmlPullParserException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
                Toast.makeText(PathGoogleMapActivity.this, "Error en serializer", Toast.LENGTH_SHORT).show();
            }

            try {

                /* This KML file has a really basic structure, if you need
                * styles and other fancy stuff, you are always free to add
                * those style lines or anything you might need in order to 
                * make it look goof :) */

                xmlSerializer.setOutput(fileos, "UTF-8");
                xmlSerializer.startDocument(null, null);
                xmlSerializer.setFeature(
                        "http://xmlpull.org/v1/doc/features.html#indent-output",
                        true);
                xmlSerializer.startTag(null, "kml");

                xmlSerializer.startTag(null, "Document");

                xmlSerializer.startTag(null, "name");
                xmlSerializer.text("Ruta de Usuario ");
                xmlSerializer.endTag(null, "name");

                xmlSerializer.startTag(null, "description");
                xmlSerializer.cdsect("Ruta generada por: Usuario");
                xmlSerializer.endTag(null, "description");

                xmlSerializer.startTag(null, "Placemark");
                xmlSerializer.startTag(null, "name");
                xmlSerializer.text("Inicio");
                xmlSerializer.endTag(null, "name");
                xmlSerializer.startTag(null, "description");
                xmlSerializer.cdsect("");
                xmlSerializer.endTag(null, "description");
                xmlSerializer.startTag(null, "Point");
                xmlSerializer.startTag(null, "coordinates");

                xmlSerializer.text(Double.toString(((Double) lons.get(0))) + ","
                        + Double.toString((Double) lats.get(0)) + ",0.000000");
                xmlSerializer.endTag(null, "coordinates");
                xmlSerializer.endTag(null, "Point");
                xmlSerializer.endTag(null, "Placemark");

                // Poligons

                xmlSerializer.startTag(null, "Placemark");
                xmlSerializer.startTag(null, "name");
                xmlSerializer.text("Ruta del Usuario ");
                xmlSerializer.endTag(null, "name");
                xmlSerializer.startTag(null, "description");
                xmlSerializer.cdsect("");
                xmlSerializer.endTag(null, "description");

                xmlSerializer.startTag(null, "LineString");
                xmlSerializer.startTag(null, "tessellate");
                xmlSerializer.text("1");
                xmlSerializer.endTag(null, "tessellate");
                xmlSerializer.startTag(null, "coordinates");
                sb.setLength(0);

                /*This is the part where I get the size of the path arraylist
                * and start printing the coordinates for my path*/

                int size=coorlo.size();
                for(x=0;x<size;x++) {

                    sb.append(Double.toString((Double) coorlo.get(x)));
                    sb.append(",");
                    sb.append(Double.toString((Double) coorla.get(x)));
                    sb.append(",0\n");
                }

                sb.setLength(sb.length() - 2);
                String s = sb.toString();
                xmlSerializer.text(s);
                xmlSerializer.endTag(null, "coordinates");
                xmlSerializer.endTag(null, "LineString");
                xmlSerializer.endTag(null, "Placemark");

                xmlSerializer.startTag(null, "Placemark");
                xmlSerializer.startTag(null, "name");
                xmlSerializer.text("Fin");
                xmlSerializer.endTag(null, "name");
                xmlSerializer.startTag(null, "description");
                xmlSerializer.cdsect("");
                xmlSerializer.endTag(null, "description");
                xmlSerializer.startTag(null, "Point");
                xmlSerializer.startTag(null, "coordinates");

                xmlSerializer.text(Double.toString((Double) lonfi.get(0)) + ","
                        + Double.toString((Double) latfi.get(0)) + ",0.000000");
                xmlSerializer.endTag(null, "coordinates");
                xmlSerializer.endTag(null, "Point");
                xmlSerializer.endTag(null, "Placemark");

                xmlSerializer.endTag(null, "Document");
                xmlSerializer.endTag(null, "kml");
                xmlSerializer.endDocument();
                xmlSerializer.flush();
                fileos.close();

            /* Once all is done and the file is generated I call my upload function to upload the file 
            * into my webserver. PS: You should use an ASYNCTASK to do things like this, I'm gonna
            * correct this thing later*/

                String absolutePath = getBaseContext().getFileStreamPath("Ruta.kml").toString();
                uploadFile(absolutePath);



            }catch (IOException e) {
                e.printStackTrace();
                // Log.e("Exception", "Exception occured in wroting");
                Toast.makeText(PathGoogleMapActivity.this, "Error en la generacion del archivo", Toast.LENGTH_SHORT).show();
            }

        }
    });






}




/**
 * Manipulates the map once available.
 * This callback is triggered when the map is ready to be used.
 * This is where we can add markers or lines, add listeners or move the camera. In this case,
 * we just add a marker near Sydney, Australia.
 * If Google Play services is not installed on the device, the user will be prompted to install
 * it inside the SupportMapFragment. This method will only be triggered once the user has
 * installed Google Play services and returned to the app.
 */
@Override
public void onMapReady(GoogleMap googleMap) {
    mMap = googleMap;

    LatLng ecuador = new LatLng( -1.7873763, -82.6352677);

    mMap.moveCamera(CameraUpdateFactory.newLatLng(ecuador));

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {
        mMap.setMyLocationEnabled(true);



        LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
        Criteria criteria = new Criteria();

        Location location = locationManager.getLastKnownLocation(locationManager.getBestProvider(criteria, false));
        if (location != null)
        {
            mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(
                    new LatLng(location.getLatitude(), location.getLongitude()), 13));

            CameraPosition cameraPosition = new CameraPosition.Builder()
                    .target(new LatLng(location.getLatitude(), location.getLongitude()))      // Sets the center of the map to location user
                    .zoom(17)                   // Sets the zoom
                    .bearing(90)                // Sets the orientation of the camera to east
                    .tilt(40)                   // Sets the tilt of the camera to 30 degrees
                    .build();                   // Creates a CameraPosition from the builder
            mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));

            lati=location.getLatitude();
            loni=location.getLongitude();

            latlon = new LatLng(lati, loni);

            // Once the map is loaded and we got the coordinates we create the start marker

            marker = mMap.addMarker(new MarkerOptions()
                    .position(latlon)
                    .title("Inicio"));

            //We declare the arraylists that will store the coordinates of the start marker to print them
            //later on in our KML file

             lats= new ArrayList<>();
            lons= new ArrayList<>();

            //We add the coordinates to the array

            lats.add(lati);
            lons.add(loni);

/* Ignore this, this is a timer I added for my app purposes*/

            startTime = SystemClock.uptimeMillis();
            customHandler.postDelayed(updateTimerThread, 0);

        }




    } else {
        // Show rationale and request permission.

        Toast.makeText(PathGoogleMapActivity.this,"Adquiriendo datos del GPS. Intente de nuevo.",Toast.LENGTH_LONG).show();
        finish();
    }


}


private void moveCameraToKml(KmlLayer kmlLayer) {
    //Retrieve the first container in the KML layer
    KmlContainer container = kmlLayer.getContainers().iterator().next();
    //Retrieve a nested container within the first container
    container = container.getContainers().iterator().next();
    //Retrieve the first placemark in the nested container
    KmlPlacemark placemark = container.getPlacemarks().iterator().next();
    //Retrieve a polygon object in a placemark

    //Create LatLngBounds of the outer coordinates of the polygon
    LatLngBounds.Builder builder = new LatLngBounds.Builder();

    mMap.moveCamera(CameraUpdateFactory.newLatLngBounds(builder.build(), 1));
}


@Override
public void onLocationChanged(Location location) {
    // TODO Auto-generated method stub

    // Toast.makeText(MapsActivity.this,"Posicion cambiada",Toast.LENGTH_LONG).show();

    //Path trace begins

    LatLng curr = new LatLng(location.getLatitude(), location.getLongitude());

    //We add our current coordinates to the path arraylist to print them later in the KML file

    coorla.add(location.getLatitude());
    coorlo.add(location.getLongitude());

    if (flag == 0)  //when the first update comes, we have no previous points, so we add the current coordinates
    {
        prev=curr;
        flag=1;
    }
    CameraUpdate update = CameraUpdateFactory.newLatLngZoom(curr, 17);
    mMap.animateCamera(update);
    mMap.addPolyline((new PolylineOptions())
            .add(prev, curr).width(6).color(Color.BLUE)
            .visible(true));




    prev=curr;
    curr = null;

    // End of tracing

    /*This block is supposed to get the distance and speed
    in KM between the 2 last coordinate points, however
    its not working, if you can tell me how to fix it would be
     awesome :( */

    lata=location.getLatitude();
    lona=location.getLongitude();

    latlon = new LatLng(lata, lona);

    txt3.setText(Double.toString(lata));
    txt4.setText(Double.toString(lona));


    if(lati>0&&loni>0&&lata>0&&lona>0){        Location.distanceBetween(lati, loni, lata, lona,res);

        Toast.makeText(PathGoogleMapActivity.this,"Distancia actualizada",Toast.LENGTH_LONG).show();

    }else{

        res[0]=0;

    }

    txt2.setText(Float.toString(res[0])+" km");

    double vel=res[0]/1200;

    txt1.setText(Double.toString(vel) + " Km/h");
}

@Override
public void onStatusChanged(String provider, int status, Bundle extras) {

}

@Override
public void onProviderEnabled(String provider) {

}

@Override
public void onProviderDisabled(String provider) {

}

private Runnable updateTimerThread = new Runnable() {

    //This is the thread I use for my timer feel free to ignore this as well

    public void run() {

        timeInMilliseconds = SystemClock.uptimeMillis() - startTime;

        updatedTime = timeSwapBuff + timeInMilliseconds;

        int secs = (int) (updatedTime / 1000);
        int mins = secs / 60;
        secs = secs % 60;
        int milliseconds = (int) (updatedTime % 1000);
        StopWatch.setText("" + mins + ":"
                + String.format("%02d", secs) + ":"
                + String.format("%03d", milliseconds));
        customHandler.postDelayed(this, 0);
    }

};




public int uploadFile(String sourceFileUri) {

    /*
    * Because my app needs it I upload the KML file to my server.
    * The upload is sent to a webservice via POST and the PHP
    * handles the file storeage and then generates a JSON that
    * gives me the KML file URL
    * */

    upLoadServerUri = "http://img.ecqpon.com/appuploader.php";

    String fileName = sourceFileUri;

    HttpURLConnection conn = null;
    DataOutputStream dos = null;
    String lineEnd = "\r\n";
    String twoHyphens = "--";
    String boundary = "*****";
    int bytesRead, bytesAvailable, bufferSize;
    byte[] buffer;
    int maxBufferSize = 1 * 1024 * 1024;
    File sourceFile = new File(sourceFileUri);

    if (!sourceFile.isFile()) {


        Log.e("uploadFile", "Source File not exist :"+imagepath);

        runOnUiThread(new Runnable() {
            public void run() {

                Toast.makeText(PathGoogleMapActivity.this, "Source File not exist :"+ imagepath, Toast.LENGTH_SHORT).show();
            }
        });

        return 0;

    }
    else
    {
        try {

            // open a URL connection to the Servlet
            FileInputStream fileInputStream = new FileInputStream(sourceFile);
            URL url = new URL(upLoadServerUri);
            InputStream is = null;
            JSONObject jObj = null;
            String json = null;

            // Open a HTTP  connection to  the URL
            conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true); // Allow Inputs
            conn.setDoOutput(true); // Allow Outputs
            conn.setUseCaches(false); // Don't use a Cached Copy
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("ENCTYPE", "multipart/form-data");
            conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
            conn.setRequestProperty("file", fileName);

            dos = new DataOutputStream(conn.getOutputStream());

            dos.writeBytes(twoHyphens + boundary + lineEnd);
            dos.writeBytes("Content-Disposition: form-data; name=\"file\";filename=\""
                    + fileName + "\"" + lineEnd);

            dos.writeBytes(lineEnd);

            // create a buffer of  maximum size
            bytesAvailable = fileInputStream.available();

            bufferSize = Math.min(bytesAvailable, maxBufferSize);
            buffer = new byte[bufferSize];

            // read file and write it into form...
            bytesRead = fileInputStream.read(buffer, 0, bufferSize);

            while (bytesRead > 0) {

                dos.write(buffer, 0, bufferSize);
                bytesAvailable = fileInputStream.available();
                bufferSize = Math.min(bytesAvailable, maxBufferSize);
                bytesRead = fileInputStream.read(buffer, 0, bufferSize);

            }

            // send multipart form data necesssary after file data...
            dos.writeBytes(lineEnd);
            dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);

            // Responses from the server (code and message)
            serverResponseCode = conn.getResponseCode();
            String serverResponseMessage = conn.getResponseMessage();



             is = conn.getInputStream();

            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    is, "iso-8859-1"), 8);
            StringBuilder sb = new StringBuilder();
            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line + "\n");
            }
            is.close();
            json = sb.toString();

            try {
                jObj = new JSONObject(json);
            } catch (JSONException e) {
                Log.e("JSON Parser", "Error parsing data " + e.toString());
            }

            parseJson(jObj);

            Log.i("uploadFile", "HTTP Response is : "
                    + serverResponseMessage + ": " + serverResponseCode);

            if(serverResponseCode == 200){

                runOnUiThread(new Runnable() {
                    public void run() {
                        String msg = "File uploaded to server";
                        // messageText.setText(msg);
                        Toast.makeText(PathGoogleMapActivity.this, "Kml subido al servidor.", Toast.LENGTH_SHORT).show();
                    }
                });
            }

            //close the streams //
            fileInputStream.close();
            dos.flush();
            dos.close();

            //Toast.makeText(PathGoogleMapActivity.this, "Url: "+urlkml, Toast.LENGTH_SHORT).show();

            /*This is supposed to give me the distance between the begin and end marker
            however this is not working either */

            int Radius=6371;//radius of earth in Km
            double lat1 = (Double)lats.get(0);
            double lat2 = (Double)latfi.get(0);
            double lon1 = (Double)lons.get(0);
            double lon2 = (Double)lonfi.get(0);
            double dLat = Math.toRadians(lat2-lat1);
            double dLon = Math.toRadians(lon2-lon1);
            double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
                    Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) *
                            Math.sin(dLon/2) * Math.sin(dLon/2);
            double c = 2 * Math.asin(Math.sqrt(a));
            double valueResult= Radius*c;
            double km=valueResult/1;
            DecimalFormat newFormat = new DecimalFormat("####");
            int kmInDec =  Integer.valueOf(newFormat.format(km));
            double meter=valueResult%1000;
            int  meterInDec= Integer.valueOf(newFormat.format(meter));
            Log.i("Radius Value",""+valueResult+"   KM  "+kmInDec+" Meter   "+meterInDec);

        Double kms=Radius * c;

            /*
            * Finally after I get the response from my webservice that the kml file
            * has been uploaded to the server, I send the url of the kml file in this
            * intent to ask some more data to save the record in my MySQL database
            * but I do this because my app needs it you feel free to do whatever you
            * want with this information
            * */

            Intent map=new Intent(PathGoogleMapActivity.this, SaveActivity.class);

            map.putExtra("Urlkml", urlkml);
            map.putExtra("Kms", kms);
            startActivity(map);
            finish();

        } catch (MalformedURLException ex) {


            ex.printStackTrace();

            runOnUiThread(new Runnable() {
                public void run() {
                    //messageText.setText("MalformedURLException Exception : check script url.");
                    Toast.makeText(PathGoogleMapActivity.this, "MalformedURLException", Toast.LENGTH_SHORT).show();
                }
            });

            Log.e("Upload file to server", "error: " + ex.getMessage(), ex);
        } catch (Exception e) {

            //dialog.dismiss();
            e.printStackTrace();

            runOnUiThread(new Runnable() {
                public void run() {
                    // messageText.setText("Got Exception : see logcat ");
                    Toast.makeText(PathGoogleMapActivity.this, "Error : ver logcat ", Toast.LENGTH_SHORT).show();
                }
            });
            Log.e("Excepci├│n de subida", "Exception : "  + e.getMessage(), e);
        }

// dialog.dismiss(); return serverResponseCode;

    } // End else block
}


public void parseJson(JSONObject json) {
    try {
/*
* I parse the JSON response I get from my server once I uploaded the   KML file to get the URL
* from the KML file once my server tells me it has been uploaded
* */
         JSONArray posts = json.getJSONArray("Archivo");


        for (int i = 0; i < posts.length(); i++) {
            JSONObject post = (JSONObject) posts.getJSONObject(i);
            FeedItem item = new FeedItem();

            //I store the KML url in a global variable

            urlkml=post.getString("Url");

        }

    } catch (JSONException e) {
        e.printStackTrace();
    }
}

}