2013年12月25日水曜日

GAE + SBT + Unfiltered + Scalate


GAEの環境でUnfilteredScalateを使ってみました。

SBTでGAEを開発するときの設定はこちらを参照。
SBTでUnfilteredの設定をするにはこちらを参照。

Unfilteredのintentメソッドの実装

まず、前回の復習から。

GAEでは、サーブレットを使って開発するので、サーブレット対応のUnfilteredであるunfiltered-filterパッケージを使用します。

unfiltered-filterを使った基本的な開発の流れ

  1. unfiltered.filter.Planを継承したフィルターを作る
  2. intentメソッドを実装する
  3. web.xmlにフィルターを登録する

unfiltered.filter.Planは、javax.servlet.Filterを継承しています。javax.servlet.Filterを継承したクラスは、doFilterメソッドを実装しないといけないんですが、それはunfiltered.filter.Planクラスで実装されているので実装する必要はありません。その代わりにintentメソッドを実装しなければいけません。

intentメソッドは、unfiltered.request.HttpRequest[HttpServletRequest]を受け取り、unfiltered.response.ResponseFunction[HttpServletResponse]を返すPartialFunctionを返すメソッドです。PartialFunctionになっているので、リクエストの処理をmatch式でやろうというわけですね。

unfiltered.request.HttpRequestは、HttpServletRequestのラッパーで、unfiltered.response.ResponseFunctionは、HttpServletResponseOutputStreamにレスポンスを書き込むトレイトです。

レスポンスを返すには、unfiltered.response.ResponseFunctionを実装したインスタンスを返さなければいけないんですが、それが面倒な人の為にちゃんと予め実装されたサブクラスが幾つか用意されいます。よく使うであろうと思われるのは、ResponseStringでしょう。これを使ってintentメソッドを実装すると、例えば次のようになります。

class Filter extends unfiltered.filter.Plan {
  def intent = {
    case Path("/index.html") => new ResponseString( "Hoge" )
  }
}

web.xmlへの登録は、前回やったので、スキップしま~す。

GAEでのUnfiltered

今回は、Scalateを使ってレスポンスを返すので、ResponseStringは使えません。なぜなら、Scalateの処理をするためには、HttpServletResponseが持っているOutputStreamインスタンスが必要だからです。少なくとも上記の例では、OutputStreamは登場してません。

そこでScalateを使うために、unfiltered.response.ResponseFunctionのサブトレイトResponseWriterを使います。このトレイトは、writeメソッドを実装する必要があるます。このwriteメソッドは、引数としてHttpServletResponseOutputStreamWriterを取るので、このメソッドの中でOutputStreamを使ってScalateの処理を行うことができます。

intentメソッド内でScalateを使う

Scalateとは

Scalateとは、テンプレートエンジンです。いろいろな書式のテンプレートに対応しています。
  • Jade
  • Mustache
  • Scaml(HamlのScala版)
  • SSP(Scala Server Page, JSPのScala版)
  • Scuery

基本的な使い方

Scalateの基本的な使い方は、とても簡単です。TemplateEngineのインスタンスを生成して、layoutメソッドを呼び出すだけです。layoutは、String型のHTMLを返します。

val engine = new TemplateEngine
val html = engine.layout("index.ssp"Map("name" -> "Hoge")List(Biding("name""String")))

layoutは、テンプレートのソースコードをコンパイルして一時領域にクラスファイルを出力します。キャッシュが効くので、テンプレートのソースコードに変更がなければ、2度目移行の呼び出しは、コンパイルは回避されて、一時領域のクライスファイルを使ってHTMLを出力します。

layoutメソッドの詳細
def layout (uri: String, attributes: Map[String, Any] = Map(), extraBindings: Traversable[Binding] = Nil): String
引数:
  • uri
  • Ssp(Scala Server Page)やScaml(HamlのScala版)のテンプレートのソースコードのパス。
  • attributes
  • URIの場所にあるテンプレートに渡す値。複数渡せます。上記の例では、Stringの"Hoge"という値をnameという変数で渡しています。テンプレート側で使用するには、テンプレート側でnameという変数を宣言する必要があります。次の引数のextraBindingsを渡せば宣言も必要なくなります。
  • extraBindings
  • テンプレート側で暗黙的に使える変数の宣言。上記の例では、nameという変数をテンプレート側で宣言せずに使えるようになります。

