Google Web履歴からWeb活動履歴をダウンロード

注意

ここのプログラムは、不具合があるため、正常に動作しない可能性が高いです。不具合を修正したソースコードは、下記の記事から取得できます。
http://d.hatena.ne.jp/hippu/20091207/1260206750

はじめに

GoogleツールバーWebブラウザにインストールすると、Googleが検索活動の履歴(検索クエリと検索結果の閲覧履歴)やを記録し始める。

設定次第では検索活動の履歴だけでなく、ハイパーリンクやブックマーク、ブラウザの戻る・進む操作などを利用したWebページへのアクセスも記録する。

記録された履歴は、Google Web Historyというサービスから閲覧したり、検索することができる。

Google Web Historyから活動履歴をダウンロードしようと思ったのだが、備え付けのダウンロード機能がなかった。

幸いにもGoogle Web Historyは活動履歴をRSS形式で出力してくれるので、それを利用して自分でダウンロードプログラムを作成した。

内容

  • プログラム概要
  • 出力内容

プログラム概要

  • 作成言語:python 2.6(jsonを使用、simplejsonなどを導入すれば2.5、2.4でも動くはず)
  • 機能概要
    • 全履歴のダウンロード
    • 日付範囲を指定したダウンロード
    • 履歴の検索結果のダウンロード
  • 出力形式
  • 使い方

下のソースコードに適当なファイル名filenameを付けて、コマンドラインからpython filename -hとすればヘルプが表示されるのでそちらを参考。

#-*- coding:utf-8 -*-
import urllib2, urllib
from datetime import timedelta, datetime
import time
import json
from xml.dom.minidom import parseString 
import sys
import getopt
from urlparse import urlparse, urlunparse
import urllib
import re

def getText(elm):
    nodelist = elm.childNodes
    rc = ""
    for node in nodelist:
        if node.nodeType == node.TEXT_NODE:
            rc = rc + node.data
    return rc

def toDict(elm):
    ob = {}
    ob["id"] = getText(elm.getElementsByTagName("guid")[0])
    ob["title"] = getText(elm.getElementsByTagName("title")[0])
    ob["link"] = getText(elm.getElementsByTagName("link")[0])
    ob["date"] = getText(elm.getElementsByTagName("pubDate")[0])
    category = getText(elm.getElementsByTagName("category")[0])
    ob["category"] = category
    ob["description"] = getText(elm.getElementsByTagName("description")[0])
    
    
    if category == "browser result":
        if elm.getElementsByTagName("smh:bkmk"):
             ob["bkmk"] = getText(elm.getElementsByTagName("smh:bkmk")[0])
             ob["bkmkId"] = getText(elm.getElementsByTagName("smh:bkmk_id")[0])
             
             ob["bkmkLabels"] = []
             for label in elm.getElementsByTagName("smh:bkmk_label"):
                 ob["bkmkLabels"].append(getText(label))
                 
             ob["bkmkTitle"] = getText(elm.getElementsByTagName("smh:bkmk_title")[0])
             ob["category"] = "bookmark"
    elif category.endswith("result"):
        ob["queryId"] = getText(elm.getElementsByTagName("smh:query_guid")[0])
    elif category.endswith("query"):
        pass
    else:
        raise Error("")
    
    return ob

def decideDateRange(startDate, endDate, all):
    if all:
        endDate = datetime.now() + timedelta(days=1)
        startDate = datetime(1900, 1, 1)
    else:
        if not startDate and not endDate:
            startDate = datetime.now() - timedelta(days=1)
            endDate = datetime.now()
        elif not startDate:
            startDate = endDate
            endDate = endDate + timedelta(days=1)
        elif not endDate:
            endDate = startDate + timedelta(days=1)
        elif endDate < startDate:
            temp = startDate
            startDate = endDate
            endDate = temp
            
    return startDate, endDate 

def getWebHistoryOrderByDate(opener, startDate, maxTime, num, query, length):
    if query:
        mode = "find"
    else:
        mode = "lookup"
        
    count = 0
    entries = []
    while True:
        params = {"hl" : "en", "output" : "rss", "lr" : "lang_ja", "max" : maxTime, "num" : num, "q" : query, "sort" : "date"}
        req = urllib2.Request('http://www.google.com/history/%s?%s' % (mode, urllib.urlencode(params)))
