Google Maps SDK for iOSがキタ——(゚∀゚)——!!

Google Maps API Teamから「使えるようになったぜ」ってメールが着たのでお試し。
前はAPI keyが生成できなかったんだが、はてさて。

Getting Started

ココから順序通りにやってみる。
まずはSDKをダウンロード。なにげにバージョンが1.1.0に上がってるw

API keyを作成

  1.  ココからAPI Projectを作成
  2. ServicesのペインからGoogle Maps SDK for iOSをONにする
  3. API AccessのペインからCreate new iOS key…をクリックする
  4. ダイアログのテキストエリアにSDKを使用するアプリのbundle identifiersを登録する
  5. CreateをクリックするとAPI keyが作成される
  6. API AccessのペインのKey for iOS apps (with bundle identifiers)の項目にあるAPI keyがアプリで使用するキーになる

4のbundle identifiersの登録は制限をかけるためのものなので、API keyを使い回す場合は複数登録するのかな。
何も登録しなかったら全てのアプリからAPI keyが使い回せるみたい。

サンプルプロジェクトで確認

ダウンロードしたSDKにサンプルプロジェクトが同梱されているのでAPI keyがちゃんと使えるかどうかだけならこのプロジェクトで試せばいいかな。
SDKDemos/APIKey.hに

const static NSString *APIKey = @"";

があるので、API keyをコピペ。
実行するとサンプルのリストが出てくるので適当に。
iOSシミュレータのスクリーンショット 2013.02.22 1.48.58

MapSampleViewController

iOSシミュレータのスクリーンショット 2013.02.22 1.49.12

ShowcaseSample

iOSシミュレータのスクリーンショット 2013.02.22 1.54.00

CameraSample

iOSシミュレータのスクリーンショット 2013.02.22 2.00.12

サンプルの一覧

  • MapSampleViewController
  • UISettingsSample
  • Map Types
  • ResizeMapSample
  • CustomMarkerSample
  • GroundOverlaySample
  • ProjectionSample
  • TrafficSample
  • ShowcaseSample
  • CameraSample
  • PolylineSample
  • MarkerSample

自前のプロジェクトを起こしてみる

API keyが問題なく作成できたので自前のプロジェクトで試してみる。

  1. Single View Applicationでプロジェクトを作成
  2. ダウンロードしたGoogleMaps.frameworkをフォルダごとプロジェクトにコピー
  3. コピーしたGoogleMaps.frameworkをShow in Finderで開いて、ResourcesフォルダにあるGoogleMaps.bundleをプロジェクトにコピー
  4. プロジェクトのTargetsのBuild PhasesのLink Binary With Librariesに以下のライブラリを追加
    • AVFoundation.framework
    • CoreData.framework
    • CoreLocation.framework
    • CoreText.framework
    • GLKit.framework
    • ImageIO.framework
    • libicucore.dylib
    • libstdc++.dylib
    • libz.dylib
    • OpenGLES.framework
    • QuartzCore.framework
    • SystemConfiguration.framework
  5. Build SettingsのArchitecturesをarmv7に変更
  6. Other Linker Flagsに-ObjCを追加
  7. AppDelegateあたりで初期化
    #import <GoogleMaps/GoogleMaps.h>
    

    application:didFinishLaunchingWithOptions:メソッドで

    [GMSServices provideAPIKey:@"YOUR_API_KEY"];
    

ViewControllerにマップの追加

#import "YourViewController.h"
#import <GoogleMaps/GoogleMaps.h>

@implementation YourViewController {
  GMSMapView *mapView_;
}

// You don't need to modify the default initWithNibName:bundle: method.

- (void)loadView {
  GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:-33.8683
                                                          longitude:151.2086
                                                               zoom:6];
  mapView_ = [GMSMapView mapWithFrame:CGRectZero camera:camera];
  mapView_.myLocationEnabled = YES;
  self.view = mapView_;

  GMSMarkerOptions *options = [[GMSMarkerOptions alloc] init];
  options.position = CLLocationCoordinate2DMake(-33.8683, 151.2086);
  options.title = @"Sydney";
  options.snippet = @"Australia";
  [mapView_ addMarkerWithOptions:options];
}

サンプルコードそのまんまだけど、適当に自分のViewControllerに突っ込んだらマップが表示された。
iOSシミュレータのスクリーンショット 2013.02.22 4.00.43

GPUImage for Androidを試してみた

