2015-09-01 08:19:52 -07:00
/*
2016-02-16 06:09:30 -07:00
* OwnTracks Recorder
2023-01-02 06:05:26 -07:00
* Copyright ( C ) 2015 - 2023 Jan - Piet Mens < jpmens @ gmail . com >
2015-09-01 08:19:52 -07:00
*
2016-02-16 06:09:30 -07:00
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version 2
* of the License , or ( at your option ) any later version .
2015-09-01 08:19:52 -07:00
*
2016-02-16 06:09:30 -07:00
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2015-09-01 08:19:52 -07:00
*
2016-02-16 06:09:30 -07:00
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 , USA .
2015-09-01 08:19:52 -07:00
*/
2015-08-14 09:40:35 -07:00
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
2018-05-04 00:34:02 -07:00
# include <ctype.h>
2015-08-14 09:40:35 -07:00
# include <curl/curl.h>
# include "utstring.h"
# include "geo.h"
# include "json.h"
2015-09-21 07:07:31 -07:00
# include "util.h"
2015-08-14 09:40:35 -07:00
2018-05-04 00:34:02 -07:00
typedef enum {
GOOGLE ,
2018-10-23 05:09:32 -07:00
OPENCAGE ,
REVGEOD
2018-05-04 00:34:02 -07:00
} geocoder ;
# define GOOGLE_URL "https: //maps.googleapis.com/maps/api/geocode/json?latlng=%lf,%lf&sensor=false&language=EN&key=%s"
# define OPENCAGE_URL "https: //api.opencagedata.com/geocode/v1/json?q=%lf+%lf&key=%s&abbrv=1&no_record=1&limit=1&format=json"
2015-09-11 02:07:01 -07:00
2019-01-20 07:00:02 -07:00
# define REVGEOD_URL "http: //%s/rev?lat=%lf&lon=%lf&app=recorder" /* "host:port", lat, lon */
2018-10-23 05:09:32 -07:00
2015-08-14 09:40:35 -07:00
static CURL * curl ;
static size_t writemem ( void * contents , size_t size , size_t nmemb , void * userp )
{
UT_string * cbuf = ( UT_string * ) userp ;
size_t realsize = size * nmemb ;
utstring_bincpy ( cbuf , contents , realsize ) ;
return ( realsize ) ;
}
2015-09-25 03:26:41 -07:00
static int goog_decode ( UT_string * geodata , UT_string * addr , UT_string * cc , UT_string * locality )
2015-08-14 09:40:35 -07:00
{
2015-08-16 03:23:47 -07:00
JsonNode * json , * results , * address , * ac , * zeroth , * j ;
2015-08-14 09:40:35 -07:00
/*
* We are parsing this . I want the formatted_address in ` addr ' and
* the country code short_name in ` cc '
*
* {
* " results " : [
* {
* " address_components " : [
* {
* " long_name " : " New Zealand " ,
* " short_name " : " NZ " ,
* " types " : [ " country " , " political " ]
* } , . . .
* ] ,
* " formatted_address " : " 59 Example Street, Christchurch 8081, New Zealand " ,
*/
2015-09-21 07:07:31 -07:00
if ( ( json = json_decode ( UB ( geodata ) ) ) = = NULL ) {
2015-08-14 09:40:35 -07:00
return ( 0 ) ;
}
2015-08-16 03:23:47 -07:00
/*
* Check for :
*
* { " error_message " : " You have exceeded your daily request quota for this API. We recommend registering for a key at the Google Developers Console: https://console.developers.google.com/ " ,
* " results " : [ ] ,
* " status " : " OVER_QUERY_LIMIT "
* }
*/
2015-09-21 07:07:31 -07:00
// printf("%s\n", UB(geodata));
2015-08-16 03:23:47 -07:00
if ( ( j = json_find_member ( json , " status " ) ) ! = NULL ) {
// printf("}}}}}} %s\n", j->string_);
2015-08-16 03:49:51 -07:00
if ( strcmp ( j - > string_ , " OK " ) ! = 0 ) {
2015-09-21 07:07:31 -07:00
fprintf ( stderr , " revgeo: %s (%s) \n " , j - > string_ , UB ( geodata ) ) ;
2015-09-25 03:26:41 -07:00
json_delete ( json ) ;
2015-08-16 03:23:47 -07:00
return ( 0 ) ;
}
}
2015-08-14 09:40:35 -07:00
if ( ( results = json_find_member ( json , " results " ) ) ! = NULL ) {
if ( ( zeroth = json_find_element ( results , 0 ) ) ! = NULL ) {
address = json_find_member ( zeroth , " formatted_address " ) ;
if ( ( address ! = NULL ) & & ( address - > tag = = JSON_STRING ) ) {
utstring_printf ( addr , " %s " , address - > string_ ) ;
}
}
/* Country */
if ( ( ac = json_find_member ( zeroth , " address_components " ) ) ! = NULL ) {
JsonNode * comp , * j ;
2015-09-25 03:26:41 -07:00
int have_cc = 0 , have_locality = 0 ;
2015-08-14 09:40:35 -07:00
json_foreach ( comp , ac ) {
JsonNode * a ;
if ( ( j = json_find_member ( comp , " types " ) ) ! = NULL ) {
json_foreach ( a , j ) {
if ( ( a - > tag = = JSON_STRING ) & & ( strcmp ( a - > string_ , " country " ) = = 0 ) ) {
JsonNode * c ;
if ( ( c = json_find_member ( comp , " short_name " ) ) ! = NULL ) {
utstring_printf ( cc , " %s " , c - > string_ ) ;
have_cc = 1 ;
break ;
}
2015-09-25 03:26:41 -07:00
} else if ( ( a - > tag = = JSON_STRING ) & & ( strcmp ( a - > string_ , " locality " ) = = 0 ) ) {
JsonNode * l ;
if ( ( l = json_find_member ( comp , " long_name " ) ) ! = NULL ) {
utstring_printf ( locality , " %s " , l - > string_ ) ;
have_locality = 1 ;
break ;
}
2015-08-14 09:40:35 -07:00
}
}
}
2015-09-25 03:26:41 -07:00
if ( have_cc & & have_locality )
2015-08-14 09:40:35 -07:00
break ;
}
}
}
json_delete ( json ) ;
return ( 1 ) ;
}
2018-10-23 05:09:32 -07:00
static int revgeod_decode ( UT_string * geodata , UT_string * addr , UT_string * cc , UT_string * locality )
{
JsonNode * json , * village , * j , * a ;
/*
* We are parsing this data returned from revgeod ( 1 ) :
*
* { " address " : { " village " : " La Terre Noire, 77510 Sablonnières, France " , " locality " : " Sablonnières " , " cc " : " FR " , " s " : " lmdb " } }
*
*/
if ( ( json = json_decode ( UB ( geodata ) ) ) = = NULL ) {
return ( 0 ) ;
}
if ( ( a = json_find_member ( json , " address " ) ) ! = NULL ) {
if ( ( village = json_find_member ( a , " village " ) ) ! = NULL ) {
if ( village - > tag = = JSON_STRING ) {
utstring_printf ( addr , " %s " , village - > string_ ) ;
}
}
if ( ( j = json_find_member ( a , " locality " ) ) ! = NULL ) {
if ( j - > tag = = JSON_STRING ) {
utstring_printf ( locality , " %s " , j - > string_ ) ;
}
}
if ( ( j = json_find_member ( a , " cc " ) ) ! = NULL ) {
if ( j - > tag = = JSON_STRING ) {
utstring_printf ( cc , " %s " , j - > string_ ) ;
}
}
}
json_delete ( json ) ;
return ( 1 ) ;
}
2024-01-31 02:56:10 -07:00
static int opencage_decode ( UT_string * geodata , UT_string * addr , UT_string * cc , UT_string * locality , UT_string * tzname )
2018-05-04 00:34:02 -07:00
{
JsonNode * json , * results , * address , * ac , * zeroth ;
2024-01-31 02:56:10 -07:00
JsonNode * annotations , * timezone ;
2018-05-04 00:34:02 -07:00
/*
* We are parsing this . I want the formatted in ` addr ' and
* the country code short_name in ` cc '
*
* {
* " documentation " : " https://geocoder.opencagedata.com/api " ,
* " licenses " : [
* {
* " name " : " CC-BY-SA " ,
* " url " : " http://creativecommons.org/licenses/by-sa/3.0/ "
* } ,
* {
* " name " : " ODbL " ,
* " url " : " http://opendatacommons.org/licenses/odbl/summary/ "
* }
* ] ,
* " rate " : {
* " limit " : 2500 ,
* " remaining " : 2495 ,
* " reset " : 1525392000
* } ,
* " results " : [
* {
2024-01-31 02:56:10 -07:00
* " annotations " : {
* " timezone " : {
* " name " : " America/Cancun " ,
* " now_in_dst " : 0 ,
* " offset_sec " : - 18000 ,
* " offset_string " : " -0500 " ,
* " short_name " : " EST "
* } ,
2018-05-04 00:34:02 -07:00
* . . .
* " components " : {
* " city " : " Sablonnières " ,
* " country " : " France " ,
* " country_code " : " fr " ,
* " place " : " La Terre Noire " ,
* } ,
* " formatted " : " La Terre Noire, 77510 Sablonnières, France " ,
*/
if ( ( json = json_decode ( UB ( geodata ) ) ) = = NULL ) {
return ( 0 ) ;
}
if ( ( results = json_find_member ( json , " results " ) ) ! = NULL ) {
if ( ( zeroth = json_find_element ( results , 0 ) ) ! = NULL ) {
address = json_find_member ( zeroth , " formatted " ) ;
if ( ( address ! = NULL ) & & ( address - > tag = = JSON_STRING ) ) {
utstring_printf ( addr , " %s " , address - > string_ ) ;
}
}
2024-01-31 02:56:10 -07:00
if ( ( annotations = json_find_member ( zeroth , " annotations " ) ) ! = NULL ) {
if ( ( timezone = json_find_member ( annotations , " timezone " ) ) ! = NULL ) {
JsonNode * tz ;
// puts(json_stringify(timezone, NULL));
if ( ( tz = json_find_member ( timezone , " name " ) ) ! = NULL )
{
utstring_printf ( tzname , " %s " , tz - > string_ ) ;
}
}
}
2018-05-04 00:34:02 -07:00
if ( ( ac = json_find_member ( zeroth , " components " ) ) ! = NULL ) {
/*
* {
* " ISO_3166-1_alpha-2 " : " FR " ,
* " _type " : " place " ,
* " city " : " Sablonnières " ,
* " country " : " France " ,
* " country_code " : " fr " ,
* " county " : " Seine-et-Marne " ,
* " place " : " La Terre Noire " ,
* " political_union " : " European Union " ,
* " postcode " : " 77510 " ,
* " state " : " Île-de-France "
* }
*/
JsonNode * j ;
if ( ( j = json_find_member ( ac , " country_code " ) ) ! = NULL ) {
if ( j - > tag = = JSON_STRING ) {
char * bp = j - > string_ ;
int ch ;
while ( * bp ) {
ch = ( islower ( * bp ) ) ? toupper ( * bp ) : * bp ;
utstring_printf ( cc , " %c " , ch ) ;
+ + bp ;
}
}
}
if ( ( j = json_find_member ( ac , " city " ) ) ! = NULL ) {
if ( j - > tag = = JSON_STRING ) {
utstring_printf ( locality , " %s " , j - > string_ ) ;
}
}
}
}
json_delete ( json ) ;
return ( 1 ) ;
}
2016-01-05 01:59:49 -07:00
JsonNode * revgeo ( struct udata * ud , double lat , double lon , UT_string * addr , UT_string * cc )
2015-08-14 09:40:35 -07:00
{
static UT_string * url ;
static UT_string * cbuf ; /* Buffer for curl GET */
2015-09-25 03:26:41 -07:00
static UT_string * locality = NULL ;
2024-01-31 02:56:10 -07:00
static UT_string * tzname = NULL ;
2018-05-04 00:34:02 -07:00
long http_code ;
2015-08-14 09:40:35 -07:00
CURLcode res ;
int rc ;
JsonNode * geo ;
time_t now ;
2018-05-04 00:34:02 -07:00
geocoder geocoder ;
2015-08-14 09:40:35 -07:00
if ( ( geo = json_mkobject ( ) ) = = NULL ) {
return ( NULL ) ;
}
if ( lat = = 0.0 L & & lon = = 0.0 L ) {
utstring_printf ( addr , " Unknown (%lf,%lf) " , lat , lon ) ;
utstring_printf ( cc , " __ " ) ;
return ( geo ) ;
}
utstring_renew ( url ) ;
utstring_renew ( cbuf ) ;
2015-09-25 03:26:41 -07:00
utstring_renew ( locality ) ;
2024-01-31 02:56:10 -07:00
utstring_renew ( tzname ) ;
2016-01-05 01:59:49 -07:00
2018-05-04 00:34:02 -07:00
if ( ! ud - > geokey | | ! * ud - > geokey ) {
utstring_printf ( addr , " Unknown (%lf,%lf) " , lat , lon ) ;
utstring_printf ( cc , " __ " ) ;
return ( geo ) ;
}
if ( strncmp ( ud - > geokey , " opencage: " , strlen ( " opencage: " ) ) = = 0 ) {
utstring_printf ( url , OPENCAGE_URL , lat , lon , ud - > geokey + strlen ( " opencage: " ) ) ;
geocoder = OPENCAGE ;
2018-10-23 05:09:32 -07:00
} else if ( strncmp ( ud - > geokey , " revgeod: " , strlen ( " revgeod: " ) ) = = 0 ) {
/* revgeod:localhost:8865 */
utstring_printf ( url , REVGEOD_URL , ud - > geokey + strlen ( " revgeod: " ) , lat , lon ) ;
geocoder = REVGEOD ;
2016-01-05 01:59:49 -07:00
} else {
2018-05-04 00:34:02 -07:00
utstring_printf ( url , GOOGLE_URL , lat , lon , ud - > geokey ) ;
geocoder = GOOGLE ;
2016-01-05 01:59:49 -07:00
}
2018-10-23 05:09:32 -07:00
// fprintf(stderr, "--------------- %s\n", UB(url));
2018-05-04 00:34:02 -07:00
2015-09-21 07:07:31 -07:00
curl_easy_setopt ( curl , CURLOPT_URL , UB ( url ) ) ;
2015-09-16 03:14:52 -07:00
curl_easy_setopt ( curl , CURLOPT_USERAGENT , " OwnTracks-Recorder/1.0 " ) ;
2015-08-14 09:40:35 -07:00
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
2015-11-10 01:21:07 -07:00
curl_easy_setopt ( curl , CURLOPT_NOSIGNAL , 1L ) ;
2018-11-29 12:16:10 -07:00
curl_easy_setopt ( curl , CURLOPT_TIMEOUT_MS , GEOCODE_TIMEOUT ) ;
2015-08-14 09:40:35 -07:00
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , writemem ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , ( void * ) cbuf ) ;
res = curl_easy_perform ( curl ) ;
2018-05-04 00:34:02 -07:00
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_code ) ;
if ( res ! = CURLE_OK | | http_code ! = 200 ) {
utstring_printf ( addr , " revgeo failed for (%lf,%lf): HTTP status_code==%ld " , lat , lon , http_code ) ;
2015-08-14 09:40:35 -07:00
utstring_printf ( cc , " __ " ) ;
fprintf ( stderr , " curl_easy_perform() failed: %s \n " ,
curl_easy_strerror ( res ) ) ;
json_delete ( geo ) ;
return ( NULL ) ;
}
2018-05-04 00:34:02 -07:00
switch ( geocoder ) {
case GOOGLE :
rc = goog_decode ( cbuf , addr , cc , locality ) ;
break ;
case OPENCAGE :
2024-01-31 02:56:10 -07:00
rc = opencage_decode ( cbuf , addr , cc , locality , tzname ) ;
2018-05-04 00:34:02 -07:00
break ;
2018-10-23 05:09:32 -07:00
case REVGEOD :
rc = revgeod_decode ( cbuf , addr , cc , locality ) ;
break ;
2018-05-04 00:34:02 -07:00
}
2015-08-14 09:40:35 -07:00
2018-05-04 00:34:02 -07:00
if ( ! rc ) {
2015-08-16 03:23:47 -07:00
json_delete ( geo ) ;
return ( NULL ) ;
2015-08-14 09:40:35 -07:00
}
2015-09-21 07:07:31 -07:00
// fprintf(stderr, "revgeo returns %d: %s\n", rc, UB(addr));
2015-08-14 09:40:35 -07:00
time ( & now ) ;
2015-09-21 07:07:31 -07:00
json_append_member ( geo , " cc " , json_mkstring ( UB ( cc ) ) ) ;
json_append_member ( geo , " addr " , json_mkstring ( UB ( addr ) ) ) ;
2015-08-14 09:40:35 -07:00
json_append_member ( geo , " tst " , json_mknumber ( ( double ) now ) ) ;
2015-09-25 03:26:41 -07:00
json_append_member ( geo , " locality " , ( utstring_len ( locality ) > 0 ) ?
json_mkstring ( UB ( locality ) ) : json_mknull ( ) ) ;
2024-01-31 02:56:10 -07:00
json_append_member ( geo , " tzname " , ( utstring_len ( tzname ) > 0 ) ?
json_mkstring ( UB ( tzname ) ) : json_mknull ( ) ) ;
2015-08-14 09:40:35 -07:00
return ( geo ) ;
}
void revgeo_init ( )
{
curl = curl_easy_init ( ) ;
}
2016-02-23 09:30:48 -07:00
void revgeo_free ( )
{
curl_easy_cleanup ( curl ) ;
curl = NULL ;
}
2015-08-14 09:40:35 -07:00
#if 0
int main ( )
{
2015-09-25 03:26:41 -07:00
double lat = 52.25458 , lon = 5.1494 ;
2015-08-14 09:40:35 -07:00
double clat = 51.197500 , clon = 6.699179 ;
2015-09-25 03:26:41 -07:00
UT_string * location = NULL , * cc = NULL ;
JsonNode * json ;
char * js ;
2015-08-14 09:40:35 -07:00
curl = curl_easy_init ( ) ;
utstring_renew ( location ) ;
2015-09-25 03:26:41 -07:00
utstring_renew ( cc ) ;
2016-01-05 01:59:49 -07:00
if ( ( json = revgeo ( NULL , lat , lon , location , cc ) ) ! = NULL ) {
2015-09-25 03:26:41 -07:00
js = json_stringify ( json , " " ) ;
printf ( " %s \n " , js ) ;
free ( js ) ;
} else {
printf ( " Cannot get revgeo \n " ) ;
2015-08-14 09:40:35 -07:00
}
2016-01-05 01:59:49 -07:00
if ( ( json = revgeo ( NULL , clat , clon , location , cc ) ) ! = NULL ) {
2015-09-25 03:26:41 -07:00
js = json_stringify ( json , " " ) ;
printf ( " %s \n " , js ) ;
free ( js ) ;
} else {
printf ( " Cannot get revgeo \n " ) ;
2015-08-14 09:40:35 -07:00
}
curl_easy_cleanup ( curl ) ;
return ( 0 ) ;
}
# endif