libcurlを使ってGoogle Maps Geolocation APIを呼び出して、周囲のWiFiの情報から緯度経度を求めるソフトを作ってみた。
この間のGeolocation APIを使う話の続きだ。
Windows 10で動くものをVisual Studio 2017のC言語でプログラムを書いた。
以前に古いGeolocation APIで作ったソフトの焼き直し版だ。
ソースコード(geolocation.c)はこのようになっている。ちょっと長い。そして、エラー処理とかが甘い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | #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つをこのソースコードとは別に用意する必要がある。
1 2 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行だけだ。
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)は、次のような内容だ。
1 2 3 4 5 6 7 8 9 10 11 | 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ファイルを作ってあり、これを使ってビルドする。
1 2 3 4 5 6 | 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機能で検出した周囲のアクセスポイントの情報から緯度経度情報を得られる。