GPUImageでiOSのカメラアプリを作るエントリーを読んでたら、GPUImage for Androidがあるとのことなのでサンプルを試してみた。

GPUImageで高速フィルター!iOSカメラアプリの作り方(まとめ・サンプルコードあり)
GPUImage for Android

サンプルコード

githubからcloneしたプロジェクトはmavenのプロジェクトになっているのだが、eclipseにインポートする際はAndroidのプロジェクトとしてインポートした。
どうもandroid-maven-pluginが上手く動いていないのか、SDKやNDKのパスが無いとか、maven installはできてもエラーのマークがプロジェクトから消えないとか、よく分からないのでeclipse上でのmavenは諦めた。
ここを参考に結構頑張ってみたけど。。。
EclipseでAndroidプロジェクトをMavenで管理するための環境構築 + Emacsキーバインドの設定 (2012年版)

Androidのプロジェクトとしてインポートしたら、コマンドラインでlibraryを

mvn clean install

して、サンプルプロジェクトをeclipseから実行すればよい。
ビルド時にminSDKのAPI levelが低いと怒られるので、AndroidManifest.xmlのAPI levelを14に修正した。

手元に開発機がなかったのでエミュレータを使ったら、実行時にsoがloadできないと言われてしまってちょっとハマった。
高速化用のIntel版のイメージを使ってたせいで、armビルドしたsoはloadできないのだった。
当たり前っちゃ当たり前なんだけど、普段エミュレータなんてほとんど使わないせいですっかり頭から抜け落ちていた。
arm版のイメージのエミュレータだとOpen GL 2.0がサポートされてないようなので、libraryをx86用にビルドすることにした。

ndk-build APP_ABI=x86

これでx86用にビルドしなおして実行したところエミュレータでもリアルタイムにフィルターが掛かるサンプルが動いた。
device-2013-02-03-005409
端末の向きを90度回転すると何故か上下逆になってしまうので縦のままw
フィルターは「Grouped Filters」を選択している。

フィルターを作ってみたいからフィルターのソース読んでOpenGLを勉強してみようかな。

IPC(プロセス間通信)におけるException

Androidのサービスを別プロセスからバインドしたい時に使うIPC絡みのメモ。
android.os.BinderのexecTransact(int, int, int, int)の実装。

     // Entry point from android_util_Binder.cpp's onTransact
    private boolean execTransact(int code, int dataObj, int replyObj,
            int flags) {
        Parcel data = Parcel.obtain(dataObj);
        Parcel reply = Parcel.obtain(replyObj);
        // theoretically, we should call transact, which will call onTransact,
        // but all that does is rewind it, and we just got these from an IPC,
        // so we'll just call it directly.
        boolean res;
        try {
            res = onTransact(code, data, reply, flags);
        } catch (RemoteException e) {
            reply.writeException(e);
            res = true;
        } catch (RuntimeException e) {
            reply.writeException(e);
            res = true;
        }
        reply.recycle();
        data.recycle();
        return res;
    }

onTransact()でRemoteExceptionかRuntimeExceptionが発生したら、android.os.ParcelのwriteException(Exception)が呼ばれる。

    /**
     * Special function for writing an exception result at the header of
     * a parcel, to be used when returning an exception from a transaction.
     * Note that this currently only supports a few exception types; any other
     * exception will be re-thrown by this function as a RuntimeException
     * (to be caught by the system's last-resort exception handling when
     * dispatching a transaction).
     * 
     * <p>The supported exception types are:
     * <ul>
     * <li>{@link BadParcelableException}
     * <li>{@link IllegalArgumentException}
     * <li>{@link IllegalStateException}
     * <li>{@link NullPointerException}
     * <li>{@link SecurityException}
     * </ul>
     * 
     * @param e The Exception to be written.
     *
     * @see #writeNoException
     * @see #readException
     */
    public final void writeException(Exception e) {
        int code = 0;
        if (e instanceof SecurityException) {
            code = EX_SECURITY;
        } else if (e instanceof BadParcelableException) {
            code = EX_BAD_PARCELABLE;
        } else if (e instanceof IllegalArgumentException) {
            code = EX_ILLEGAL_ARGUMENT;
        } else if (e instanceof NullPointerException) {
            code = EX_NULL_POINTER;
        } else if (e instanceof IllegalStateException) {
            code = EX_ILLEGAL_STATE;
        }
        writeInt(code);
        StrictMode.clearGatheredViolations();
        if (code == 0) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }
        writeString(e.getMessage());
    }