#       rssをutf8で出力させるのに必要
        req.add_header("User-Agent", "Mozilla/5.0")
        pagehandle = opener.open(req)
        
        if pagehandle.code != 200:
            raise Error("")
        
        dom = parseString(pagehandle.read())
        if not dom.getElementsByTagName("item"):
            dom.unlink()
            return entries
        
        for entry in dom.getElementsByTagName("item"):
            date = getText(entry.getElementsByTagName("pubDate")[0])
            d = datetime.strptime(date, "%a, %d %b %Y %H:%M:%S GMT") + timedelta(hours=9)
            tempTime = int(time.mktime(time.strptime(d.strftime("%y%m%d %H:%M:%S"), "%y%m%d %H:%M:%S")) * (10 ** 6))
            #サマータイムの都合で、maxTimeの一時間後の履歴までが取得されるため
            #重複した履歴を無視
            if tempTime > maxTime:
                continue
            
            if d < startDate or length <= count:
                dom.unlink()
                return entries
            
            entries.append(toDict(entry))
            count += 1
        
        d = datetime.strptime(entries[-1]["date"], "%a, %d %b %Y %H:%M:%S GMT") + timedelta(hours=9)
        maxTime = int(time.mktime(time.strptime(d.strftime("%a, %d %b %Y %H:%M:%S"), "%a, %d %b %Y %H:%M:%S")) * (10 ** 6))
        dom.unlink()
    
    return entries

def getWebHistoryOrderByRelation(opener, start, num, query, length):
    entries = []
    count = 0
    while True:
        params = {"hl" : "ja", "output" : "rss", "lr" : "lang_ja", "start" : start, "num" : num, "q" : query, "sort" : ""}
        req = urllib2.Request('http://www.google.com/history/find?%s' % (urllib.urlencode(params)))
#       rssをutf8で出力させるのに必要
        req.add_header("User-Agent", "Mozilla/5.0")
        pagehandle = opener.open(req)
        
        if pagehandle.code != 200:
            raise Error("")
        
        dom = parseString(pagehandle.read())
        if not dom.getElementsByTagName("item"):
            dom.unlink()
            return entries
        
        for entry in dom.getElementsByTagName("item"):
            if length <= count:
                dom.unlink()
                return entries
            else:
                entries.append(toDict(entry))
                count += 1
            
            
            
        l = len(dom.getElementsByTagName("item"))
        start += l
        dom.unlink()
        
    return entries    

def dateStrCmp(dateStr1, dateStr2):
    d1 = datetime.strptime(dateStr1, "%a, %d %b %Y %H:%M:%S GMT")
    d2 = datetime.strptime(dateStr2, "%a, %d %b %Y %H:%M:%S GMT")
    
    if d1 > d2: return 1
    elif d1 < d2: return -1
    else:   return 0
    
def correctRelation(entries):
    queries = {}
    p = re.compile("[0-9]+")
    for entry in entries:
        if entry["category"].endswith("query"):
            mob = p.match(entry["description"])
            if mob:
                count = int(mob.group())
            else:
                count = 0
            queries[entry["id"]] = [entry, count]
            
    unknownSearchResults = []
    for entry in entries:
        if entry["category"].endswith("result") and entry["category"] != "browser result":
            if entry["queryId"] in queries:
                queries[entry["queryId"]][1] -= 1
            else:
                unknownSearchResults.append(entry)
    
    unknownQueries = [[v[0], v[1]] for v in queries.values() if v[1] > 0]
    unknownQueries.sort(lambda x, y: -dateStrCmp(x[0]["date"], y[0]["date"]))
    
    for e in unknownSearchResults:
        if len(unknownQueries) == 0:
            break
        
        query, count = unknownQueries[0]
        if dateStrCmp(e["date"], query["date"]) < 0:
            unknownQueries = unknownQueries[1:]
        else:
            query, count = unknownQueries[0]
            e["queryId"] = query["id"]
            unknownQueries[0][1] -= 1
            if unknownQueries[0][1] <= 0:
                unknownQueries = unknownQueries[1:]
                
