My objective is to extract a route which some user would have created on Google Maps into a GPX file containing latitude longitudes for further use in other apps.
The route will be shared via a link like this - https://goo.gl/maps/HcikiDXFwN2coeFN8
Interestingly, this app is already doing what I want to do - https://mapstogpx.com/ and https://www.gpsvisualizer.com/convert_input?convert_format=gpx. I just want to know how they are doing it so that I can emulate it for my own needs.
Since both the tools I've mentioned also require providing a Google Maps API key with the directions API enabled, my initial guess is that these tools first parse the webpage for the waypoints and then use those waypoints in a Directions API call to get all the route trackpoints.
Thanks
The solution lies in the "data" param in the expanded url for the google maps route link.
Lets take this URL for example - https://goo.gl/maps/zrcP5gL1cd2AHGoq8
This expands to https://www.google.com/maps/dir/Eiffel+Tower,+Paris,+France/Palais+Garnier,+Pl.+de+l'Op%C3%A9ra,+75009+Paris,+France/#48.8606129,2.2961092,14z/am=t/data=!4m29!4m28!1m20!1m1!1s0x47e6701f7e8337b5:0xa2cb58dd28914524!2m2!1d2.2930458!2d48.8560934!3m4!1m2!1d2.3122286!2d48.8490916!3s0x47e6702fa62d0bc5:0xd2d94ed604f2e5a0!3m4!1m2!1d2.3035972!2d48.8729816!3s0x47e66fc1755cf609:0x3c5040f902b41a5f!3m4!1m2!1d2.3235117!2d48.8581242!3s0x47e66e2ac3bca3ed:0x1c289763e3096e61!1m5!1m1!1s0x47e66e30d4668339:0xa9abf21c286d0767!2m2!1d2.3316014!2d48.8719697!3e0
On closer examination, you will see that the data param has lat-lng coordinates embedded in it. For example this
!1d2.2930458!2d48.8560934 or !1d2.3122286!2d48.8490916. These are longitude latitude pairs representing various waypoints on the route. The longitude always starts with !1d and ends with !2d and the latitude starts with !2d and ends with !3.
Here is a small kotlin method to extract all the coordinates :
//https://stackoverflow.com/a/62191093/3090120
fun String?.indexesOf(pat: String, ignoreCase: Boolean = true): List<Int> =
pat.toRegex(if (ignoreCase) setOf(RegexOption.IGNORE_CASE) else emptySet())
.findAll(this ?: "")
.map { it.range.first }
.toList()
fun extractCoordinates(expandedUrl: String): List<LatLng> {
val latLngList = arrayListOf<LatLng>()
val indexes = expandedUrl.indexesOf("!1d")
indexes.forEach {
val coordinatesStr = expandedUrl.substring(it + 3).substringBefore("!3").split("!2d")
latLngList.add(LatLng(coordinatesStr[1].toDouble(), coordinatesStr[0].toDouble()))
}
return latLngList
}
The result with this string would be
[lat/lng: (48.8560934,2.2930458), lat/lng: (48.8490916,2.3122286), lat/lng: (48.8729816,2.3035972), lat/lng: (48.8581242,2.3235117), lat/lng: (48.8719697,2.3316014)]
Once we have all the lat-lng pairs we can simply fire off a Google Maps Directions API call and recreate the route in the link and export it as a GPX file.
Related
I am working on a project where users can add the addresses of their clients. I managed to make these addresses to be interactive so that when you tap on them a sheet shows the option to open either GoogleMaps or Apple Maps. However I am not sure how to set the specific address I tap to be the destination when the map opens. This is what I have:
.actionSheet(isPresented: $showingSheet){
let latitude = 51.49
let longitude = -30
let appleURL = "http://maps.apple.com/?daddr=\(latitude),\(longitude)"
let googleURL = "comgooglemaps://?daddr=\(latitude),\(longitude)&directionsmode=driving"
//let wazeURL = "waze://?ll=\(latitude),\(longitude)&navigate=false"
let googleItem = ("Google Map", URL(string:googleURL)!)
//let wazeItem = ("Waze", URL(string:wazeURL)!)
var installedNavigationApps = [("Apple Maps", URL(string:appleURL)!)]
if UIApplication.shared.canOpenURL(googleItem.1) {
installedNavigationApps.append(googleItem)
}
// if UIApplication.shared.canOpenURL(wazeItem.1) {
// installedNavigationApps.append(wazeItem)
// }
var buttons: [ActionSheet.Button] = []
for app in installedNavigationApps {
let button: ActionSheet.Button = .default(Text(app.0)) {
UIApplication.shared.open(app.1, options: [:], completionHandler: nil)
}
buttons.append(button)
}
let cancel: ActionSheet.Button = .cancel()
buttons.append(cancel)
return ActionSheet(title: Text("Navigate"), message: Text("Select an app..."), buttons: buttons)
Any suggestions?
Both Apple Maps and Google Maps have a similar URL scheme for opening their map for driving directions.
Apple Maps reference: https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/MapLinks/MapLinks.html
Google Maps URL scheme for iOS: https://developers.google.com/maps/documentation/urls/ios-urlscheme#directions
In both cases, the daddr value you're passing can be sent a full address, which the Maps product will then geocode and pinpoint.
You're currently sending a stringified version of your lat/lng coordinates in that string, which each maps product can use to derive a location. But there neither scheme seems to support an option of "here are the coordinates I want to use, and this is the address I want to show" – it's one or the other.
So if you take the address you have and enter it as a single string (don't forget to URL encode it – you may find it easiest to assemble your URL using the URLComponents and URLQueryItem helper objects) you ought to be able to use that encoded string as a new daddr value.
So, I set up my firebase to communicate with my web app which uses google maps api and my goal is this: When a user draws a shape on the map(polygon, linestring), I want to send the geoJson value of it to the firebase(currently sending it as a String), and then retrieve it back so it appears on the map for everyone(since it's getting synced from the firebase database). My problem is that when I try to retrieve the geoJson data back and add it on google maps, at the line map.data.addGeoJson(geoJsonString);(geoJsonString = geoJson value that is stored in firebase) I get an error saying:
Uncaught Jb {message: "not a Feature or FeatureCollection", name: "InvalidValueError", stack: "Error↵ at new Jb (https://maps.googleapis.com/m…tatic.com/firebasejs/4.13.0/firebase.js:1:278304)"}
For some reason google maps api doesnt accept the geoJson value even though console.log(geoJsonString); returns a valid geoJson value (checked at http://geojsonlint.com/)
Now the strange part is that if I try to import the same geoJson value manually(storing the geoJson value in a var and then map.data.addGeoJson(geoJsonString);) it works just fine.
This function syncs firebase with the web app
function gotData(data){
paths = data.val();
if(paths == null){
console.log("firebase null");
alert('Database is empty! Try adding some paths.');
}
else{
var keys = Object.keys(paths);
for(var i = 0; i < keys.length; i++){
var k = keys[i];
var geoJsonString = paths[k].geoJsonString;
console.log(geoJsonString);
map.data.addGeoJson(geoJsonString);
}
}
}
This function updates and pushes data in firebase
function updateData(){
data = {
geoJsonString: geoJsonOutput.value
}
ref = database.ref('firebasePaths');
ref.push(data);
}
In this function(which is used to store geoJson values locally in a file), I call updateData function), after a new path is drawn on the map
// Refresh different components from other components.
function refreshGeoJsonFromData() {
map.data.toGeoJson(function(geoJson) {
geoJsonOutput.value = JSON.stringify(geoJson);
updateData();
refreshDownloadLinkFromGeoJson();
});
}
Example of my firebase that contains 2 random geoJson
I can't trace where the problem is. Any ideas?
Update: I managed to fix this issue by parsing the string with JSON.parse("retrieved string from firebase"), saving it to a variable and then adding it to the map with map.data.addgeoJson(parsed variable).
We still have not faced that issue, however, we are aware of it.
Our intended solution is to use GeoFire: An open-source library for the Firebase Realtime Database that adds support for geospatial querying.
You can find the library description in here:
https://firebase.google.com/docs/libraries/
For the Web supported library:
https://github.com/firebase/geofire-js
I wonder, is it possible to connect a db to google maps api to render polygons, points etc?
If a store all kml coordinates connected to polygons, will it be possible to render it fra database or do i need to create a kml file to visualize it?
Is there any example?
Thanks!
A KML file is not required to visualize points, polygons, etc using Google Maps API. However, the KML layer is a useful way to represent complex geospatial features.
A backend database with HTTP access could return a list of map coordinates that your client code can render into appropriate shapes using Google Maps API.
The Google Maps API provides examples to create various shapes.
Example to create simple point marker:
https://developers.google.com/maps/documentation/javascript/examples/marker-simple
Example to create simple polygon:
https://developers.google.com/maps/documentation/javascript/examples/polygon-simple
The client code will need to query the data from the database such as from a servlet with access to the database. Database will most likely be running on a different port or from a different server so javascript won't be able to access it directly.
Your server-side component could query a database and return formatted KML or it could return a JSON result that your client code would render. Depends on whether you want to write more backend server code or JavaScript code on the client.
hmmm... I am using another solution now (data from DB to show on Google earth which is default application for KML file) and hope this help :
( may refer to https://sites.google.com/site/canadadennischen888/home/kml/auto-refresh-3d-tracking )(plus, in my other page, there is sample java code)
Details as :
prepare a RestFul service to generate KML file from DB (KML sample as inside above link)
My other jsp code will generate a KMZ file which has a link to my Restful service. KMZ file has onInterval ( as in the bottom)
Jsp web page allow user to download KMZ file.
When Google Earth open KMZ file, Google Earth will auto refresh to get new data from that Restful service
Everytime refreshing, server will send the latest update KML data with new data to GE.
KMZ sample:
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2"
xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<NetworkLink>
<name>Dennis_Chen_Canada#Hotmail.com</name>
<open>1</open>
<Link>
<href>http://localhost:9080/google-earth-project/rest/kml/10001/20002</href>
<refreshMode>onInterval</refreshMode>
</Link>
</NetworkLink>
</kml>
result as :
Thanks for you're answer. I don't know if i get somewhat wiser of this, but at least i know that it is possible.
I have used Shape2Sql to save the coordinates in the database.
But as i understand, i need someway to convert this to geojson before it can render in Google maps? if i understand correct?
As i understand to render geojson is mostly the same as to render kml when it comes to files. But i don't know how to Connect to a database.
I make a list from database:
var adresses = _unitOfWork.GeoAddressRepository.Get(q => q.GeoRouteNorpost.Code == "3007106", includeProperties: "GeoRouteNorpost").ToList();
var points = new List<LatLong>();
foreach (var address in adresses)
{
points.Add(new LatLng() { Lat = "59.948261", Lng = "10.750699" });
points.Add(new LatLng() { Lat = "59.943128", Lng = "10.755814" });
points.Add(new LatLng() { Lat = "59.941245", Lng = "10.746084" });
points.Add(new LatLng() { Lat = "59.943527", Lng = "10.742786" });
points.Add(new LatLng() { Lat = "59.946824", Lng = "10.744435" });
points.Add(new LatLng() { Lat = "59.946813", Lng = "10.744446" });
points.Add(new LatLng() { Lat = "59.947107", Lng = "10.748241" });
points.Add(new LatLng() { Lat = "59.947827", Lng = "10.749525" });
points.Add(new LatLng() { Lat = "59.948248", Lng = "10.750699" });
}
This example show a polygon on a map. But i'm not sure how to get this coordinates out of the database and how to solve it when it is serveral polygons.
As i have written, i have saved the coordinates in the db With shape2Sql. So now i have a Field for geometry. If i look at the spatial result in sql server this looks correct. But how can i display this in Google maps?
I am grateful for all help:)
I would like to launch Google StreetView in the Safari browser from my native iOS app. I have the street address and latitude longitude available. I know it is relatively easy to find panorama ID using an API, but unfortunately I can't use any Google API. I need to launch the google maps website outside of my app with the given address or latitude longitude.
The url for street view (for 40.535673,-74.209823) on desktop looks something like this:
https://www.google.com/maps?es_sm=91&um=1&ie=UTF-8&q=719+Ramona+ave+new+york&layer=c&z=17&iwloc=A&sll=40.535673,-74.209823&cbp=13,354.4,0,0,0&cbll=40.535462,-74.209796&sa=X&ei=BMdjVIaPMIn5yASA64Jw&ved=0CCAQxB0wAA
How can construct this url without using any Google Maps API? If I simply try to replace the latitude longitude above the street view ends up facing in the incorrect direction. Any way to get the heading just right?
The way I solved this was to first call the following API to get a panoId and best_view_direction_deg. Here's an example url for that API:
http://maps.google.com/cbk?output=json&ll=40.766200,-73.982500
Here's a piece of ObjC code I used to convert the pano ID and direction into a url:
+ (NSString *)bestStreetViewWebUrlForCoordinate:(CLLocationCoordinate2D)coordinate panoId:(NSString *)panoId bestViewDirectionDeg:(NSString *)bestViewDirectionDeg
{
NSString *streetViewUrlString = nil;
if (panoId != nil)
{
streetViewUrlString = [NSString stringWithFormat:#"https://maps.gstatic.com/m/streetview/?panoid=%#&cbp=0,%#,0,0,0", panoId, bestViewDirectionDeg];
}
else
{
streetViewUrlString = [NSString stringWithFormat:#"https://maps.gstatic.com/m/streetview/?q=&layer=c&cbll=%f,%f&cbp=11,0,0,0,0&output=classic&dg=brw",coordinate.latitude, coordinate.longitude];
}
return streetViewUrlString;
}
Output looks like:
https://maps.gstatic.com/m/streetview/?panoid=fB883QwmW5xNsXRuEHxmHw&cbp=0,207.478,0,0,0
I am trying to find a specific listing via the google-maps-places API, but I don't get any results. This is strange to me, as there is a Google+ page and also a google-maps entry.
Let's take a look at the links:
Google+:
https://plus.google.com/115673722468988785755/about
Maps:
https://www.google.de/maps/place/AMWAY+Beratung+%26+Vertrieb+Haegebarth/#53.171976,9.465828,17z/data=!3m1!4b1!4m2!3m1!1s0x47b106116fc69d69:0xe17811ab2780c71d
If I use the very same coordinates from the maps entry in my nearby search and use the name from the entry as the keyword (or location for what it's worth) the results is empty.
Places-API (with exact same coordinates):
https://maps.googleapis.com/maps/api/place/nearbysearch/json?sensor=false&radius=2000&name=amway&location=53.171976,9.465828&language=de-DE&key=YOURKEY
I can of course imagine that the db for the Google+ POIs is a different one. But then again I don't see how the maps api does not find what I can find on the maps web app.
Thanks a lot for any help!
Try something like this:
map.places.nearbySearch({
location: latLng,
rankBy: google.maps.places.RankBy.DISTANCE,
types: ['shop'], // optional
name: "AMWAY Beratung & Vertrieb Haegebarth" // optional
},function(results, status){
if (status == google.maps.places.PlacesServiceStatus.OK) {
// The thing you need should be result[0]
// console.log(results[0]);
if (results[0].name == "AMWAY Beratung & Vertrieb Haegebarth") { // optionally check the type
// You have your place :)
}
}
}
What a workaround! Rumours are that the Google Maps JS API v3.20 or later will allow us to trigger click events on POIs, thereby getting Place objects directly.