SecurityException, BadParcelableException, IllegalArgumentException, NullPointerException, IllegalStateExceptionの5つのRuntimeExceptionのサブクラスはそのままwriteString()されるが、それ以外のRuntimeExceptionのサブクラス、若しくはExceptionのサブクラスはRuntimeExceptionとしてthrowされる、と。
つまり、上記5つのRuntimeExceptionのサブクラスはIPCの呼び出し元でcatchできる。
API Level 15からはRemoteExceptionのコンストラクターにStringが渡せるようになってるが、API Level 15未満の環境ではこの5つのどれかで代替するというのもアリなのか?
まぁ、名前からしてIllegalArgumentExceptionかIllegalStateExceptionぐらいしか使えそうにないが。

AChartEngineでチャート(グラフ)の非同期更新

Androidでチャートの非同期更新をやったのでメモ。

Androidでチャートを扱う時に便利なライブラリにAChartEngineというモノがある。
チャートのデモもダウンロードできるので、ソースを読めば理解が早い。

サポートしているチャートは色々あるが、今回はラインチャート(折れ線グラフ)を使った。
デモの「AverageTemperatureChart.java」のソースを参考に、チャート表示に必要なコードを流用し簡単なラインチャートを書いた。

package com.nalabjp.example.achart;

import java.util.ArrayList;
import java.util.List;

import org.achartengine.ChartFactory;
import org.achartengine.GraphicalView;
import org.achartengine.chart.PointStyle;
import org.achartengine.model.XYMultipleSeriesDataset;
import org.achartengine.model.XYSeries;
import org.achartengine.renderer.XYMultipleSeriesRenderer;
import org.achartengine.renderer.XYSeriesRenderer;

import android.app.Activity;
import android.graphics.Color;
import android.graphics.Paint.Align;
import android.os.Bundle;
import android.os.Handler;

public class AChartExampleActivity extends Activity {
	private XYMultipleSeriesDataset dataset;
	private GraphicalView graphicalView;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		String[] titles = new String[] { "Blue", "Green" };
		List<double[]> x = new ArrayList<double[]>();
		for (int i = 0; i < titles.length; i++) {
			x.add(new double[] { 1, 2, 3, 4, 5 });
		}
		List<double[]> values = new ArrayList<double[]>();
		values.add(new double[] { 1, 2, 3, 4, 5 });
		values.add(new double[] { 18, 17, 16, 15, 14 });
		int[] colors = new int[] { Color.BLUE, Color.GREEN };
		PointStyle[] styles = new PointStyle[] { PointStyle.CIRCLE,	PointStyle.DIAMOND };
		XYMultipleSeriesRenderer renderer = buildRenderer(colors, styles);
		int length = renderer.getSeriesRendererCount();
		for (int i = 0; i < length; i++) {
			((XYSeriesRenderer) renderer.getSeriesRendererAt(i))
					.setFillPoints(true);
		}
		setChartSettings(renderer, "Average temperature", "Horizontal axis",
				"Vertical axis", 0.5, 12.5, -10, 40, Color.LTGRAY, Color.LTGRAY);
		renderer.setXLabels(12);
		renderer.setYLabels(10);
		renderer.setShowGrid(true);
		renderer.setXLabelsAlign(Align.RIGHT);
		renderer.setYLabelsAlign(Align.RIGHT);
		renderer.setZoomButtonsVisible(true);
		renderer.setPanLimits(new double[] { -10, 20, -10, 40 });
		renderer.setZoomLimits(new double[] { -10, 20, -10, 40 });

		dataset = buildDataset(titles, x, values);
		
		graphicalView = ChartFactory.getLineChartView(
				getApplicationContext(), dataset, renderer);