def discoverGoogleScholar(entries):
    for e in entries:
        if e["category"] == "browser result" and e["title"].startswith("http://scholar.google.co.jp"):
            o = urlparse(e["title"])
            
            if o.path in ["", "/", "/schp"]:
                e["title"] = "Google Scholar"
            elif o.path == "/scholar":
                query = {}
                for pair in o.query.split("&"):
                    k, v = pair.split("=")
                    k = urllib.unquote_plus(k)
                    v = urllib.unquote_plus(v)
                    query[k] = v
                
                if "q" in query and "start" not in query:
                    e["category"] = "scholar query"
                    e["title"] = query["q"].encode('raw_unicode_escape').decode('utf8')

#広告、画像などの断片ページのURLに頻出する文字列
FLAGMENT_PAGE_WORDS = [
                       "ad", "rot", "banner", "rss", "footer", "cookie", "headline", "gadget", "widget", "rcm", "doubleclick",
                       "right", "button", "send", "visitor", "affiliate", "img", "wrs", "topics", "thumb", "access"]

def findFlagmentPageWords(url):
    url = url.lower()
    for w in FLAGMENT_PAGE_WORDS:
        if url.find(w) != -1:
            return True
    return False

#断片的なページのURLにはほぼ出現しない文字列
NOT_FLAGMENT_PAGE_WORDS = ["pdf", "entry", "archive"]

def findNotFlagmentPageWords(url):
    url = url.lower()
    for w in NOT_FLAGMENT_PAGE_WORDS:
        if url.find(w) != -1:
            return True
    return False


def removeNoise(entries):
    entries = [e for e in entries if not (e["category"] == "browser result" and e["title"].startswith("https://"))]
    entries = [e for e in entries if not (e["category"] == "browser result" and findFlagmentPageWords(e["title"]))]
    entries = [e for e in entries if e["category"] != "browser result" or not e["title"].startswith("http://") or findNotFlagmentPageWords(e["title"])]
        
    entries = [e for e in entries if e["link"].lower().find("mail") == -1]
    entries = [e for e in entries if e["title"].lower().find("headline") == -1]
    entries = [e for e in entries if e["title"].lower().find(u"ヘッドライン") == -1]
    
    l = len(entries) 
    if  l <= 1:
        return entries
    
    resultEntries = []
    for i in range(0, l - 1):
        cur = entries[i]
        next = entries[i+1]
        
        if cur["link"].strip() == "":
            continue
        
        if cur["category"] == "browser result" and cur["link"] == next["link"]:
            cd = datetime.strptime(cur["date"], "%a, %d %b %Y %H:%M:%S GMT")
            nd =  datetime.strptime(next["date"], "%a, %d %b %Y %H:%M:%S GMT")
            td = cd - nd
            if td.seconds < 3600:
               continue
        resultEntries.append(cur)
    resultEntries.append(next)

    return resultEntries

def getWebHistory(user, passwd, startDate=None, endDate=None, all=False, num=500, query=None, sortedByDate=True, length=sys.maxint, start=0):
    if all:
        num = 1000
        start = 0
        length = sys.maxint
        
    num = min(num, length)
    
    passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
    passman.add_password(None, "www.google.com", user, passwd)
    authhandler = urllib2.HTTPBasicAuthHandler(passman)
    opener = urllib2.build_opener(authhandler)
    
    if sortedByDate:
        startDate, endDate = decideDateRange(startDate, endDate, all)
        print "startDate", startDate
        print "endDate", endDate
        maxTime = int(time.mktime(time.strptime(endDate.strftime("%y%m%d %H:%M:%S"), "%y%m%d %H:%M:%S")) * (10 ** 6))
        entries = getWebHistoryOrderByDate(opener, startDate, maxTime, num, query, length)
        
    elif query:
        entries = getWebHistoryOrderByRelation(opener, start, num, query, length)
    
    result = {"user" : user, "date" : datetime.now().strftime("%Y%m%d %H:%M:%S"), "entries" : entries, "query" : query, "all" : all, "sortedByDate" : sortedByDate, "start" : start}
    if sortedByDate:
        result["startDate"] = startDate.strftime("%Y%m%d %H:%M:%S")
        result["endDate"] = endDate.strftime("%Y%m%d %H:%M:%S")
    elif query:
        result["startDate"] = None
        result["endDate"] = None
    
    return result
        
