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
}
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://test.com/uploader.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();
}
}
}