petitviolet_blog

@petitviolet blog

geoalchemyで位置情報をpythonから扱う

sqlalchemyでMySQLを使うで説明したsqlalchemyでは、ここで説明したMySQLの位置情報を利用できない。
そこで、geoalchemyを使う。

インストール

sudo pip install geoalchemy

以上。

マッピング

sqlalchemyではMySQLテーブルとマッピングするためのクラスを定義する。
その際に、geometry型のカラムをsqlalchemyでは扱えないため、geoalchemy.GeometryColumnを利用する
import文はsqlalchemyでMySQLを使うと同じ。

from sqlalchemy import Column, Integer, String, Text, ForeignKey, DECIMAL, DATETIME, create_engine
from sqlalchemy.orm import session maker, relationship
from sqlalchemy.ext.declarative import declarative_base
from geoalchemy import GeometryColumn, Point
from geoalchemy.mysql import MySQLComparator

class Place(Base):
  __tablename__ = "place"
  __table_args__ = {"mysql_engine": "MyISAM"}
  	id = Column("id", Integer, primary_key=True, autoincrement=True)
	name = Column("name", String(255))
	latlng = GeometryColumn("latlng", Point(dimension=2, srid=4326),\
	                                          nullable=False, comparator=MySQLComparator)
	def __init__(self, name, latlng):
		self.name = name
		self.latlng = latlng
	

これでマッピング用のクラスPlaceが定義出来た。

latlng = GeometryColumn("latlng", Point(dimension=2, srid=4326),\
                                         nullable=False, comparator=MySQLComparator)

ではカラム自体をGeometryColumnで作成し、カラムのタイプを2次元のPointで(sridはよく分からないのでデフォルト値)、
MySQLを利用するのでcomparatorにMySQLComparatorをセットしています。

DBから位置情報の取得し、扱う

# これが使える
import geoalchemy.functions as gf

# echo=Trueでログを吐く
engine = create_engin("mysql://[user]:[passwd]@[host]/[dbname]",\
				encoding="utf-8", echo=False)
Session = sessionmaker(bind=engine)
session = Session()

# 矩形範囲にあるPlaceを取得する
margin = 0.01
lng, lat = 35.0, 135.0
left, right = lng - margin, lng + margin
bottom, top = lat - margin, lat + margin
# boxは(left, bottom), (right, bottom), (right, top), (left, top)の4点を結ぶ正方形となる
box = "POLYGON(({left} {bottom}, {right} {bottom}, {right} {top}, {left} {top}, {left} {bottom})\
			.format(left=left, right=right, bottom=bottom, top=top)"

s = session.query(Place.id, Place.name, Place.latlng, \
	gf.wkt(Place.latlng).label("point"), gf.x(Place.latlng).label("lat"), gf.y(Place.latlng).label("lng"))\
	.filter(Place.latlng.within(box)).all()

# dict化
return [place._asdict() for place in s]

こんな感じ
geometry.functionsでいろいろ出来そう。
ちなみに、sqlalchemyでは

from sqlalchemy import func

で、func.round(四捨五入)や.group_byしてfunc.avg(平均)が可能となる。

ジオコーディング

緯度経度を扱うために、googleが提供するジオコーディングのapiを叩いて結果を取得する

from urllib import url open
import json

url = "http://maps.googleapis.com/maps/api/geocode/json?address={landmark}&sensor=true"
landmark = "京都駅"
geo_json = json.load(urlopen(url.format(landmark=landmark)))
latlng = geo_json["results"][0]["geometry"]["location"]
result = {"lat": latlng["lat"], "lng": latlng["lng"]}

urlopen使っててエラー処理もしてないので、使うときはきちんと書きましょう...