def printHelp():
    print "Usage: getwebhistory.py -u username -p password [OPTION]"
    print u"-s <date> 履歴を取得する期間の始まりの日時をyyyymmddまたはyyyymmdd hh:mm:dd形式で指定"
    print u"-e <date> 履歴を取得する期間の終わりの日時をyyyymmddまたはyyyymmdd hh:mm:dd形式で指定"
    print u"-q <query> 履歴を検索するためのクエリを指定"
    print u"-l <length> 履歴の取得数を指定"
    print u"-n <num> 一回のHttpリクエストで取得する履歴の数(最大1000)を指定"
    print u"-h --help プログラムの使用方法を表示して、プログラムを終了"
    print u"-f <file> 取得した履歴を出力するファイル名を指定"
    print u"--all このオプションを指定すると履歴を全て取得します。-q、-f、--order-relation、--raw、--not-correct-relation以外のオプションは無視します"
    print u"--order-relation このオプションを指定すると履歴を関連度順に取得します。-qで空でないクエリを指定したときのみ有効です。"
    print u"--start=<num> 取得する履歴のオフセットを指定します。-qに空でないクエリを指定し、かつ--order-relationを指定したときのみ有効です。"
    print u"--raw このオプションを指定すると、Google Web Historyから取得した履歴をそのまま出力します。指定しない場合は、タイトルやURLから不要なページを推測し、それを除去します。"
    print u"--not-correct-relation このオプションを指定するとクエリのIDから単純に判断できないクエリと検索結果の対応の推測を行いません。オプションを指定しない場合は、この推測を行います。"
    
if __name__ == '__main__':
    print sys.argv
    opts, args = getopt.getopt(sys.argv[1:], ":u:p:s:e:q:l:n:hf:", ["all", "order-relation", "start=", "help", "raw", "not-correct-relation"])
    for o, a in opts:
        if o in ["-h", "--help"]:
            printHelp()
            sys.exit()
    
    user = None
    passwd = None
    startDate=None
    endDate=None
    query = None
    length = length=sys.maxint
    num = 300
    sortedByDate=True
    start=0
    fname = None
    all = False
    raw = False
    correctRelationFlag = True
    
    for o, a in opts:
        print o, a
        if o == "-u":
            user = a 
        elif o == "-p":
            passwd = a
        if o == "-s":
            try:
                startDate = datetime.strptime(a, "%Y%m%d")
            except ValueError:
                startDate = datetime.strptime(a, "%Y%m%d %H:%M:%S")
        elif o == "-e":
            try:
                endDate = datetime.strptime(a, "%Y%m%d")
            except ValueError:
                endDate = datetime.strptime(a, "%Y%m%d %H:%M:%S")
        elif o == "-q":
            query = a
        elif o == "-l":
            length = int(l)
        elif o == "-n":
            num = int(a)
        elif o == "-f":
            fname = a
        elif o == "--all":
            all = True
        elif o == "--order-relation":
            sortedByDate = False
        elif o == "--start":
            start = int(a)
        elif o == "--raw":
            raw = True
        elif o == "--not-correct-relation":
            correctRelationFlag = False
            
    result = getWebHistory(user, passwd, startDate, endDate, all, num, query, sortedByDate, length, start)
    
#    discoverGoogleScholar(result["entries"])
    if not raw:
        result["entries"] = removeNoise(result["entries"])
    
    if correctRelationFlag:
        correctRelation(result["entries"])
    
    #重複した履歴がないか確認
    entries = result["entries"]
    for i in range(0, len(entries) -1):
        d1 = datetime.strptime(entries[i]["date"], "%a, %d %b %Y %H:%M:%S GMT")
        d2 = datetime.strptime(entries[i+1]["date"], "%a, %d %b %Y %H:%M:%S GMT")
        if d1 < d2:
            print entries[i]["title"], entries[i+1]["title"]
            raise Exception("")

    if not fname:
        print json.dumps(result)
    else:
        fo = open(fname, "w")
        fo.write(json.dumps(result, indent=2))
        fo.close()

出力内容