ただし、GAEのような環境で使用するには、上記では上手く行きません。サーブレット環境であることとファイルの書き込みができないからです。

プリコンパイル

ファイルの書き込みができない場合は、予めコンパイルしてクラスファイルを作っておくという手があります。

Scalateでは、テンプレートのソースコードを動的にコンパイルしJavaのクラスファイルを生成します。そして、そのクラスファイルをシステムの一時領域に保存します。GAEでは、ファイルの書き込みができないので、一時的にコンパイルしたクラスファイルを保存しておくことはできません。これを避ける為に、予めテンプレートをコンパイルしておく必要があるのです。Scalateでは、プリコンパイルされていればテンプレートのソースコードを再度コンパイルすることはないのでGAEでも大丈夫です。プリコンパイルするには、SBTのプラグインxsbt-scalate-generatorを使います。

SBTの設定
project/plugins.sbtに次のコードを追加します。
addSbtPlugin("com.mojolly.scalate" % "xsbt-scalate-generator" % "0.4.2")

build.sbtに次のコードを追記します。
10 
11 
12 
13 
14 
15 
16 
seq(scalateSettings:_*)

scalateTemplateConfig in Compile <<= (sourceDirectory in Compile){ base =>
  Seq(
    TemplateConfig(
      // テンプレートの場所
      base / "webapp" / "WEB-INF"
      // 暗黙的にテンプレートにインポートします
      List("import scala.math._")
      // 暗黙的にテンプレートに変数を宣言します
      List(Binding("userAgent""String"))
      // 出力クラスのパッケージ
      Some("webTmpl") 
    )
  )
}

プリコンパイル
特別に何もする必要はありません。sbtのcompileコマンドやpackageコマンドでwarファイル作成時に自動的にテンプレートをコンパイルしてくれます。出力先は、targetディレクトリです。

サーブレット対応

サーブレット対応したintentメソッドは次のようになります。
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
    def intent = {
      // ResponseWriterを返すクロージャ
      case req @ Path(Seg( _ :: Nil )) => new ResponseWriter {
        def write( writer: OutputStreamWriter ) = {
          val engine = new TemplateEngine
          // サーブレットの環境を設定する
          engine.resourceLoader = new ServletResourceLoader(config.getServletContext)
          // build.sbtで指定したディレクトリ
          engine.workingDirectory = new File("WEB-INF")
          // build.sbtで指定したパッケージ
          engine.packagePrefix = "webTmpl"
          // layoutテンプレートの場所の設定
          engine.layoutStrategy = new DefaultLayoutStrategy(engine, TemplateEngine.templateTypes.map("WEB-INF/layouts/default." + _):_*)
          // プリコンパイルされている場合は、URIと同じ名前のクラスファイルを読み込む。
          // そうでない場合はテンプレートをコンパイルする。
          val scalateTemplate = engine.load(req.underlying.getServletPath)
          // 描画環境を設定
          val context = new DefaultRenderContext(req.underlying.getServletPath, engine, new PrintWriter(writer))
          // テンプレートを描画
          engine.layout( scalateTemplate, context )
        }
      }
    }

ちなみに、ServletTemplateEngineServletRenderContextとうクラスもあるので、こっちを使ってもできるかもしれませんが、試してません。

テンプレートファイルの作成

では、簡単なテンプレートを作成してみましょう。今回はScamlを使います。

レイアウトテンプレート
src/main/webapp/WEB-INF/layouts/default.scaml
-@ var body: String
%html
  %head
    %title Title
    %meta(http-equiv="Content-Type" content="text/html; charset=UTF-8")
  %body
    #{unescape(body)}

レイアウトテンプレートは、全てのテンプレートに自動的に適応されます。テンプレートの内容が#{unescape(body)}の部分に置換されます。

ページのテンプレート
src/main/webapp/index.scaml
%div Hello World

この場合は、%div Hello Worldがレイアウトテンプレートの#{unescape(body)}の部分に置換されます。

コンパイル

> sbt package

サーバを起動してブラウザで確認

開発用サーバを起動:
> appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp

ブラウザでhttp://localhost:8080/index.scamlに行って、動作を確認してみましょう。ブラウザにHello Worldと表示されていれば成功です。

関連記事

GAE + SBT + Unfiltered


GAEをSBTで使う記事を書きましたが、今回はリクエストの処理にUnfilteredを使ってみることにしました。

Unfilteredとは

Unfilteredとは、いろんなフレームワークや環境に使えるリクエスト処理するライブラリらしい。Servletにも対応しているので、GAEでも使えるようです。

Servletに対応しているパッケージは、unfiltered-filterのようなので、これを使ってみましょう。

build.sbtの設定

build.sbtに次の記述を追加すれば、Unfilteredを使えるようになります。

libraryDependencies += "net.databinder" %% "unfiltered-filter" % "0.7.1"

フィルターの作成

リクエストを処理するフィルターを作成してweb.xmlに登録します。

Unfilteredでは、Planというトレイトを継承してフィルターを作るようです。

src/main/scala/mypackage/Filter.scalaを作成します。

10 
11 
12 
package mypackage {

  import unfiltered.request._
  import unfiltered.response._

  class Filter extends unfiltered.filter.Plan {
    def intent = {
      case Path(Seg( x :: Nil )) => ResponseString( x )
    }
  }

}

intentというメソッドでリクエストを処理します。caseでパス名を取得して、パス名をそのままページに表示するようにしました。

詳しい使い方は、こちらを参照してください。

web.xmlの設定

作ったフィルターをweb.xmlに登録します。

10 
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <filter>
    <filter-name>filter</filter-name>
    <filter-class>mypackage.Filter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>filter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

とりあえず、全てのリクエストはこのフィルターを通すように設定しました。

コンパイル

SBTのインタープリタに入り、packageと入力します。

> sbt
sbt> package

サーバの起動

appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp
ブラウザでhttp://localhost:8080/hogeに行って、動作を確認してみましょう。ブラウザにhogeと表示されていれば成功です。

関連記事

2013年12月20日金曜日

Google App Engine + SBTで始めました

Google App EngineをSBTで使ってみました!

GAE用のSBTのプラグインsbt-appengineがあるようなので、これを使ってもいいのですが、GAE初心者の僕としては、GAEを理解する為に、sbt-appengineを使わずにやってみました。


Google App Engine SDK for Javaのダウンロード

ここからGoogle App Engine SDK for Javaをダウンロードします。

Google App Engine SDK for Javaの解凍

ダウンロードしたGoogle App Engine SDK for Javaを好きな場所に解凍します。

SBTのダウンロード

ここからZIPまたはTGZ版をダウンロードします。

SBTの解凍

ダウンロードしたSBTを好きな場所に解凍します。

xsbt-web-plugin

GAEはWARファイルを使ってデプロイするようなので、sbt-appengineは使いませんが、WARファイルを作る為のプラグインxsbt-web-pluginは使います。

plugins.sbtの設定

解凍したSBTディレクトリの中にsbt/project/plugins.sbtを作成します。

addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "0.5.0")

build.sbtの作成

解凍したSBTディレクトリの中にsbt/build.sbtを作成します。

10 
11 
12 
13 
name := "****"

organization := "****"

version := "0.1.0"

scalaVersion := "2.10.3"

seq(webSettings :_*)

libraryDependencies += "org.eclipse.jetty" % "jetty-webapp" % "9.1.0.v20131115" % "container"

libraryDependencies += "org.eclipse.jetty" % "jetty-plus" % "9.1.0.v20131115" % "container"

WARファイルに組み込むライブラリ

xsbt-web-pluginで作るWARファイルに入れるライブラリをsbt/libに置きます。必要なライブラリは、appengine-java-sdk-***/lib/userの中のjar全てです。サブディレクトリの中のjarもです。だだし、sbt/libにコピーするときに、全てのjarファイルをsbt/lib直下に置く必要があります。ディレクトリ構造をそのままコピーしなように注意が必要です。

外部ライブラリ参照

WARファイルに組み込まないライブラリも使用するので、それらのライブラリを参照するようにクラスパスの設定をする必要があります。

必要な外部ライブラリは、appengine-java-sdk-***/lib/sharedの中のライブラリです。