		setContentView(graphicalView);
	}

	private XYMultipleSeriesDataset buildDataset(String[] titles,
			List<double[]> xValues, List<double[]> yValues) {
		XYMultipleSeriesDataset dataset = new XYMultipleSeriesDataset();
		addXYSeries(dataset, titles, xValues, yValues, 0);
		return dataset;
	}

	private void addXYSeries(XYMultipleSeriesDataset dataset, String[] titles,
			List<double[]> xValues, List<double[]> yValues, int scale) {
		int length = titles.length;
		for (int i = 0; i < length; i++) {
			XYSeries series = new XYSeries(titles[i], scale);
			double[] xV = xValues.get(i);
			double[] yV = yValues.get(i);
			int seriesLength = xV.length;
			for (int k = 0; k < seriesLength; k++) {
				series.add(xV[k], yV[k]);
			}
			dataset.addSeries(series);
		}
	}

	private XYMultipleSeriesRenderer buildRenderer(int[] colors,
			PointStyle[] styles) {
		XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
		setRenderer(renderer, colors, styles);
		return renderer;
	}

	private void setRenderer(XYMultipleSeriesRenderer renderer, int[] colors,
			PointStyle[] styles) {
		renderer.setAxisTitleTextSize(16);
		renderer.setChartTitleTextSize(20);
		renderer.setLabelsTextSize(15);
		renderer.setLegendTextSize(15);
		renderer.setPointSize(5f);
		renderer.setMargins(new int[] { 20, 30, 15, 20 });
		int length = colors.length;
		for (int i = 0; i < length; i++) {
			XYSeriesRenderer r = new XYSeriesRenderer();
			r.setColor(colors[i]);
			r.setPointStyle(styles[i]);
			renderer.addSeriesRenderer(r);
		}
	}

	private void setChartSettings(XYMultipleSeriesRenderer renderer,
			String title, String xTitle, String yTitle, double xMin,
			double xMax, double yMin, double yMax, int axesColor,
			int labelsColor) {
		renderer.setChartTitle(title);
		renderer.setXTitle(xTitle);
		renderer.setYTitle(yTitle);
		renderer.setXAxisMin(xMin);
		renderer.setXAxisMax(xMax);
		renderer.setYAxisMin(yMin);
		renderer.setYAxisMax(yMax);
		renderer.setAxesColor(axesColor);
		renderer.setLabelsColor(labelsColor);
	}
}

ほとんどデモソースそのまま。 チャートを見やすくするためにプロットするデータは簡略化した。 また、リアルタイム更新処理を実装するので、ラインチャートの表示はIntentをstartActivityするのではなく、ラインチャートのGraphicalViewを生成してインスタンス変数として保持しておき、Activityに貼り付けた。 この状態だと静的なラインチャートが表示されるだけで、更新処理はまだ実装されていない。 更新処理の実装はラインチャートのデータセットに追加表示したいデータをセットし、チャートを再描画すれば良い。 実に簡単。 当たり前だが、更新処理は非同期で行いたい場面がほとんどのはずなのでHandlerを使用する。 インスタンス変数に以下のコードを追加。

	private double addX = 6;
	private double plus = 6;
	private double minus = 13;
	private Handler handler = new Handler();
	private Runnable updateRunnable = new Runnable() {
		@Override
		public void run() {
			dataset.getSeriesAt(0).add(addX, plus);
			dataset.getSeriesAt(1).add(addX, minus);
			addX++;
			plus++;
			minus--;
			graphicalView.repaint();
			if (addX < 20) handler.postDelayed(updateRunnable, 1000);
		}
	};

8〜9行目、チャートデータのdatasetにデータを追加する。
addXは横軸のメモリ、plus, minusが追加するデータ。
13行目、チャートを再描画。
14行目、横軸のメモリが20より小さければ、1秒後に再度データの更新。
これでデータの更新処理は実装できたので、onCreate()の一番最後の行に以下の1行を追加。

handler.postDelayed(updateRunnable, 1000);

これで、アプリ起動後に非同期でチャートが更新される。
今回は1秒ごとに定期的に更新させているが、実際に使用するシーンでは、ネット、Bluetooth、ソケット等々を経由して更新すべきデータを受信した際に、その都度チャートを更新するような感じになると思う。

ソース

Ruby on Railsの環境構築

Ruby on Rails 3.2 を Mac OS X にインストールする手順をかなり丁寧に説明してみました
こちらを参考にRailsをインストールしてみた。
Xcode, Homebrew, RVM, Ruby(RVM経由)が既にインストール済みの環境でバージョンは以下の通り。
MacOSX 10.7.4
Xcode 4.3.3
Homebrew 0.9

RVMを削除した際に.bash_profileに記載してあるRVMの設定も削除。

$ brew install rbenv
$ brew install ruby-build

あたりで「/usr/local/Cellar」のpermissionでコケたのでsudoを追加する。
# /usr/local/Cellarのオーナーがrootになっていたのが原因だが、何故かは不明w
# readlineも同様