このプログラムでは、取得した履歴のデータをJSON形式で出力します。
出力内容は、履歴の取得時に指定した条件と履歴のデータです。
履歴データの持つ情報は、履歴の種類によって異なります。

トップレベルの出力(187行目付近を参照)
  • user:ユーザ名
  • date:ダウンロード日時
  • entries:履歴データ
  • query:クエリ
  • all:全ての履歴を取得したか否か
  • start:取得開始位置
  • sortedByDate:
  • startDate:履歴の取得するときの日時の下限値
  • endDate:履歴の取得するときの日時の上限値
履歴データについて
  • id
  • title:閲覧ページ名、クエリ
  • link:閲覧ページや検索結果ページのURL
  • date:閲覧日時、検索実行日時
  • description:不明
  • category:閲覧データの種類
    • XXX query:検索クエリ(XXXにはweb, map, imagesなどが入る)
    • XXX result:検索結果中で閲覧したページ(XXXにはweb, map, imagesなどが入る)
      • queryId:検索クエリのID
    • browser result:進む・戻る、ブックマークからのページアクセスなどのブラウザ操作によるページ閲覧
    • bookmark:browser resultの内、Google Bookmarkに登録されているページへのアクセス
      • bkmk:
      • bkmkId
      • bkmkLabels:タグ
      • bkmkTitle:ブックマーク名

Flex+Google Mapsで独自地図の表示

本記事では、Flex+Google Mapsでデフォルトの世界地図ではなく、独自の地図を表示する方法について説明します。

地図画像を準備

Google Mapsの地図は一枚の画像ではなく複数のタイル画像から構成されます。

地図のズームレベルを0, 1, 2, 3・・・とすると、対応するタイル画像の枚数は、それぞれ、1, 4, 8, 16・・・です。

したがって、Google Mapsにて独自地図画像を表示させるためには、複数のタイル画像を用意する必要があります。

もし、独自地図画像のズームレベルを0, 1, 2の範囲で表示したいなら、必要な画像の枚数は1+4+8=13枚です。

こんな枚数の画像の用意を手作業で行うのは非常に酷なことです。

Google Maps用の地図画像を生成するツールは多数あるので大丈夫です。

ここでは、GMapImageCutter1.4を使用します。

Google Map Image Cutter

その使用手順は以下の通りです。

  1. GMapImageCutter1.4をダウンロードして解凍
  2. 解凍したフォルダ内の「GMapImageCutter.jar」を実行
  3. 表示したい地図画像を読み込む
  4. ズームレベルの最大値を指定
  5. 「Create」ボタンを押す

すると、地図画像と同一フォルダ内に「tiles」というフォルダが作成され、その中に各ズームレベルに応じた複数のタイル画像が生成されます。

生成されるタイル一枚の大きさは256×256です。

「tiles」フォルダを見ると、jpg形式の画像ファイルがt, tq, tr, ts, tt, tqt, tqr, tqs, tqt・・・という名前で作成されています。

ここで、ファイル名の長さはズームレベルを表します。
例えば、ファイル名をtならズームレベル0のタイル画像であり、ttqrstならズームレベル5のタイル画像の一枚であることが分かります。

また、ファイル名に使用される文字は、地図上での位置を表します。ファイル名に使用される文字は、q, r, s, tであり、左上、右上、右下、左下を意味しています。

qrstの解釈

ズームレベル0-2までのタイルの構成を下に示す。

レベル0 レベル1 レベル2

Flexで独自地図画像を表示

Flexから上で用意したタイル画像から独自地図画像を表示する方法を説明します。

Flex+Google Mapsで独自地図画像を表示するためには、TileLayerBaseクラスの継承クラスを作成し、loadTileメッソドをオーバライドする必要があります。

TileLayerBaseクラスを継承したクラスの名前をMyTileLayerとし、その実装コードを下に示します。

public class MyTileLayer extends TileLayerBase {
	//画像ファイルへのパス
	private var path : String;
	//画像形式
	private var format : String;
	
	public function MyTileLayer(path : Strings, format : String)
	{
		this.path = path;
		this.format = format;
		super(new CopyrightCollection(), 0, 4);
	}
	