sbt/build.sbtに次の記述を追加します。

unmanagedJars in Compile ++= {
  val base = baseDirectory.value
  val gae = base / ".." / "appengine-java-sdk-1.8.8" / "lib" / "shared"
  val jars = (gae ** "*.jar") +++ (gae / "jsp" / "*.jar")
  jars.classpath
}

web.xmlの作成

sbt/src/main/webapp/WEB-INFweb.xmlを作成します。とりあえず、空のやつを作ります。

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
</web-app>

appengine-web.xmlの作成

sbt/src/main/webapp/WEB-INFappengine-web.xmlを作成します。とりあえず、最小限の項目を記述します。

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
  <application>application-id</application>
  <version>1</version>
  <threadsafe>true</threadsafe>
</appengine-web-app>

<application>タグに、アプリケーションIDを指定するのを忘れずに。アプリケーションIDは、ここから取得できます。

<threadsafe>タグは、アプリケーションが同時に複数のリクエストを受け付けられるようになっているかを指定する項目です。1つづしかリクエスト受け付けられなければfalse、同時に複数のリクエストを処理できる場合にはtrueを指定します。

静的ファイルでHello World

sbt/src/main/webappindex.htmlを作成ます。とりあえず、Hello Worldです。

<html>
  <head>
    <title>Title</title>
  </head>
  <body>
    Hello World
  </body>
</html>

WARファイルの作成

SBTのインタープリタに入り、packageと入力します。

> sbt
sbt> package

すると、sbt/targetにWARファイル化する前のwebappディレクトリが現れます。GAEの開発用ウェブサーバを起動するときには、このディレクトリを指定するといいでしょう。さらに、sbt/target/scala-***には、(project name)_(scala version)-(project version).warが作成されています。

開発用ウェブサーバの起動

開発用に使用するウェブサーバもGAE SDKについてきます。appengine-java-sdk-***/bin/dev_appserver.cmd(Windows)です。

起動
appengine-java-sdk-***/bin/dev_appserver.cmd sbt/target/webapp

起動したら、ブラウザでhttp://localhost:8080にアクセスしてみましょう。Hello Worldと表示されていれば成功です。

アプリケーションのアップロード

次のコマンドでアップロードができます。

appengine-java-sdk-***/bin/appcfg.cmd update sbt/target/webapp

途中でemailとpasswordを聞かれるので、入力します。しばらくすると完了します。

管理画面に行って、デプロイされている確認しましょう。

関連記事

2013年12月12日木曜日

[数学]直線が線分を内分する点

いきなりですが、下の図のように直線APが線分BCを内分する点Dを求めたいと思います。つまり、BD対DCの比を求めます。


平行四辺形の面積を比較することによって比を求めます。
次の4枚の図で平行四辺形を作っています。








次の図を見ると、緑の部分の面積と青の部分の面積の比が求めるBD対DCの比になっていることが分かります。


緑の部分の面積は黄色の部分の面積に等しいので、結果的には黄色の面積対青の面積の比を求めればよいことになります。


下の図のように、黄色の部分の面積は、ベクトルABとベクトルAPの外積の絶対値で求められます。紫の部分の面積は、ベクトルBP'とベクトルAPが等しいので、ベクトルBCとベクトルAPの外積の絶対値になります。


よって、BD対DCは、
BD/BC = |AB x AP| / |BC x AP|
となります。

2013年12月7日土曜日

[Java][Scala] 浮動小数点の有効桁数と同値性

浮動小数点で掛け算や割り算を行うと微妙に正確な値からずれていって困っちゃいますよね。例えば、いろいろ計算した結果、机上ではぴったり1になるはずなのに、コンピュータ上では、0.9999999になってたりとか。こんなとき困るのが同値性を検証するときです。こっちは1だと思ってるので、とうぜん、
if( x == 1.)
とかで比較したくなりますよね。でも実はxが0.999999だったりしてfalseになっちゃったりします。でも、これほぼ1やん!って。trueにしよて!ってなりますよね。こんなときに、上位何桁まで一致してたら同じって判断してくるようなものないでしょうか。

どうやらあるようです。

