I am using the following Google Places script to get the location of the user. I get the full address, but I need to parse the result to get city, country.
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Google Places Autocomplete textbox using google maps api</title>
</head>
<body>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true&libraries=places"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0-alpha1/jquery.js"></script>
<script type="text/javascript">
google.maps.event.addDomListener(window, 'load', initialize);
function initialize() {
var autocomplete = new google.maps.places.Autocomplete(document.getElementById('txtAutocomplete'));
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var place = autocomplete.getPlace();
var location = "<b>Address</b>: " + place.formatted_address + "<br/>";
location += "<b>Latitude</b>: " + place.geometry.location.lat() + "<br/>";
location += "<b>Longitude</b>: " + place.geometry.location.lng();
document.getElementById('lblResult').innerHTML = location;
});
}
</script>
<span>Location:</span>
<input type="text" id="txtAutocomplete" style="width: 300px" placeholder="Enter your address" /><br /><br />
<label id="lblResult" />
</body>
</html>
I've tried the below script, but it does not work all the time as in some cases, address format is different.
var city = place.address_components[0] && place.address_components[0].short_name || '';
document.getElementById('lblResult').innerHTML = location;
console.log(city);
If you have a look at console.log(place); you'll see something like...
The returned object contains address_components. This is an array which is created from the available data so you can't guarantee what fields it will contain. More details about what is returned can be found in the description of the Google Places API Web Service.
You need to loop through the array and extract out the fields that you need. city may not exist but postal_town or locality might in which case you'll want to use those values instead.
Bear in mind that you may not get any value for city or country if that data is not available.
There is some sample code on the Google Developers Site which does most of what you need.
I like to use Lodash, because it provides defense against errors when a property is missing, or the passed-in value is not the expected type.
While I'm sure there's more elegant ways, I've developed this method for plucking certain pieces of information from Google Places information:
ES5 version Fiddle:
var address = place.address_components;
var city, state, zip;
address.forEach(function(component) {
var types = component.types;
if (types.indexOf('locality') > -1) {
city = component.long_name;
}
if (types.indexOf('administrative_area_level_1') > -1) {
state = component.short_name;
}
if (types.indexOf('postal_code') > -1) {
zip = component.long_name;
}
});
var lat = place.geometry.location.lat;
var lng = place.geometry.location.lng;
Utilizing Lodash:
var address = _.get(place, 'address_components');
var city, state, zip;
_.forEach(address, function (component) {
let types = _.get(component, 'types');
if (_.includes(types, 'locality')) {
city = _.get(component, 'long_name');
}
if (_.includes(types, 'administrative_area_level_1')) {
state = _.get(component, 'short_name');
}
if (_.includes(types, 'postal_code')) {
zip = _.get(component, 'long_name');
}
});
var lat = _.get(place, 'geometry.location.lat');
var lng = _.get(place, 'geometry.location.lng');
Related
I'm using webflow to display google reviews on my website using Google API, for now, everything ok
but instead display 5 reviews I would like to display only 2
I already a code to display the reviews but is there anyway to add in my code to display only 2 reviews?
<script>
var map = document.getElementById('google-places');
function initMap() {
var service = new google.maps.places.PlacesService(map);
service.getDetails({
placeId: 'xxxxxxxxxxxxxxxxx'
}, function (places, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
reviewsArray = [];
reviewsArray.push(places.reviews);
for (var key in reviewsArray) {
var arr = reviewsArray[key];
for (i = 0; i < arr.length; i++) {
var review = arr[i];
var author = review.author_name;
var when = review.relative_time_description;
var comment = review.text;
var starNumber = review.rating;
var starPercentage = `${(starNumber / 5) * 100}%`;
console.log(starPercentage);
var profilePic = review.profile_photo_url;
// console.log(author + ', ' + when + ', ' + comment + ', ' + rating + ', ' + profilePic);
document.getElementById('google-places').innerHTML +=
`
${author}
☆☆☆☆☆
★★★★★
</div>
</div>
<div class="review-content-wrapper">
<div class="review-text">${comment}</div>
</div>
</div>
</div>
`
}
}
}
})
}
</script>
<!-- Replace XXX with your key that you created at https://console.cloud.google.com -->
<script async defer src="https://maps.googleapis.com/maps/api/js?key=xxxxxxxxxxxxxxxxx&libraries=places&callback=initMap">
</script>
It appears Google Reviews defaults to 5 reviews (source: Is it possible to get latest five google reviews from google places api?).
Could you use only the first two and ignore the last three?
You should be able to do this in your own code; it isn't API dependent; if you use Jquery or PHP for example you can loop through the array and only display the reviews with index 0 and 1 for example to only show the two and leave out the rest.
I built a hybrid application using 3rd party container for iPhone application. We are getting ERROR status code in Production, but we are not able to able to reproduce the ERROR status code in DEV/QA. Can anyone help to find out when Google returns status code as ERROR ? I tried providing wrong address information, without any value, but it always returns some latitude and longitude during my test.
<html>
<head>
<title>WOM</title>
<meta charset="utf-8">
<!-- Apple metatags -->
<script>
function loadGoogleScript(clientId) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?client=valid_client_id&v=3.21&' +
'callback=initialize';
//script.src='http://maps.googleapis.com/maps/api/js?sensor=false';
document.body.appendChild(script);
console.log('Google initialization done ');
}
function initialize() {
var geocoder = new google.maps.Geocoder;
var i;
var componentRestrictions = { country: 'CA' };
var count = 0;
for( i = 0; i < 15 ; i++) {
geocoder.geocode({'address':'Valley Ranch Parkway W, MA','componentRestrictions':componentRestrictions},function(results, status){
try {
if (status == google.maps.GeocoderStatus.OK) {
var lat = ""+results[0].geometry.location.lat();
var long = ""+results[0].geometry.location.lng();
console.log("count = " + count++ + "lat = " + lat + "long = " + long);
}
else{
console.log("count = " + count++ + " status = " + status);
}
} catch(e){
console.log(" exception e " + e);
}
});
}
}
</script>
</head>
<body onload='loadGoogleScript()'>
</body>
</html>
Status Codes
The "status" field within the Geocoding response object contains the status of the request, and may contain debugging information to help you track down why geocoding is not working. The "status" field may contain the following values:
"OK" indicates that no errors occurred; the address was successfully parsed and at least one geocode was returned.
"ZERO_RESULTS" indicates that the geocode was successful but returned no results. This may occur if the geocoder was passed a non-existent address.
"OVER_QUERY_LIMIT" indicates that you are over your quota.
"REQUEST_DENIED" indicates that your request was denied.
"INVALID_REQUEST" generally indicates that the query (address, components or latlng) is missing.
"UNKNOWN_ERROR" indicates that the request could not be processed due to a server error. The request may succeed if you try again.
Given two locations you can calculate a route in Google Maps.
Is it possible to find all zip codes along the route?
Given a zip code, can I expand the area easily with a 10 km radius and find all zip codes in that area?
What methods should I use to get this information? Tutorials are welcome. I don't need a complete working solution, although if one is available that would be really nice.
You need a data source containing the zipcode (ZCTA) polygons. One possible source is this FusionTable.
proof of concept
proof of concept showing ZCTA polygons
Note: since it queries for the zip code at every point along the route, it will take longer to finish the longer the route is.
code that performs the query (using the Google Visualization API):
function queryForZip(latlng) {
//set the query using the current latlng
var queryStr = "SELECT geometry, ZIP, latitude, longitude FROM "+ tableid + " WHERE ST_INTERSECTS(geometry, CIRCLE(LATLNG"+latlng+",1))";
var queryText = encodeURIComponent(queryStr);
var query = new google.visualization.Query('http://www.google.com/fusiontables/gvizdata?tq=' + queryText);
//set the callback function
query.send(addZipCode);
}
function addZipCode(response) {
if (!response) {
alert('no response');
return;
}
if (response.isError()) {
document.getElementById('status').innerHTML += 'Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage()+"<br>";
return;
}
FTresponse = response;
//for more information on the response object, see the documentation
//http://code.google.com/apis/visualization/documentation/reference.html#QueryResponse
numRows = response.getDataTable().getNumberOfRows();
numCols = response.getDataTable().getNumberOfColumns();
for(i = 0; i < numRows; i++) {
var zip = response.getDataTable().getValue(i, 1);
var zipStr = zip.toString()
if (!zipcodes[zipStr]) {
zipcodes[zipStr] = zipStr;
document.getElementById('zipcodes').innerHTML += zipStr+"<br>";
}
}
}
This question may look familiar: I have the latitude and longitude of a place. I need to get the name of country. I know I have to use reverse geo coding for this. But my problem is that sometimes it returns the short form of the area or country (for example US for United State or CA for California). Is there any way that I can get the full name of the country? I can't perform a match operation by this short forms with my prestored country database.
I have already gone through this, this. But it's not much help for my problem.
The geocoder response usually returns several results which include street corners, intersections, counties, and other alternate representation names. I found that results[0] is the best description.
The trick is searching for "country" in the results. Then the long_name can be retrieved.
Click on the map
function getCountry(latLng) {
geocoder.geocode( {'latLng': latLng},
function(results, status) {
if(status == google.maps.GeocoderStatus.OK) {
if(results[0]) {
for(var i = 0; i < results[0].address_components.length; i++) {
if(results[0].address_components[i].types[0] == "country") {
alert(results[0].address_components[i].long_name);
}
}
}
else {
alert("No results");
}
}
else {
alert("Status: " + status);
}
}
);
}
The JSON array normally includes both long_name and short_name. You should be able to extract both...
Here is an JSON/XML parser that works for both google street maps and open street maps.
(the only problem is that it requires a JSON or XML object as the "reply" its tested on version 3 google and 0.6 open street maps and it works good)
NOTE: it returns an object location.lat or location.lon you can also have it return whatever other field you want.
JSON.parse(text) // where text is the reply from google or open street maps
XML.parse(text) // you can make your own to convert the reply to XML or use regex to parse it. If someone has a regex version to parse the text reply that may also be helpful.
// Parser(ajax reply object, google/open, json/xml);
// takes the reply from google maps or open street maps and creates an object with location[lat/lon]
function Parser(reply, provider, type) {
var location = {};
if(reply != null) {
if(provider == "google") { // Google Street Maps
switch(type) {
case "xml":
location["lat"] = reply.getElementsByTagName("lat")[0].textContent;
location["lon"] = reply.getElementsByTagName("lng")[0].textContent;
break;
default: // json
location["lat"] = reply.results[0].geometry.location.lat;
location["lon"] = reply.results[0].geometry.location.lng;
}
}
else { // Open Street Maps
switch(type) {
case "xml":
location["lat"] = reply.getElementsByTagName("place")[0].getAttribute("lat");
location["lon"] = reply.getElementsByTagName("place")[0].getAttribute("lon");
break;
default: // json
location["lat"] = reply[0].lat;
location["lon"] = reply[0].lon;
}
}
}
return location;
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
$.post("https://maps.googleapis.com/maps/api/geocode/json?latlng=" + position.coords.latitude + "," + position.coords.longitude + "&sensor=false", function (result) {
for (var i = 0; i < result['results'][0]['address_components'].length; i++) {
if (result['results'][0]['address_components'][i]['types'][0] == "country") {
alert(result['results'][0]['address_components'][i]['long_name']);
}
}
});
});
}
}
getLocation();
How do I integrate Salesforce with Google Maps? I'm just looking for information on how to...
Search for contacts in Salesforce
Plot those on a google map.
EDIT:
Thanks to tggagne's comment I've realized that people still see this answer. The code that was here is over 2.5 years old. If you want to see it - check the history of edits.
A lot has changed in the meantime, more mashup examples were created. Not the least of them being "SF Bus Radar" (github, youtube) app by Cory Cowgill (created on Dreamforce'11 I think).
Nonetheless - here's my updated example with server-side geocoding, new field of type Geolocation and usage of JSON parsers.
It tries to cache the geocoding results in the contact records. Bear in mind it might not be 'production-ready' (no Google Business API key = as all our requests come out from same pool of Salesforce IP servers there might be error messages). That's why I've left the client-side geocoding too.
You'll need to make 2 changes in your environment before checking it out:
Add "Remote Site Setting" that points to https://maps.googleapis.com to enable callouts from Apex
Add field "Location" in Setup -> Customize -> Contacts -> fields. Type should be "Geolocation". I've selected display as decimals and precision of 6 decimal places.
public with sharing class mapController {
public String searchText {get;set;}
public List<Contact> contacts{get; private set;}
public static final String GEOCODING_URI_BASE = 'https://maps.googleapis.com/maps/api/geocode/json?sensor=false&address=';
// For purposes of this demo I'll geocode only couple of addresses server-side. Real code can use the commented out value.
public static final Integer MAX_CALLOUTS_FROM_APEX = 3; // Limits.getLimitCallouts()
public mapController(){
searchText = ApexPages.currentPage().getParameters().get('q');
}
public void find() {
if(searchText != null && searchText.length() > 1){
List<List<SObject>> results = [FIND :('*' + searchText + '*') IN ALL FIELDS RETURNING
Contact (Id, Name, Email, Account.Name,
MailingStreet, MailingCity, MailingPostalCode, MailingState, MailingCountry,
Location__Latitude__s, Location__Longitude__s)
];
contacts = (List<Contact>)results[0];
if(contacts.isEmpty()){
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'No matches for "' + searchText + '"'));
} else {
serverSideGeocode();
}
} else {
if(contacts != null) {
contacts.clear();
}
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, 'Please provide at least 2 characters for the search.'));
}
}
public void clearGeocodedData(){
for(Contact c : contacts){
c.Location__Latitude__s = c.Location__Longitude__s = null;
}
Database.update(contacts, false);
contacts.clear();
}
public String getContactsJson(){
return JSON.serialize(contacts);
}
public String getDebugContactsJson(){
return JSON.serializePretty(contacts);
}
private void serverSideGeocode(){
List<Contact> contactsToUpdate = new List<Contact>();
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setMethod('GET');
req.setTimeout(10000);
for(Contact c : contacts){
if((c.Location__Latitude__s == null || c.Location__Longitude__s == null)){
String address = c.MailingStreet != null ? c.MailingStreet + ' ' : '' +
c.MailingCity != null ? c.MailingCity + ' ' : '' +
c.MailingState != null ? c.MailingState + ' ' : '' +
c.MailingPostalCode != null ? c.MailingPostalCode + ' ' : '' +
c.MailingCountry != null ? c.MailingCountry : '';
if(address != ''){
req.setEndpoint(GEOCODING_URI_BASE + EncodingUtil.urlEncode(address, 'UTF-8'));
try{
HttpResponse res = h.send(req);
GResponse gr = (GResponse) JSON.deserialize(res.getBody(), mapController.GResponse.class);
if(gr.status == 'OK'){
LatLng ll = gr.results[0].geometry.location;
c.Location__Latitude__s = ll.lat;
c.Location__Longitude__s = ll.lng;
contactsToUpdate.add(c);
} else {
ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Geocoding of "' + address + '" failed:' + gr.status));
}
}catch(Exception e){
ApexPages.addMessages(e);
}
}
// Bail out if we've reached limit of callouts (not all contacts might have been processed).
if(Limits.getCallouts() == MAX_CALLOUTS_FROM_APEX) {
break;
}
}
}
if(!contactsToUpdate.isEmpty()) {
Database.update(contactsToUpdate, false); // some data in Developer editions is invalid (on purpose I think).
// If update fails because "j.davis#expressl&t.net" is not a valid Email, I want the rest to succeed
}
}
// Helper class - template into which results of lookup will be parsed. Some fields are skipped!
// Visit https://developers.google.com/maps/documentation/geocoding/#Results if you need to create full mapping.
public class GResponse{
public String status;
public GComponents[] results;
}
public class GComponents{
public String formatted_address;
public GGeometry geometry;
}
public class GGeometry {
public LatLng location;
}
public class LatLng{
public Double lat, lng;
}
}
<apex:page controller="mapController" tabStyle="Contact" action="{!find}" id="page">
<head>
<style>
div #map_canvas { height: 400px; }
</style>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?sensor=false"></script>
</head>
<apex:sectionHeader title="Hello StackOverflow!" subtitle="Contact full text search + Google Maps integration" />
<apex:pageMessages />
<apex:form id="form">
<apex:pageBlock id="searchBlock">
<apex:inputText value="{!searchText}" />
<apex:commandButton value="Search" action="{!find}"/>
<p>Examples: "USA", "Singapore", "Uni", "(336) 222-7000". If it works in the global search box, it will work here.</p>
</apex:pageBlock>
<apex:pageBlock title="Found {!contacts.size} Contact(s)..." rendered="{!NOT(ISNULL(contacts)) && contacts.size > 0}" id="resultsBlock">
<apex:pageBlockButtons location="top">
<apex:commandButton value="Clear cached locations" title="Click if you want to set 'null' as geolocation info for all these contacts" action="{!clearGeocodedData}" />
</apex:pageBlockButtons>
<apex:pageBlockTable value="{!contacts}" var="c" id="contacts">
<apex:column headerValue="{!$ObjectType.Contact.fields.Name.label}">
<apex:outputLink value="../{!c.Id}">{!c.Name}</apex:outputLink>
</apex:column>
<apex:column headerValue="Address">
{!c.MailingStreet} {!c.MailingCity} {!c.MailingCountry}
</apex:column>
<apex:column value="{!c.Account.Name}"/>
<apex:column headerValue="Location (retrieved from DB or geocoded server-side)">
{!c.Location__Latitude__s}, {!c.Location__Longitude__s}
</apex:column>
</apex:pageBlockTable>
<apex:pageBlockSection columns="1" id="mapSection">
<div id="map_canvas" />
</apex:pageBlockSection>
<apex:pageBlockSection title="Click to show/hide what was geocoded server-side and passed to JS for further manipulation" columns="1" id="debugSection">
<pre>{!debugContactsJson}</pre>
</apex:pageBlockSection>
<pre id="log"></pre>
</apex:pageBlock>
</apex:form>
<script type="text/javascript">
twistSection(document.getElementById('page:form:resultsBlock:debugSection').childNodes[0].childNodes[0]); // initially hide the debug section
var contacts = {!contactsJson}; // Array of contact data, some of them might have lat/long info, some we'll have to geocode client side
var coords = []; // Just the latitude/longitude for each contact
var requestCounter = 0;
var markers = []; // Red things we pin to the map.
var balloon = new google.maps.InfoWindow(); // Comic-like baloon that floats over markers.
function geocodeClientSide() {
for(var i = 0; i < contacts.length; i++) {
if(contacts[i].Location__Latitude__s != null && contacts[i].Location__Longitude__s != null) {
coords.push(new google.maps.LatLng(contacts[i].Location__Latitude__s, contacts[i].Location__Longitude__s));
} else {
++requestCounter;
var address = contacts[i].MailingStreet + ' ' + contacts[i].MailingCity + ' ' + contacts[i].MailingCountry;
var geocoder = new google.maps.Geocoder();
if (geocoder) {
geocoder.geocode({'address':address}, function (results, status) {
if (status == google.maps.GeocoderStatus.OK) {
coords.push(results[0].geometry.location);
} else {
var pTag = document.createElement("p");
pTag.innerHTML = status;
document.getElementById('log').appendChild(pTag);
}
if(--requestCounter == 0) {
drawMap();
}
});
}
}
}
// It could be the case that all was geocoded on server side (or simply retrieved from database).
// So if we're lucky - just proceed to drawing the map.
if(requestCounter == 0) {
drawMap();
}
}
function drawMap(){
var mapOptions = {
center: coords[0],
zoom: 3,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
var map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
for(var i = 0; i < coords.length; ++i){
var marker = new google.maps.Marker({map: map, position: coords[i], title:contacts[i].Name, zIndex:i});
google.maps.event.addListener(marker, 'click', function() {
var index = this.zIndex;
balloon.content = '<b>'+contacts[index].Name + '</b><br/>' + contacts[index].Account.Name + '<br/>' + contacts[index].Email;
balloon.open(map,this);
});
markers.push(marker);
}
}
geocodeClientSide();
</script>
</apex:page>
Another place to look is the force.com platform fundamentals book (or site if you don't have a developer account). They have a very good and detailed tutorial here showing how to integrate maps with Salesforce (they use Yahoo for the tutorial but it will work just as well with Google Maps).
Since Spring '15, we can also use apex:map with no extra Google API.
Also works when viewed in Lightning -- No personal experience specifically but that's what I read.
Example from Docs:
<apex:map width="600px" height="400px" mapType="roadmap" center="{!Account.BillingStreet}, {!Account.BillingCity}, {!Account.BillingState}">
<!-- Add a CUSTOM map marker for the account itself -->
<apex:mapMarker title="{! Account.Name }" position="{!Account.BillingStreet}, {!Account.BillingCity}, {!Account.BillingState}" icon="{! URLFOR($Resource.MapMarkers, 'moderntower.png') }"/>
<!-- Add STANDARD markers for the account's contacts -->
<apex:repeat value="{! Account.Contacts }" var="ct">
<apex:mapMarker title="{! ct.Name }" position="{! ct.MailingStreet }, {! ct.MailingCity }, {! ct.MailingState }"></apex:mapMarker>
</apex:repeat>
</apex:map>
In the example, {! Account.Contacts } is a list of Contacts which
is being iterated over. Each iteration, it's creating apex:mapMarker's to map all Contacts in a list. Though the OP is old, the "search results" could basically replace the {Account.Contacts} list being iterated over in example.
Documentation:
Docs that example was pulled from.
(I know this is old but was brought to top from an update so thought update not using API would be okay.)