	//tilePosはtile座標
	//zoom=1なら、tilePosは(0,0)(1,0)(0,1)(1,1)
	override public function loadTile(tilePos:Point, zoom:Number) : DisplayObject {
		var loader : Loader = new Loader();
		var tileUrl : String = path + "/" + getTileName(tilePos, zoom) + format;
		trace(tilePos, zoom, tileUrl);
		loader.load(new URLRequest(tileUrl));
		return loader;
	}
	
	//タイルの位置とzoomレベルから適切なファイル名を生成する関数
	//例:tilePos=(0, 0) zoom=1のときの返り値はtq
	private function getTileName(tilePos : Point, zoom : Number) : String {
		var tileChar : Object = {0 : {0 : "q", 1 : "t"}, 1 : {0 : "r", 1 : "s"}}
		var imageName : String = "t";
		
		for(var i : int = int(zoom); i >= 1; i--) {
			var den : Number = Math.pow(2, i - 1);
			imageName += tileChar[int(tilePos.x / den) % 2][int(tilePos.y / den) % 2];
		}
		return imageName;
	}
}


MyTileLayerクラスの使用例(mapはMapクラスのインスタンス)

//タイル画像の配置パスはtile、画像形式はjpgとする
var myTileLayer : MyTileLayer = new MyTileLayer("tile", "jpg");
var tileLayers : Array = new Array(myTileLayer);

var mapType : MapType = new MapType(tileLayers, map.MERCATOR_PROJECTION, "original map");
myTileLayer.setMapType(mapType);
map.addMapType(mapType);
map.setMapType(mapType);

FlexからGoogle Mapsの地図上に円を表示

本文では、FlexからGoogle Mapsの地図上に円を表示する方法を記す。

まずは単純に円を表示

Google Mapsの地図上に直線や多角形を表示するには、Google MapsのライブラリのPolylineクラスとPolygonクラスを使用する。

しかし、地図上に円を表示するためのクラスやAPIはない。

そこで円を表示する代わりに、Polygonクラスで24角形、32角形といった多角形を表示する。

円のように見える多角形を表すPolygonクラスを生成する関数のコードを下に示す。

//ある点を中心にした多角形を生成する関数
//latLng:円の中心点
//radius:円の半径(単位は10進法の角度)
//options:多角形の表示方法
//pointNum:多角形の頂点の数
private function createCirclePolygon(latLng : LatLng, radius : Number, options : PolygonOptions, pointNum : int = 24) : Polygon {
	//多角形の各頂点の座標の配列を生成
	var points : Array = [];
	for(var i : int = 0; i <= pointNum; i++) {
		var radian : Number = 2.0 * Math.PI * (Number(i) / Number(pointNum)); 
		var x : Number = Math.cos(radian) * radius;
		var y : Number = Math.sin(radian) * radius;
		var lat : Number = latLng.lat() + y;
		var lng : Number = latLng.lng() + x;
		points.push(new LatLng(lat, lng, true));
	}
	
	var polygon : Polygon = new Polygon(points, options);
	return polygon;
}

この関数で緯度=0.0, 経度=0.0, 半径=10.0,点の数=24とすると、ほぼ円とみなせる多角形が表示できる。


しかし、これには問題がある。下に緯度=75.0, 経度=0.0, 半径=10.0,点の数=24としたときの表示を示す。


これを見ると、縦方向に大きく伸びた円といえない代物が表示されている。

この原因は、デフォルトではGoogle Mapsの地図の投影法として高緯度の地方ほど拡大して表示するメルカトル図法が使用されるためだ。

この原因に対処するためには、地図の投影方法に正距円筒図法を使用する。これは、緯度、経度をそれぞれ地図の縦と横にそのまま読みかえる投影方法だ。

正距円筒図法の実装

デフォルトのメルカトル図法以外の投影方法を使用するためには、Google Mapsのライブラリ中のProjectionBaseクラスを継承し、必要な処理を実装する。

その際には、ProjectionBaseクラスのメッソドの内、fromLatLngToPixel、fromPixelToLatLng、getWrapWidthの3つは必ずオーバライドする必要がある。

正距円筒図法を表すEquirectangularProjectionクラスとして実装例を示す。