$ CONFIGURE_OPTS="--with-readline-dir=/usr/local" rbenv install 1.9.3-p194

Rubyのインストールの際にp194が無く、最新がp125だった。
そのp125もGCC4.2が無いのでインストールできず。
コチラを参考にruby-buildのバージョンを上げてみる。
ruby-buildが既に最新だったがリンク先にあるバージョン20120423よりも古かったので、Homebrewをupdateしてからruby-buildをupgradeしたらruby-buildのバージョンが20120524になった。

$ brew update
$ brew upgrade ruby-build

Railsをインストールして確認すると、railsをインストールし直せと言われる。

$ rails -v
Rails is not currently installed on this system. ...

whichだと見つかるのでパスの問題のようだ。

$ which rails
/Users/<user>/.rbenv/shims/rails

そこで

$ source ~/.bash_profile
$ rails -v
Rails 3.2.6

してみたらRailsのバージョンが確認できた。
多分、.bash_profileに追記した

eval "$(rbenv init -)"

が影響してるのだと思うが、よく分かっていないw

最後にRailsのプロジェクトを作成してbundleをインストールする時に、本文中にもあるように「Segmentation fault」で落ちたので、Rubyを再インストール。

$ CONFIGURE_OPTS="--with-openssl-dir=/opt/local --with-readline-dir=/usr/local" rbenv install 1.9.3-p194

MacPortsを削除した場合は最初からこちらでも良いのかもしれない。

Rubyの環境構築

MacOSX(Lion)にはruby1.8.7がインストールされてるが、1.9系やらrvmやらをインストールしたくなったのでメモ。
https://rvm.io/rvm/install/を参考にrvmを入れる。
rvmとrubyを同時にインストールしたりできるらしいが、今回は別々で。

$ curl -L get.rvm.io | bash -s stable
$ source ~/.rvm/scripts/rvm
$ rvm -v
rvm 1.14.1 (stable) by Wayne E. Seguin <wayneeseguin@gmail.com>, Michal Papis <mpapis@gmail.com> [https://rvm.io/]

rvmはこれでおk。
次はrubyを入れる。
インストール可能なバージョンを確認。

$ rvm list known
# MRI Rubies
[ruby-]1.8.6[-p420]
・・・
[ruby-]1.9.3-preview1
[ruby-]1.9.3-rc1
[ruby-]1.9.3-p0
[ruby-]1.9.3-p125
[ruby-]1.9.3[-p194]
[ruby-]1.9.3-head
ruby-head

# GoRuby
goruby
・・・

最新の1.9.3をインストールする。

$ rvm install 1.9.3
$ rvm use 1.9.3
$ ruby -v
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin11.4.0]

一つ前のバージョンも入れてみよう。

$ rvm install 1.9.2
The provided compiler '/usr/bin/gcc' is LLVM based, it is not yet fully supported by ruby and gems, please read `rvm requirements`.

なんかダメらしい。。。
コンパイラーがサポートされてない?
rvm requirements?

$ rvm requirements

  Notes for Mac OS X 10.7.4, Xcode 4.3.2.
・・・
Xcode 4.3+ users
- please be warned
- only ruby-1.9.3-p125+ is partially supported
- in case of any compilation issues:
 * downgrade to Xcode 4.1
 * uninstall Xcode and install osx-gcc-installer
and reinstall your rubies.

rvmの意味なくね?

Node.jsの環境構築

Node.jsをMacOSX(Lion)にインストールしたのでメモ。
ググると色々出てくるのだが、とりあえず以下の手順でNode.js, nvmをインストール。

$ git clone git://github.com/creationix/nvm.git ~/.nvm
$ nvm install v0.6.18
$ nvm use v0.6.18

npmはv0.6.18のインストール時に入るらしい。

$ which npm
/Users/<user>/.nvm/v0.6.18/bin/npm

このnpmでパッケージをグローバルインストールすると、「~/.nvm/v0.6.18/lib/node_module/」にインストールされる。
バージョン毎のフォルダの下のどこかグローバルなんだ?
当然パスも通ってないので何がグローバルなのか全く分からない。
ということでnpmはローカルインストールで運用することにした。
他環境へデプロイ時はローカルインストールの方が分かりやすいので個人的にはこれでおk。

%d人のブロガーが「いいね」をつけました。