//#define DEBUG
#ifndef DEBUG
#translate trax([<list,...>]) =>
#endif
/*
May need to enable you IP address to use this:
May need to adjust quotas:
*/
class google_maps_geocode
hidden:
exported:
method county //(nResult) inline ::get_ac('administrative_area_level_2','',nResult)
method state(nResult,nLoop) inline ::get_ac('administrative_area_level_1','',nResult,,nLoop)
method street_number(nResult,nLoop) inline ::get_ac('street_number','',nResult,,nLoop)
method route(nResult,nLoop) inline ::get_ac('route','',nResult,,nLoop)
method street_name(nResult,nLoop) inline ::get_ac('route','',nResult,,nLoop)
method city(nResult,nLoop) inline ::locality(nResult,nLoop)
method locality(nResult,nLoop) inline ::get_ac('locality','',nResult,,nLoop)
method country(nResult,nLoop) inline ::get_ac('country','',nResult,,nLoop)
method zipcode(nResult,nLoop) inline ::postal_code(nResult,nLoop)
method postal_code(nResult,nLoop) inline ::get_ac('postal_code','',nResult,,nLoop)
method postal_code_suffix(nResult,nLoop) inline ::get_ac('postal_code_suffix','',nResult,,nLoop)
method ZipPlus4(nResult,nLoop) inline ::postal_code(nResult,nLoop)+if(empty(::postal_code_suffix(nResult,nLoop)),'','-'+::postal_code_suffix(nResult,nLoop))
method lat(nResult,nLoop) inline ::get({'geometry','location','lat'},,nResult,nLoop)
method lng(nResult,nLoop) inline ::get( {'geometry','location','lng'},,nResult,nLoop)
method address(nResult,nLoop) inline ::get( 'formatted_address','',nResult,nLoop)
method formattedaddress(nResult,nLoop) inline ::get( 'formatted_address','',nResult,nLoop)
method bounds(nResult,nLoop) inline ::get( {'geometry','bounds'},,nResult,nLoop)
method viewport(nResult,nLoop) inline ::get( {'geometry','viewport'},,nResult,nLoop)
method location_type(nResult,nLoop) inline ::get( {'geometry','location_type'},'',nResult,nLoop)
method location(nResult,nLoop) inline ::get( {'geometry','location'},,nResult,nLoop)
method place_id(nResult,nLoop) inline ::get( 'place_id','',nResult,nLoop)
method types(nResult,nLoop) inline ::get( 'types',{},nResult,nLoop)
method results(nLoop) inline hvalue(::hresult(,nLoop),'results',{})
method get_ac(xKey,xDefault,nResult,lShort,nLoop)
method get(xKey,xDefault,nResult,nLoop)
method address_lookup
//method muni_lookup(cAddress)
//method findmuni(cAddress,nCounty)
//method asString
method set_json
data oCurl
data matchingaddress init ''
method searched
data hSearched init {=>}
method errormsg
method cResults
method hResult
method setLoop
method getLoop
data aLoop init {}
data nStreetCityZipSearch
method streetcityzipcounty inline if(empty(::nstreetCityZipSearch),'',::county(,::nStreetCityZipSearch))
data nCityZipSearch
method cityZipcounty inline if(empty(::ncityZipSearch),'',::county(,::nCityZipSearch))
data nZipSearch
method zipcounty inline if(empty(::nZipSearch),'',::county(,::nZipSearch))
data nZipCitySearch //based on the formatted address of a zip search
method zipcitycounty inline if(empty(::nZipCitySearch),'',::county(,::nZipCitySearch))
data nStreetZipSearch
method Streetzipcounty inline if(empty(::nStreetZipSearch),'',::county(,::nStreetZipSearch))
data nCitySearch
method CityCounty inline if(empty(::nCitySearch),'',::county(,::nCitySearch))
method streetcitycounty() inline if(empty(::nStreetCitySearch),'',::county(,::nstreetCitySearch))
data nStreetCitySearch
data nCountyFound init 0
method clear
endclass
method Clear
::aLoop:={}
::nStreetCityZipSearch:=nil
::nCityZipSearch:=nil
::nZipSearch:=nil
::nZipCitySearch:=nil
::nStreetZipSearch:=nil
::nCitySearch:=nil
::nStreetCitySearch:=nil
::nCountyFound:=0
::hSearched:={=>}
return nil
method get(xKey,xDefault,nResult,nLoop)
local h, y,x,c,results,hresult,hSearch
default nLoop to 1
if nLoop=0
nLoop:=::nCountyFound
endif
if empty(nLoop).or.empty(::aLoop).or.len(::aLoop)<nLoop.or.empty(nLoop)
return(xDefault)
endif
hSearch:=::aLoop[nLoop]
hResult:=hvalue(hSearch,'hResult')
if empty(hresult)
return(xDefault)
endif
default nResult to 1
results:=hvalue(hresult,'results',{})
if nResult<1.or.nResult>len(results)
return(xDefault)
endif
h:=results[nResult]
if valtype(xKey)=="C"
return(hvalue(h,xkey,xDefault))
endif
//should be an array here
y:=len(xKey)
for x:=1 to y
c:=xKey[x]
if x==y
return(hvalue(h,c,xDefault))
elseif hhaskey(h,c)
h:=h[c]
else
return(xDefault)
endif
next
return(xDefault)
method get_ac(xKey,xDefault,nResult,lShort,nLoop) //address_component
local h, y,x,results,hresult,hSearch,a
trax(callstack())
trax(xKey,xDefault,nResult,lShort,nLoop)
default nLoop to 1
if nLoop=0
nLoop:=::nCountyFound
endif
if empty(::aLoop).or.len(::aLoop)<nLoop.or.empty(nLoop)
trax(::aLoop,nLoop)
return(xDefault)
endif
hSearch:=::aLoop[nLoop]
hResult:=hvalue(hSearch,'hResult')
if empty(hresult)
trax(hSearch)
return(xDefault)
endif
default nResult to 1,;
lShort to .t.
results:=hvalue(hresult,'results',{})
if nResult<1.or.nResult>len(results)
trax(hresult,results,nResult)
return(xDefault)
endif
h:=results[nResult]
a:=hvalue(h,'address_components',{})
trax(a)
y:=len(a)
for x:=1 to y
h:=a[x]
if hb_HHasKey(h, 'types') .and. !empty(h['types']) .and. len(h['types']) > 0 .and. h['types'][1]==xkey
if lShort
return(h['short_name'])
endif
return(h['long_name'])
endif
next
trax(a)
return(xDefault)
method address_lookup(xAddress,nRetry,/*@*/nLoop)
local lRet
local oUrl:=tUrl():new(cUrl),aQuery:={}
local cAddress
//cFile:=search->(fdbf_file('census_muni','a'))
default nRetry to 0,;
nLoop to 0
trax(nRetry,nLoop,callstack())
nLoop++
if nLoop==1
::aLoop:={}
::hSearched:={=>}
endif
begin sequence
if file('google_geocode.disable')
::errormsg(nLoop,'google geocode is currently disabled')
break
endif
if valtype(xAddress)=='C'
cAddress:=xAddress
else
cAddress:=xAddress:buildaddress(.t.)
//oPa:=xAddress //gd_parseaddress():new()
endif
if empty(cAddress)
trax('no Address')
::errormsg(nLoop,'no Address')
break
endif
Trax(cAddress)
if empty(::oCurl)
#ifdef DEBUG
::oCurl:=libcurl():new(,,.t.)
#else
::oCurl:=libcurl():new(,,)
#endif
::oCurl:setopt(HB_CURLOPT_TIMEOUT,60,.t.)
::oCurl:setopt(HB_CURLOPT_CONNECTTIMEOUT,30,.t.)
::oCurl:setHeader("Cache-Control: public, max-age=86400",.t.) //cache for 24hrs.
endif
if !empty(google_maps_ServerKey())
aadd(aQuery,{'key',google_maps_ServerKey()})
endif
cAddress:=upper(cAddress)
aadd(aQuery,{"address",cAddress})
::searched(nLoop,cAddress)
oUrl:addGetForm(aQuery)
trax(oUrl:buildquery())
if !::oCurl:gethttp( oUrl)
trax('Could not get')
::errormsg(nLoop,'Could not access the Google api')
break
endif
//nLoop>1..probably a zipcode search, save the results in county search results
lRet:=::set_json(gg_memoread(::oCurl:downloadfile),nLoop)
if !lRet
if "OVER_QUERY_LIMIT"$::errormsg(nLoop) //RETRY - too many request per second...
/*
If you exceed the usage limits you will get an OVER_QUERY_LIMIT status code as a response.
This means that the web service will stop providing normal responses and switch to returning only status code OVER_QUERY_LIMIT until more usage is allowed again. This can happen:
Within a few seconds, if the error was received because your application sent too many requests per second.
Within the next 24 hours, if the error was received because your application sent too many requests per day. The daily quotas are reset at midnight, Pacific Time.
This screencast provides a step-by-step explanation of proper request throttling and error handling, which is applicable to all web services.
Upon receiving a response with status code OVER_QUERY_LIMIT, your application should determine which usage limit has been exceeded. This can be done by pausing for 2 seconds and resending the same request. If status code is still OVER_QUERY_LIMIT, your application is sending too many requests per day. Otherwise, your application is sending too many requests per second.
Maybe this page will help in identifying what service is over the limit:
*/
if nRetry<3
hb_idlesleep(2)
lRet:=::address_lookup(cAddress,nRetry+1,nLoop)
else
::errormsg(nLoop," - Daily limit may have been reached")
/*
Note: It is also possible to get the OVER_QUERY_LIMIT error:
From the Google Maps Elevation API when more than 512 points per request are provided.
From the Google Maps Distance Matrix API when more than 625 elements per request are provided.
Applications should ensure these limits are not reached before sending requests.
*/
endif
endif
endif
recover
trax(::errormsg(nLoop))
if lRet==nil
lRet:=.f.
endif
end
if nLoop=1
trax(self)
endif
return(lRet)
method searched(nLoop,cSet)
default nLoop to ::nCountyFound
if nLoop=0.and.::nCountyFound=0
nLoop:=1
endif
if !hb_isnil(cSet)
::hSearched[upper(cSet)]:=nLoop
return(::setloop(nLoop,'searched',cSet))
endif
return(::getloop(nLoop,'searched',''))
method errormsg(nLoop,cSet)
default nLoop to 1
if !hb_isnil(cSet)
return(::setloop(nLoop,'errormsg',cSet))
endif
return(::getloop(nLoop,'errormsg',''))
method cResults(nLoop,cSet)
default nLoop to 1
if !hb_isnil(cSet)
return(::setloop(nLoop,'cResults',cSet))
endif
return(::getloop(nLoop,'cResults',''))
method hResult(nLoop,xSet)
default nLoop to 1
if !hb_isnil(xSet)
return(::setloop(nLoop,'hResult',xSet))
endif
return(::getloop(nLoop,'hResult'))
method setLoop(nLoop,cKey,xValue)
default nLoop to 1
if nLoop>len(::aLoop)
asize(::aLoop,nLoop)
::aLoop[nLoop]:={=>}
endif
::aLoop[nLoop][cKey]:=xValue
return(nil)
method getLoop(nLoop,cKey,xDefault)
default nLoop to 1
if nLoop>len(::aLoop)
//::setloop(nLoop,cKey,xDefault)
return(xDefault)
endif
return(hvalue(::aLoop[nLoop],cKey,xDefault))
method set_json(c,nLoop)
local cStatus,h
default nLoop to 1
::setloop(nLoop,'cResults',c)
hb_jsonDecode(c,@h)
::setloop(nLoop,'hResult',h)
if empty(::hResult(nLoop))
::errormsg(nLoop,'No results were returned')
return(.f.)
endif
if !hhaskey(::hResult(nLoop),'status')
::errormsg(nLoop,'Status code was not found')
return(.f.)
endif
/*
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:
*/
cStatus:=hvalue(::hResult(nLoop),'status','Unexpected Results')
switch cStatus
case "OK"
//indicates that no errors occurred; the address was successfully parsed and at least one geocode was returned.
exit
case "ZERO_RESULTS"
::errormsg(nLoop,"["+cStatus+"]Geocode was successful but returned no results. This may occur if the geocoder was passed a non-existent address.")
exit
case "OVER_QUERY_LIMIT"
::errormsg(nLoop,"["+cStatus+"]Over google api quota.")
exit
case "REQUEST_DENIED"
::errormsg(nLoop,"["+cStatus+"]Google request was denied.")
exit
case "INVALID_REQUEST"
::errormsg(nLoop,"["+cStatus+"]The query (address, components or latlng) is missing.")
exit
case "UNKNOWN_ERROR"
::errormsg(nLoop,"["+cStatus+"]The request could not be processed due to a server error. The request may succeed if you try again.")
exit
default
if !empty(cStatus)
::errormsg(nloop,"["+cStatus+"]")
endif
end
if !empty( ::errormsg(nLoop) )
return(.f.)
endif
return(.t.)
method county(nResult,nLoop)
if hb_isnil(nLoop).or.nLoop=0
nLoop:=::nCountyFound
endif
if empty(nLoop)
return('')
endif
return(::get_ac('administrative_area_level_2','',nResult,,nLoop))
function google_address_lookup(xAddress) //xAddress can be a string or a property object gd_ParseAddress():new()
static o
if empty(o)
//o:=google_addressvalidation():new()
o:=google_maps_geocode():new() //better county lookup abilities than addressvalidation
else
o:clear()
endif
o:address_lookup(xAddress)
return(o)
function google_address_lookup_county(xAddress)
//local o:=google_address_lookup(xAddress)
static o
local nLoop:=0,oPa,cTry,l,c,cCity,cState,cZip,cCountry,cStreet,aTry
if hb_isnil(o)
o:=google_maps_geocode():new()
endif
if empty(xAddress)
return('')
endif
if valtype(xAddress)=="O"
oPa:=xAddress
oPa:oCountyLookup:=o
endif
l:=o:address_lookup(xAddress,,@nLoop)
if l
c:=o:county(,nLoop)
if !empty(c)
o:nCountyFound:=nLoop //the first pass found the county so let us fill in and maybe update some of the oPa values.
if empty(oPa:zip)
oPa:zip:=o:postal_code()+if(empty(o:postal_code_suffix()),'','-'+o:postal_code_suffix())
endif
return(c)
endif
else
return('')
endif
if valtype(xAddress) =="O"
oPa:=xAddress
else
oPa:=gd_ParseAddress():new(upper(xAddress))
endif
oPa:oCountyLookup:=o
cCity:=oPa:city
cStreet:=oPa:streetname
cState:=oPa:state
cZip:=oPa:zip
cCountry:=oPa:country
aTry:={}
if !empty(cStreet)
cTry:=cStreet+", "+cCity+", "+cState+" "+cZip+", "+cCountry
if hhaskey(o:hsearched,cTry)
o:nStreetCityZipSearch:=o:hsearched[cTry]
else
aadd(aTry,cTry)
o:nStreetCityZipSearch:=len(aTry)+1
endif
endif
if !empty(cCity)
cTry:=cCity+", "+cState+" "+cZip+", "+cCountry
if hhaskey(o:hsearched,cTry)
o:nCityZipSearch:=o:hsearched[cTry]
else
aadd(aTry,cTry)
o:nCityZipSearch:=len(aTry)+1
endif
endif
if !empty(cStreet)
cTry:=cStreet+", "+cState+" "+cZip+", "+cCountry
if hhaskey(o:hsearched,cTry)
o:nStreetZipSearch:=o:hsearched[cTry]
else
aadd(aTry,cTry)
o:nStreetZipSearch:=len(aTry)+1
endif
endif
if !empty(cZip)
cTry:=cZip+", "+cCountry
if hhaskey(o:hsearched,cTry)
o:nZipSearch:=o:hsearched[cTry]
else
aadd(aTry,cTry)
o:nZipSearch:=len(aTry)+1
endif
endif
if !empty(cCity)
cTry:=cCity+", "+cState+", "+cCountry
if hhaskey(o:hsearched,cTry)
o:nCitySearch:=o:hsearched[cTry]
else
aadd(aTry,cTry)
o:nCitySearch:=len(aTry)+1
endif
endif
trax(aTry)
for each cTry in aTry
//Keep going until we work through the tree: {street name, city, state, zip}
trax(cTry)
l:=o:address_lookup(cTry,,@nLoop)
if l
c:=o:county(,nLoop)
if !empty(c)
o:nCountyFound:=nLoop
return(c)
endif
endif
end
nLoop:=o:nZipSearch
if !empty(nLoop)
cTry:=o:formattedAddress(nLoop)
trax(cTry)
if !empty(cTry)
if hhaskey(o:hSearched,cTry)
o:nZipCitySearch:=o:hSearched[cTry]
else
//try again with propbably city,zip search based on the zip search
l:=o:address_lookup(cTry,,@nLoop)
o:nZipCitySearch:=nLoop
if l
c:=o:county(,nLoop)
if !empty(c)
o:nCountyFound:=nLoop
endif
endif
endif
endif
endif
//if no county should we look at the street address with no street number?
return(c)