public class EquirectangularProjection extends ProjectionBase
{
	public function EquirectangularProjection()
	{
		super();
	}
	
	override public function fromLatLngToPixel(arg0:LatLng, arg1:Number):Point {
		var x : Number = (arg0.lng() + 180.0) * getPixelsPerLng(arg1);
		var y : Number = (90.0 - arg0.lat()) * getPixelsPerLat(arg1);
		return new Point(x, y); 
	}
	
	override public function fromPixelToLatLng(arg0:Point, arg1:Number, arg2:Boolean=false):LatLng {
		var lng : Number = arg0.x / getPixelsPerLng(arg1) - 180.0;
		var lat : Number = 90.0 - arg0.y / getPixelsPerLat(arg1);
		return new LatLng(lat, lng, arg2);
	}
	
	override public function getWrapWidth(arg0:Number):Number {
		return Math.pow(2.0, arg0) * 256;
	}
	
	//1ピクセルに対する経度を取得
	private function getPixelsPerLng(zoom : Number) : Number {
		return 256 * Math.pow(2.0, zoom) / 360;
	}
	
	//1ピクセルに対する緯度を取得
	private function getPixelsPerLat(zoom : Number) : Number {
		return 256 * Math.pow(2.0, zoom) / 180;
	}
}

なお、この実装例では地図のタイル1枚の大きさを256×256とし、ズームレベルが0、1、2、3・・・のとき、地図を構成するタイルの数は1、4、8、16・・・となるものとしている。
そのため、画面上の緯度1の長さは、経度が2の長さと等しくなる。
これに対応させるために、先ほどのcreateCirclePolygon関数内での多角形の頂点のx座標の算出方法の部分を次のように書き直す。

//var x : Number = Math.cos(radian) * radius;
var x : Number = Math.cos(radian) * radius * 2


最後に、作成したEquirectangularProjectionをGoogle Mapsのデフォルトで用意されている通常タイプの世界地図の投影方法として適用する。それには、下のようなコードを記述する(mapはMapクラスのインスタンス)。

//正距円筒図法を通常タイプの世界地図に適用
var normalMapType : IMapType = MapType.NORMAL_MAP_TYPE;
var mapType : MapType = new MapType(normalMapType.getTileLayers(), new EquirectangularProjection(), "tasukete");
map.addMapType(mapType);
map.setMapType(mapType);

先ほどの関数で緯度=75.0, 経度=0.0, 半径=10.0,点の数=24として円を表示させると、正しく円が表示される。

参考リンク

その他

Google Maps上のマーカーのアイコンに円を描画した画像を設定すれば正確な円を簡単に表示できるかも。

djangoのModelをコンソールプログラムで使用する方法

pythonのWebアプリケーションフレームワークdjangoでは、RDBの表をModelというクラスの形で表現できる。

Modelを使用すると、いちいちSQLを書かなくてもRDB上のデータのCRUD処理が簡単に行えるので便利だ。

このModelクラスをバッチ処理などのコンソールプログラムから使用したくても、単純に、目的のModelクラスをインポートするだけでは使用できない。

しかし、少し設定をすれば、コンソールプログラムからModelクラスを使用できることが最近分かった。

それには、コンソールプログラムの先頭に次のような記述を書きくわえればよい。

import sys, os
sys.path.append("djangoプロジェクトの配置場所")
os.environ["DJANGO_SETTINGS_MODULE"] = "プロジェクト名.settings"
import プロジェクト名.モデルの場所 as models

ここで、

  • djangoのプロジェクトのインストールの場所は「/home/user/django/project」
  • settings.pyの場所はデフォルトの「/home/user/django/project/settings.py」
  • Modelクラスの記述ファイルの場所は「/home/user/django/project/app/models.py」

とすると、下のように記述すればよい。

import sys, os
sys.path.append("/home/user/django/")
os.environ["DJANGO_SETTINGS_MODULE"] = "project.settings"
import project.app.models as models


この記述を先頭に書きくわえると、この場合ではmodels.ModelクラスXXXという形で、「home/user/django/project/app/models.py」にて定義しているModelクラスをコンソールプログラムから使用することができる。


参考リンク:
ソースページ:Using Django in command-line scripts
ソースページを紹介していたブログ:Pyro Memo