そりゃそうですよね。こんなシチュエーションってよくあると思いますから、解決方法もちゃんと用意されているはずですよね。
参考になったのが、このサイトです。

このサイトで言ってるのは、java.lang.Mathクラスのulpメソッドを使えってことです。簡単に言うと、このメソッドは、浮動小数点の分解能を返してくれます。精度って言ってもいいかもしれません。いくつかの例で試してみましょう。
scala> java.lang.Math.ulp(1.0f)
res2: Float = 1.1920929E-7
1.0fの場合は、10-7くらいの精度をもってるようです。
次は大きい数字で試してみましょう。
scala> java.lang.Math.ulp(87654321.0f)
res3: Float = 8.0
107くらいの大きな数の場合は、100くらいの精度のようです。
次はものすごく小さい数字です。
scala> java.lang.Math.ulp(0.00000001234567f)
res6: Float = 8.881784E-16
10-8くらいの小さな数の場合は、10-16くらいの精度のようです。
こうやってみると、Float型は、総じて、だいたい上位7,8桁くらいまで表現できる能力を持っているようですね。このメッソドを使えば、上位から好きな桁数で簡単に比較できそうですね。

もっとも細かく比較する場合は、
if( Math.abs( x - y ) < Math.ulp( x ) )
とすればよさげですね。

もっと精度を低くしたければ、
if( Math.abs( x - y ) < Math.ulp( x )*10 )
のようにulpを10倍、100倍としていけば、どんどん粗い桁で同値性を検証できます。

[Scala] 直行座標と極座標の相互変換の罠

直行座標上の点を回転させたいときに、極座標を使ってハマってしまいました。どんなとこにハマっちゃったかというと、θ方向に少しづつ加算していって点を回転させていった時のことです。θが180°に近づいたあたりで、そこからθの値がほとんど変わらなくなっちゃったんです。

なぜ!?

ここで少し情報を整理しましょう。

まず、これは3次元空間の座標系でのお話です。下の図のような感じです。原点からの距離をr、y軸とのなす角をθ、x軸とのなす角をφと定義しています。


もろもろの都合上、基本的には、座標系は直行座標系で表現することにしていました。Scalaでは、タプルで。
type Point = (Double, Double, Double)

なので、ある点を1回だけ回転させるためには、
直行座標 ⇒ 極座標 ⇒ 回転操作 ⇒ 直行座標
という操作を行っていて、複数回の回転はこの操作の連続適用していました。

次の関数は、直行座標系上の点をθ方向にd°だけ回転して移動して直行座標上の点を返す関数です。
    val addTheta = ( p: Point, d: Double ) => {
      val p1 = toPolar( p )
      fromPolar( (p1._1, p1._2 + d.toRadians, p1._3 ) )
    }
この関数をθが180°付近で適用すると、180°付近を行き来するようになるんです。

ちなみに、addTheta関数で使われているtoPolarfromPolarは、それぞれの座標系へ変換する関数で次のように定義しました。

直行座標から極座標への変換
    val toPolar: Point => Point = { case (x, y, z) => 
      val r = sqrt( x*+ y*+ z*)
      val theta = acos( y/)
      val phi = if( z > 0 ) atan2( z, x ) else Pi*+ atan2( z, x )
      (r, theta, phi)
    }

極座標系から直行座標への変換
    val fromPolar: Point => Point = { case (r, theta, phi) =>
        ( r*sin( theta )*cos( phi ), r*cos( theta ), r*sin( theta )*sin( phi ) )
    }

さて、上記のaddTheta関数を使って、点 (1.0, -10.0, 1.0)(極座標表示では、(10.1, 172.0, 45))をθ方向に20°づつ4回だけ回転してみます。
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
    // 度表示に変換する。
    val toDegrees: Point => Point = { case (r, theta, phi) =>
        ( r, theta.toDegrees, phi.toDegrees )
    }

    val p0 = (1.0, -10.0, 1.0)
    println( toDegrees( toPolar( p0 ) ) )

    // 1度目の回転
    val p1 = addTheta( p0, 20 )
    println( toDegrees( toPolar( p1 ) ) )

    // 2度目の回転
    val p2 = addTheta( p1, 20 )
    println( toDegrees( toPolar( p2 ) ) )

    // 3度目の回転
    val p3 = addTheta( p2, 20 )
    println( toDegrees( toPolar( p3 ) ) )

    // 4度目の回転
    val p4 = addTheta( p3, 20 )
    println( toDegrees( toPolar( p4 ) ) )
