Como fazer integração do Google Maps com AdvPL

Hoje vou mostrar como fazer a integração do Google Maps em um site com PHP e JavaScript consultando informações do Protheus via AdvPL.

Algumas vezes precisamos fazer alguma integração, e recentemente eu tive que fazer uma, onde em um site com PHP e JavaScript ele iria acessar os dados de uma base Protheus e mostrar pinos em uma região conforme a tabela de clientes (SA1).

Essa integração funciona da seguinte forma, em uma página web, é feito uma requisição via SOAP para o Protheus, que analisa e retorna os principais clientes daquela cidade em questão, e nisso a página web monta os pinos conforme os endereços dos clientes.

Caso você tenha cadastrado latitude e longitude, fica mais fácil para fazer a integração, porém como nesse cenário não tinha isso, eu fiz pegando pelo endereço mesmo, então vamos lá ver como é feito.

  1. Crie ou configure uma conta no Google Cloud Platform ( https://cloud.google.com/maps-platform/ ), crie uma API, que contemple as seguintes opções:
  • Maps JavaScript API
  • Maps Embed API
  • Directions API
  • Geocoding API
  • Maps SDK for Android
  • Maps SDK for iOS
  • Maps Static API
  • Street View Publish API
  • Street View Static API
  1. Após criar a API, guarde a chave gerada, geralmente é uma de 40 caracteres, por exemplo XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
  2. Agora, configure um AppServer que será o nosso WebService, nesse exemplo nós usaremos SOAP. Para ver como configurar, veja o passo a passo nesse link – https://terminaldeinformacao.com/2019/02/05/como-configurar-um-webservice-em-advpl-utilizando-soap/
  3. Então, nós iremos agora criar um fonte .prw que irá buscar as informações para mostrar no mapa do Google Maps na nossa página web. Esse método abaixo foi construído assim, ele irá receber o Estado, a Cidade e um Token. A partir disso, é verificado os 26 principais clientes dos últimos 6 meses da cidade para mostrar no mapa. Veja o código abaixo:
//Bibliotecas
#Include "Protheus.ch"
#Include "APWebSrv.ch"
#Include "TBIConn.ch"
#Include "TBICode.ch"
#Include "TopConn.ch"
#Include "aarray.ch"
#Include "json.ch"
#Include "shash.ch"

WsService WsSite Description "WebService de exemplo para o site"
	//Atributos
	WsData   cMapRece  as String
	WsData   cMapSend  as String

	//Métodos
	WsMethod RetMapClis       Description "Metodo para retornar os clientes para pinagem em mapa"
EndWsService

/*
	Função fNoAcento
	Função que retira os acentos, foi criado para substituir a FWNoAccent para tirar somente os caracteres de acentuação (sem retirar outros caracteres especiais)
*/

Static Function fNoAcento(cTexto, lTiraAspas, lRetorno)
	Local nAtual        := 0
	Local aTroca        := {}
	Default cTexto      := ""
	Default lTiraAspas  := .T.
	Default lRetorno    := .F.
	
	//ConOut("cTexto:     "+cTexto)
	//ConOut("lTiraAspas: "+cValToChar(lTiraAspas))
	//ConOut("lRetorno:   "+cValToChar(lRetorno))
	
	If ! lRetorno
		aAdd(aTroca, {"ã", "a"})
		aAdd(aTroca, {"á", "a"})
		aAdd(aTroca, {"â", "a"})
		aAdd(aTroca, {"à", "a"})
		aAdd(aTroca, {"ä", "a"})
		aAdd(aTroca, {"é", "e"})
		aAdd(aTroca, {"è", "e"})
		aAdd(aTroca, {"ê", "e"})
		aAdd(aTroca, {"ë", "e"})
		aAdd(aTroca, {"í", "i"})
		aAdd(aTroca, {"ì", "i"})
		aAdd(aTroca, {"î", "i"})
		aAdd(aTroca, {"ï", "i"})
		aAdd(aTroca, {"ó", "o"})
		aAdd(aTroca, {"ò", "o"})
		aAdd(aTroca, {"õ", "o"})
		aAdd(aTroca, {"ô", "o"})
		aAdd(aTroca, {"ö", "o"})
		aAdd(aTroca, {"ú", "u"})
		aAdd(aTroca, {"ù", "u"})
		aAdd(aTroca, {"û", "u"})
		aAdd(aTroca, {"ü", "u"})
		aAdd(aTroca, {"ç", "c"})
	EndIf
	If lTiraAspas
		aAdd(aTroca, {'"', ""})
		aAdd(aTroca, {'“', ""})
		aAdd(aTroca, {'”', ""})
	EndIf
	
	For nAtual := 1 To Len(aTroca)
		cTexto := StrTran(cTexto, aTroca[nAtual][1], aTroca[nAtual][2])
		cTexto := StrTran(cTexto, Upper(aTroca[nAtual][1]), Upper(aTroca[nAtual][2]))
	Next
Return cTexto

/*
	Método RetMapClis
	Funcionalidade que recebe alguns parâmetros e retorna uma lista de clientes
	
	Exemplo do JSON
	{
		"Filtro": {
			"Estado": "SP",
			"Cidade": "Bauru"
    	}
	}
*/

WsMethod RetMapClis WsReceive cMapRece WsSend cMapSend WsService WsSite
	Local lRet      := .T.
	Local cEstado   := ""
	Local cCidade   := ""
	Local cQryCli   := ""
	Local cTokWs    := Alltrim(GetMV('MV_X_TOKEN'))
	Local cSeqAtu   := "A"
	Local cNaoTotal     := "'201','202','206','411','413','551','556','901','903','910','912','914','915','949','913'"
	Local cTiraFilt     := "'901','903','910','915','556','206'"
	Local cTiraFilt2     := "'5411','5413', '5917'"
	Local nMeses    := 6
	Private oJSON   := Nil
	Private oFiltro := Nil
	Private cToken  := ""

	//Deserializando o JSON
	If (FWJsonDeserialize(::cMapRece, @oJSON))
		oFiltro := oJSON:Filtro
		cToken  := Iif(ValAtrib("oJSON:Token") != "U", oJSON:Token, "")
		
		//Se for o mesmo Token
		If cToken == cTokWs
			//Pegando os atributos vindos do JSON
			cEstado  := Upper(Iif(ValAtrib("oFiltro:Estado") != "U", oFiltro:Estado, ""))
			cCidade  := fNoAcento(Upper(Iif(ValAtrib("oFiltro:Cidade") != "U", oFiltro:Cidade, "")))
			
			//Monta a consulta
			cQryCli := "SELECT * FROM (" + CRLF
			cQryCli += " SELECT TOP 26 "                               + CRLF
			cQryCli += " 	A1_COD AS CODIGO, "                        + CRLF
			cQryCli += " 	A1_NOME AS NOME, "                       + CRLF
			cQryCli += " 	A1_NREDUZ AS NREDUZ, "                       + CRLF
			cQryCli += " 	A1_END AS ENDERECO, "                      + CRLF
			cQryCli += " 	A1_BAIRRO AS BAIRRO, "                     + CRLF
			cQryCli += " 	A1_MUN AS CIDADE, "                     + CRLF
			cQryCli += " 	A1_EST AS ESTADO, "                     + CRLF
			cQryCli += " 	A1_CEP AS CEP, "                           + CRLF
			cQryCli += " 	A1_DDD AS DDD, "                           + CRLF
			cQryCli += " 	A1_TEL AS TELEFONE, "                      + CRLF
			cQryCli += " 	A1_EMAIL AS EMAIL, "                       + CRLF
			cQryCli += " 	A1_HPAGE AS SITE, "                       + CRLF
			cQryCli += " 	ISNULL(( "                       + CRLF
			cQryCli += " 		SELECT " + CRLF
			cQryCli += " 			SUM(CASE WHEN SUBSTRING(D2_CF,2,3) NOT IN ("+cNaoTotal+") AND (D2_TIPO NOT IN ('I')) AND ( D2_FILIAL = '01' ) THEN D2_TOTAL ELSE 0.0 END ) AS TOTAL " + CRLF
			cQryCli += " 		FROM " + CRLF
			cQryCli += " 			"+RetSQLName("SD2")+" SD2 WITH (NOLOCK) " + CRLF
			cQryCli += " 			LEFT JOIN "+RetSQLName("SF2")+" SF2 WITH (NOLOCK) ON ( " + CRLF
			cQryCli += " 				D2_FILIAL = F2_FILIAL " + CRLF
			cQryCli += " 				AND F2_DOC = D2_DOC " + CRLF
			cQryCli += " 				AND F2_SERIE = D2_SERIE " + CRLF
			cQryCli += " 				AND SF2.D_E_L_E_T_ = ' ') " + CRLF
			cQryCli += " 		WHERE " + CRLF
			cQryCli += " 			D2_FILIAL = '01' " + CRLF
			cQryCli += " 	        AND D2_CLIENTE = A1_COD "                       + CRLF
			cQryCli += " 	        AND D2_LOJA = A1_LOJA "                       + CRLF
			cQryCli += " 			AND D2_EMISSAO >= '" + dToS(MonthSub(Date(), nMeses)) + "' " + CRLF
			cQryCli += " 			AND SUBSTRING(D2_CF,2,3) NOT IN ("+cTiraFilt+") " + CRLF
			cQryCli += " 			AND D2_CF NOT IN ("+cTiraFilt2+") "	 + CRLF
			cQryCli += " 			AND SD2.D_E_L_E_T_ = ' ' " + CRLF
			cQryCli += " 	), 0) AS VALOR "                       + CRLF
			cQryCli += " FROM "                                        + CRLF
			cQryCli += " 	"+RetSQLName('SA1')+" SA1 WITH (NOLOCK) "  + CRLF
			cQryCli += " WHERE "                                       + CRLF
			cQryCli += " 	A1_FILIAL = '"+FWxFilial('SA1')+"' "       + CRLF
			cQryCli += " 	AND A1_MSBLQL != '1' "                     + CRLF
			If ! Empty(cEstado)
				cQryCli += " 	AND A1_EST = '"+cEstado+"' "           + CRLF
			EndIf
			If !Empty(cCidade)
				cQryCli += " 	AND UPPER(A1_MUN) LIKE '%"+Alltrim(Upper(cCidade))+"%' "      + CRLF
			EndIf
			cQryCli += " 	AND SA1.D_E_L_E_T_ = ' ' "                 + CRLF
			cQryCli += " ORDER BY "                                    + CRLF
			cQryCli += " 	VALOR DESC "                               + CRLF
			cQryCli += " ) TAB_TEMPO "                               + CRLF
			cQryCli += " WHERE "                               + CRLF
			cQryCli += " 	VALOR != 0 "                               + CRLF
			cQryCli += " ORDER BY NREDUZ ASC "               + CRLF
			TCQuery cQryCli New Alias "QRY_CLI"
			
			//Se houver dados
			If ! QRY_CLI->(EoF())
				::cMapSend := '{'                  + CRLF
				::cMapSend +=     '"Clientes": {'
				
				//Enquanto houver dados na consulta
				While ! QRY_CLI->(EoF())
					
					cSite := ''
					If ! ('@' $ cSite .Or. ' ' $ cSite) .And. ('.' $ cSite)
						cSite := Alltrim(QRY_CLI->SITE)
					EndIf
					
					::cMapSend += CRLF
					::cMapSend += '"'+QRY_CLI->CODIGO+'": {'+;
						' "Sequencia":"' +cSeqAtu                              +'", '+;
						' "Codigo":"'    +QRY_CLI->CODIGO                      +'", '+;
						' "Nome":"'      +Alltrim(Capital(QRY_CLI->NOME))      +'", '+;
						' "NomeReduz":"' +Alltrim(Capital(QRY_CLI->NREDUZ))    +'", '+;
						' "Endereco":"'  +Alltrim(Capital(QRY_CLI->ENDERECO))  +'", '+;
						' "Bairro":"'    +Alltrim(Capital(QRY_CLI->BAIRRO))    +'", '+;
						' "Cidade":"'    +Alltrim(Capital(QRY_CLI->CIDADE))    +'", '+;
						' "Estado":"'    +Alltrim(Capital(QRY_CLI->ESTADO))    +'", '+;
						' "CEP":"'       +Alltrim(QRY_CLI->CEP)                +'", '+;
						' "DDD":"'       +Alltrim(QRY_CLI->DDD)                +'", '+;
						' "Telefone":"'  +Alltrim(QRY_CLI->TELEFONE)           +'", '+;
						' "Email":"'     +Alltrim(QRY_CLI->EMAIL)              +'", '+;
						' "Site":"'      +Alltrim(cSite)                       +'"  '+;
						'},'
						
					cSeqAtu := Soma1(cSeqAtu)
					QRY_CLI->(DbSkip())
				EndDo
				
				//Retirando a última vírgula
				::cMapSend := SubStr(::cMapSend, 1, Len(::cMapSend)-1)
				
				::cMapSend +=     '}'              + CRLF
				::cMapSend += '}'                  + CRLF
				
			//Senão monta estrutura vazia
			Else
				::cMapSend := '{'                  + CRLF
				::cMapSend +=     '"Clientes": {'  + CRLF
				::cMapSend +=     '}'              + CRLF
				::cMapSend += '}'                  + CRLF
			EndIf
			QRY_CLI->(DbCloseArea())
			
			//Retira os acentos
			::cMapSend := fNoAcento(::cMapSend, .F., .T.)
			
		//Caso seja um token inválido
		Else
			SetSoapFault('Erro', 'Token invalido!')
			lRet := .F.
		EndIf
		
	//Se houve erro ao deserializar o JSON
	Else
		SetSoapFault('Erro', 'JSON nao deserializado!')
		lRet := .F.
	EndIf

Return lRet

/*
	Função ValAtrib
	Função que verifica se um atributo existe no objeto
*/

Static Function ValAtrib(xAtributo)
Return ( Type(xAtributo) )
  1. No nosso site, iremos criar um arquivo chamado functions.php, que será o responsável por ter o nosso Token e a URL do nosso WebService, veja o código abaixo:

  1. Agora iremos montar o nosso site, com o nome index.php. Nessa página, iremos colocar alguns parâmetros em cima, o mapa no meio e uma tabela no final. Nos códigos intermediários, através de PHP e JavaScript iremos fazer as integrações com o Protheus (PHP com WebService SOAP) e com o Google Maps (JavaScript). Ah, no código abaixo, lembre-se de colocar a sua chave da API ao invés do XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX (linhas 22 e 309)


	Teste Google Maps x AdvPL




 true]);
		
		//Tenta fazer a conexao com o Protheus e buscar os clientes
		try {
			$parametrosProtheus = array(
				"RETMAPCLIS" => array(
					"CMAPRECE" => 
						'{'.
						'	"Filtro": {'.
						'	   "Estado": "'  . $parametroEstado  . '", '.
						'	   "Cidade": "'  . $parametroCidade  . '" '.
						'	},'.
						'	"Token": "'.$tokenProtheus.'"'.
						'}'
					)
				);
			
			$respostaProtheus = $soapClientProtheus->__soapCall("RETMAPCLIS", $parametrosProtheus);
			
			$jsonClientesProtheus = json_decode($respostaProtheus->RETMAPCLISRESULT);
			if (json_last_error() == 0) {
				$clientesProtheus = $jsonClientesProtheus->Clientes;
			}
			
		} catch (SoapFault $soapException) {
			echo 'Houve um erro no carregamento dos clientes, contate o Administrador. Exception: 
'; echo $soapException->getMessage(); return; } } //Agora cria algumas variaveis em javascript com o conteudo das variaveis em php echo ''; ?> Endereco) . "+" . str_replace(" ", "+", $clienteAtual->Cidade) . "+" . $clienteAtual->Estado . "+" . $clienteAtual->CEP; $urlGoogleMaps = "https://maps.googleapis.com/maps/api/geocode/json?address=" . $enderecoParaGoogle . "&key=" . $keyGoogleMaps; $respostaGoogleMaps = file_get_contents($urlGoogleMaps); $respostaGoogleDecod = json_decode($respostaGoogleMaps, true); //Se encontrou informacoes, pega latitude e longitude do cliente, adiciona no array de informacoes if ($respostaGoogleDecod != null) { $clienteLatitude = $respostaGoogleDecod['results'][0]['geometry']['location']['lat']; $clienteLongitude = $respostaGoogleDecod['results'][0]['geometry']['location']['lng']; echo ''; } else { echo ''; } //Adicionando mensagem do Popup que aparece ao clicar no marcador do cliente $atualizaPopupMarcadores = ''; echo $atualizaPopupMarcadores; } //Busca o endereco digitado pelo usuario $enderecoParaGoogle = str_replace(" ", "+", $parametroRua) . "+" . str_replace(" ", "+", $parametroCidade) . "+" . $parametroEstado . "+" . $parametroCEP; $urlGoogleMaps = "https://maps.googleapis.com/maps/api/geocode/json?address=" . $enderecoParaGoogle . "&key=" . $keyGoogleMaps; $respostaGoogleMaps = file_get_contents($urlGoogleMaps); $respostaGoogleDecod = json_decode($respostaGoogleMaps, true); //Se encontrar, o mapa ira comecar da localizacao do usuario if ($respostaGoogleDecod != null) { $clienteLatitude = $respostaGoogleDecod['results'][0]['geometry']['location']['lat']; $clienteLongitude = $respostaGoogleDecod['results'][0]['geometry']['location']['lng']; echo ''; echo ''; } } ?>

Filtros

Obs.: Se o navegador perguntar, permita acesso à sua localização, e depois clique em Procurar







'; echo ' '; echo ' '; echo ''; } //Porem, se existir o array de clientes existir, mas nao tiver conteudos, mostra outra mensagem else if (count((array)$clientesProtheus) == 0) { echo ''; echo ' '; echo ''; } //Se nao, entao existem dados e sera percorrido os clientes else { foreach ( $clientesProtheus as $clienteAtual ) { //Monta o link para ver a distância no Google $linkDistancia = "https://www.google.com/maps/dir/" . str_replace(" ", "+", $parametroRua) . ",+" . str_replace(" ", "+", $parametroCidade) . "+-+" . $parametroEstado . ",+" . str_replace(" ", "", $parametroCEP); $linkDistancia .= "/"; $linkDistancia .= str_replace(" ", "+", $clienteAtual->Endereco) . ' - ' . str_replace(" ", "+", $clienteAtual->Bairro) . ",+" . str_replace(" ", "+", $parametroCidade) . "+-+" . $parametroEstado ."," . str_replace(" ", "", $clienteAtual->CEP) . "/"; echo ''; //Coluna para ver a distância echo ' '; //Coluna com os dados do cliente echo ' '; echo ''; } } ?>
Sequencia Local
......
Não foi possível encontrar dados com o(s) filtro(s) informado(s)!
' . $clienteAtual->Sequencia . '

'; echo ''; echo 'Ver Distância'; echo ''; echo '
'; echo '' . $clienteAtual->NomeReduz . '
'; echo 'Razão Social: ' . $clienteAtual->Nome . '
'; echo 'Endereço: ' . $clienteAtual->Endereco . ' - ' . $clienteAtual->Bairro . '
'; echo 'CEP: ' . $clienteAtual->CEP . '
'; if (! empty($clienteAtual->Telefone)) echo 'Telefone: DDD . $clienteAtual->Telefone . '"> (' . $clienteAtual->DDD . ') ' . $clienteAtual->Telefone . '
'; if (! empty($clienteAtual->Email)) echo 'e-Mail: Email . '">' . $clienteAtual->Email . '
'; if (! empty($clienteAtual->Site)) echo 'Site: Site . '">' . $clienteAtual->Site . ''; echo '
  1. Teste o carregamento da página, e ela irá mostrar basicamente uma página sem muitas informações, apenas com o que a gente criou.

Página criada antes de fazer a pesquisa

  1. Agora, preencha as informações acima de filtro (se o navegador tiver suporte a geolocalização, basta usuário clicar em permitir e recarregar a página), e em seguida clique em pesquisar. Com isso será feito uma pesquisa no nosso WebService criado no passo 4, e se tiver dados irá retornar um JSON para o PHP se comunicar com o JavaScript e montar os pinos no Google, inclusive com opção de clicar nos pinos e mostrar informações para o usuário

Verificando os pinos mostrados no Google Maps vindos do Protheus

Bom pessoal, por hoje é só.

Abraços e até a próxima.

Dan (Daniel Atilio)
Cristão de ramificação protestante. Especialista em Engenharia de Software pela FIB, graduado em Banco de Dados pela FATEC Bauru e técnico em informática pelo CTI da Unesp. Entusiasta de soluções Open Source e blogueiro nas horas vagas. Autor e mantenedor do portal Terminal de Informação.

6 Responses

  1. Almir Bandina disse:

    Atilio, muito interessante a matéria. obrigado por compartilhar

  2. George disse:

    Muito legal Atílio, obrigado ae!

  3. JUSCELINO ALVES DOS SANTOS disse:

    SHOW

Deixe uma resposta

Terminal de Informação