libcurlを使ってGoogle Maps Geolocation APIを呼び出して、周囲のWiFiの情報から緯度経度を求めるソフトを作ってみた。
この間のGeolocation APIを使う話の続きだ。
Windows 10で動くものをVisual Studio 2017のC言語でプログラムを書いた。
以前に古いGeolocation APIで作ったソフトの焼き直し版だ。
ソースコード(geolocation.c)はこのようになっている。ちょっと長い。そして、エラー処理とかが甘い。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "curl.h"
#include "jsmn.h"
#include "google_maps_api_key.h"
#define LINELEN 1024
char url_str[LINELEN],mac_str[LINELEN],list_str[LINELEN*60],post_str[LINELEN*60];
struct MemoryStruct { char *memory; size_t size; };
struct MemoryStruct chunk;
int WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp);
int GetMACList(char *buf);
int FindMAC(char *buf,int pos);
int MakePOST(char *buf,int num,char *string);
int main()
{
CURL *curl;
CURLcode res;
struct curl_slist *list = NULL;
jsmn_parser p;
jsmntok_t tokens[40];
int pos = 0 , mac_cnt = 0 , ok_flag = 0;
char buf[256];
double lat , lng;
if (GetMACList(list_str) == -1) {
printf("Error: cannot get MAC Address\n");
return 1;
}
//printf("%s\n",list_str);
MakePOST(post_str,1,NULL);
while (1) {
pos=FindMAC(list_str,pos);
if (pos == -1) { break; }
if (mac_cnt!=0) { strcat(post_str,","); }
strncpy(mac_str,list_str+pos,17);
pos+=17;
MakePOST(post_str,2,mac_str);
mac_cnt++;
}
MakePOST(post_str,3,NULL);
if (mac_cnt==0) {
printf("WiFi MAC Address not found.\n");
return 1;
}
printf("JSON DATA:\n%s\n",post_str);
strcpy(url_str,"https://www.googleapis.com/geolocation/v1/geolocate?key=");
strcat(url_str,GOOGLE_MAPS_API_KEY);
chunk.memory = malloc(1); chunk.size = 0;
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, url_str);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_str );
list = curl_slist_append(list, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_perform(curl);
curl_easy_cleanup(curl);
printf("RESULT:\n%s\n", chunk.memory);
jsmn_init(&p);
jsmn_parse(&p, chunk.memory , tokens, 40);
if ((tokens[0].type == JSMN_OBJECT)&&(tokens[1].type == JSMN_STRING)) {
strncpy(buf, chunk.memory + tokens[1].start, tokens[1].end - tokens[1].start);
buf[tokens[1].end - tokens[1].start]='\0';
if (strcmp(buf,"location")==0) {
if ((tokens[2].type == JSMN_OBJECT)&&(tokens[3].type == JSMN_STRING)) {
strncpy(buf, chunk.memory + tokens[3].start, tokens[3].end - tokens[3].start);
buf[tokens[3].end - tokens[3].start]='\0';
if ((strcmp(buf,"lat")==0)&&(tokens[4].type == JSMN_PRIMITIVE)) {
strncpy(buf, chunk.memory + tokens[4].start, tokens[4].end - tokens[4].start);
buf[tokens[4].end - tokens[4].start]='\0';
//printf("lat=<%s>\n",buf);
sscanf(buf,"%lf",&lat);
}
if ((tokens[5].type == JSMN_STRING)) {
strncpy(buf, chunk.memory + tokens[5].start, tokens[5].end - tokens[5].start);
buf[tokens[5].end - tokens[5].start]='\0';
if ((strcmp(buf,"lng")==0)&&(tokens[6].type == JSMN_PRIMITIVE)) {
strncpy(buf, chunk.memory + tokens[6].start, tokens[6].end - tokens[6].start);
buf[tokens[6].end - tokens[6].start]='\0';
//printf("lng=<%s>\n",buf);
sscanf(buf,"%lf",&lng);
ok_flag=1;
}
}
}
}
}
if (ok_flag==1) {
printf("latitude=%.8f , longitude=%.8f \n",lat,lng);
} else {
printf("Not enougth data to find location.\n");
}
if (chunk.memory) { free(chunk.memory); }
getchar();
return 0;
}
int WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory = realloc(mem->memory, mem->size + realsize + 1);
if (mem->memory == NULL) { printf("Errpr: Not enough memory\n"); exit(1); }
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
int GetMACList(char *buf)
{
FILE *fp = NULL;
char linebuf[LINELEN];
buf[0]='\0';
if ((fp = _popen("netsh wlan show all | findstr BSSID", "r")) == NULL) {
return -1;
}
while (1) {
if (fgets(linebuf,LINELEN-1,fp)==NULL) { break; }
strcat(buf,linebuf);
}
_pclose(fp);
return 0;
}
int FindMAC(char *buf,int pos)
{
int i,j,l,c,cnt;
l=strlen(buf);
if (l>17) { l-=17; }
for (i=pos;i<l;i++) {
if ((buf[i+2]==':')&&(buf[i+5]==':')&&(buf[i+8]==':')&&(buf[i+11]==':')&&(buf[i+14]==':')) {
cnt=0;
for (j=0;j<17;j++) {
c=buf[i+j];
if (((c>='0')&&(c<='9'))||((c>='A')&&(c<='F'))||((c>='a')&&(c<='f'))) {
cnt++;
}
}
if (cnt==12) { return i; }
}
}
return -1;
}
int MakePOST(char *buf,int num,char *string)
{
if (num==1) {
buf[0]='\0';
strcat(buf, "{ ");
strcat(buf, " \"considerIp\": \"false\" , ");
strcat(buf, " \"wifiAccessPoints\": ");
strcat(buf, " [ ");
} else if (num==2) {
strcat(buf, " { ");
strcat(buf, "\"macAddress\": \"");
strcat(buf, string);
strcat(buf, "\" , ");
strcat(buf, " \"signalStrength\": -55 , ");
strcat(buf, " \"signalToNoiseRatio\": 0 ");
strcat(buf, " } ");
} else if (num==3) {
strcat(buf, " ] ");
strcat(buf, "} ");
}
return 0;
}
ヘッダファイルの部分で、この3つをこのソースコードとは別に用意する必要がある。
#include "curl.h" #include "jsmn.h" #include "google_maps_api_key.h"
curl.hは、libcurlのヘッダファイルだ。libcurlの32bitのdll版をソースからビルドして、そのライブラリを組み込んで使う。(古いバージョンのバイナリだとVisual Studio 2017でリンクエラーが出てリンクできなかったり、最新版のバイナリ版でもうまくリンクできなかったからだ。)
→ Visual Studio 2017でlibcurlを使おうとして、libcurlのソースからビルドして動かした
jsmn.hは、JSON形式のデータをparseして中身を取り出すのに使うライブラリだ。こちらは古いバージョンのまま使っている。
google_maps_api_key.hは、Google MapsのAPIを使うためのAPIキーを書いてあるファイルで、API Keyは利用者それぞれ各個人がKeyを準備する必要がある。
内容は次のような形式のヘッダファイルになっていて、GOOGLE_MAPS_API_KEYを定義している1行だけだ。
#define GOOGLE_MAPS_API_KEY "ここに各自のAPI Keyを書くこと"
プログラムの動作としては、前半で、popenを使ってWindowsのnetshコマンドを外部プロセスとして呼び出して、結果を取り込む。そして、MACアドレス(BSSID)のデータを取り出す。
プログラムの後半では、MACアドレスのデータをGeolocation APIに渡せるようにJSON形式のデータに変換して、それを使ってAPIを呼び出して、結果を得る。そしてjsmnを使って結果のJSON形式のデータから緯度経度を取り出す。
という内容だ。
ソースコードのビルドにはVisual Studioのnmakeコマンドをコマンドラインで使う。
メイクファイル(Makefile.txt)は、次のような内容だ。
TARGET = geolocation.exe OBJS = geolocation.obj LIBS = libcurl.lib jsmn.lib LIBPATH = lib INCPATH = -I"include/curl" -I"include" CFLAGS = -c -DCURL_STATICLIB $(INCPATH) .c.obj: cl $(CFLAGS) $< $(TARGET): $(OBJS) cl $(OBJS) $(LIBS) /link/LIBPATH:$(LIBPATH) /out:$(TARGET)
コマンドラインで入力する代わりに次のようなbatファイルを作ってあり、これを使ってビルドする。
rem set environment for Visual Studio 2017 call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" x86 rem build del *.obj nmake -f makefile.txt
ビルドしてできたexeファイルを実行すれば、あとは自動でPCに内蔵されたWiFi機能で検出した周囲のアクセスポイントの情報から緯度経度情報を得られる。