20°の回転を4回適用したので、172.0°から80°回転されて、θは252.0°(つまり、0 < θ < 180なので108°)になっているはず! と思っていたんですが、結果は、
(10.099504938362077,171.95053302447164,45.0)
(10.099504938362077,168.04946755734255,225.0)
(10.099504938362077,171.95053302447164,44.99999999999999)
(10.099504938362077,168.04946755734255,225.0)
(10.099504938362077,171.95053302447164,44.99999999999999)
となりました。うーむ、171°と168°を行き来してますね。なんでこんなことが起きたんだー!

addTheta関数をもう1度見てみましょう。
さっきも言ったように、「直行座標 ⇒ 極座標 ⇒ 回転操作 ⇒ 直行座標」って操作をしているんですね。具体的にどのように値が変化しているのかを見るために、前述の点 (1.0, -10.0, 1.0)の値の変化を追ってみます。まず、点 (1.0, -10.0, 1.0)を極座標に変換するとだいたい(10.1, 172.0, 45)になります。これをθ方向に20°回転させると、172+20=192になり、この点は極座標上で(10.1, 192.0, 45)という位置に移動しました。そして、この点を直行座標に戻すと、(-1.478,-9.880,-1.478)になりました。ここまでは、問題ないでしょう。予想通り。では、この点(-1.478,-9.880,-1.478)を極座標表示で確認してみましょう。toPolar関数を適用しますね。きっと(10.1, 192.0, 45)に変換されることでしょう。ところが期待に反して、(10.099,168.049,225.0)という計算結果になっちゃってます。toPolarにバグがあるのでしょうか? でも、ちょっと待ってください。もうちょっと結果をよく見てみましょう。rが10.1なのは予想通り。θφは、予想と違いますが、θは、192も168も180°を挟んでともに12°だけ、φは、45も225もちょうど180°だけずれているではありませんか!つまり、これはどういうことかというと、(10.1, 192.0, 45)(10.099,168.049,225.0)も数字こそ違いますが示している場所は同じだったんですね。紛らわしいよ~。じゃあ、どうしてこんな紛らわしいことになっちゃったの??という疑問に答えるために、toPolar関数の定義を見てみましょう。その中で、直行座標からθの値を得るときに、acosを使ってますね。acosは、0°~180°までしか返しません。この定義のためにθは180°より大きい値は扱えないんです。その代わり、φが0~360°まで取れるので、θが180°を超えた分は、φを180°回転させて表現していたんですね。ということで、ここまでの計算過程は、問題なさそうですね。次のステップに進みましょう。さらにこの点(-1.478,-9.880,-1.478)θ方向に20°回転させましょう。先ほどと同じようにaddTheta関数をこの点に適用します。同じことの繰り返しですね。さっさと極座標に変換してθに20°だけ加算しちゃいましょう。この点(-1.478,-9.880,-1.478)の極座標のθは、168°だから、これに20°足して178°にして・・・ん?なんかおかしいぞ。ちょっと整理しよう。最初に20°回転させて、さらに20°回転させたんだから、計40°足されて212°になり、θは180°までで表現しなければいけないので、142°になってないとおかしいはずですけど~!なるほど。いくら回転させても180°付近を往来する原因は、ここだったのか。θは180°以内で表さなければいけないというtoPolarの定義によるものだったのですね。そして、いちいち極座標に変換して、また直行座標に変換するというaddThetaがいけなかったんですね。172°+20°=168° ⇒ 168°+20°=172° ⇒ 172°+20°=168°・・・となっちゃうんですか。。。ということで、θを少しづつ増加させて360°回転するのは、このやり方では、不可能!

では、どうすればいいのか?

いちいち直行座標に変換なんてさせずに、ずっと極座標の値を保持していくのが一番簡単なんでしょうね。直行座標が必要になったときだけ、極座標から変換して使うようにすればいいんでしょう。
または、一貫して直行座標にこだわるのであれば、回転行列を使って直行座標を変換するのもいいのかもしれません。