前回に引き続き今回もProcessingによる日本全国コンビニ店舗分布地図について書いてみる。前回は大きさが固定された地図で拡大・縮小や移動ができなかったので、高解像度インタラクティブ版として、それをできるようにしてみた。
まず、日本地図がPNG画像なのでこれをベクタ画像に変更する。フォーマットはSVGだ。前回と同様に「カビパン男と私」で提供されている日本地図のSVGファイルを若干加工して利用させてもらった。
次にこのSVGデータをProcessingで利用する方法について述べる。「ビジュアライジング・データ」にもSVGの利用方法(processing.candy.*)が書いてあるのだが、実はここに書いてある情報は古くて現在では使用できない。現在では、PShapeを使ってSVGを利用する。
PShape mapShape = loadShape("japan.svg");
こんな感じだ。画面の(x, y)座標に表示するにはdraw関数内で以下のように記述すればいい。
shape(mapShape, x, y);
詳しくはProcessingのリファレンスに書かれているので、下記のソースコードなどを参考にして調べてみて欲しい。
さて、これでSVG画像が利用できるようになったので、今度はこれを使って、マウスの左ボタンのドラッグで平行移動を行えるようにしてみる。やり方としてはmouseDragged関数を使う。移動したピクセル分だけ画像も移動する。mouseDragged関数内で移動分のオフセットを変数に入れるようにし、上述のshape関数でオフセット分だけ移動した位置に描画すればいい。
次に画像の拡大・縮小を行う。マウスの右ボタンのドラッグで操作する。マウスを上に移動すれば拡大、下に移動すれば縮小だ。SVG画像の縮小・拡大はscaleメソッドを使う。例えば、上記の例で元の画像を1.5倍にしたければ以下のようにする。
mapShape.scale(1.5);
scaleメソッドによる拡大・縮小は画像の位置やそのオフセットなどもちゃんと正しい位置になるようにしなければならないのだが、それらについてはソースコードを参考にして欲しい。
上記はSVG画像による日本地図の描画について説明したが、コンビニデータの描画についても位置のオフセットと拡大率・縮小率で行うことができ、Placeクラスのdrawメソッドをちょっといじればいい。
以下が完成版。最初に全部のコンビニを表示してしまうと分布が解りづらいので、今回はセブンイレブンとローソンのみを最初に表示するようにした。コンビニの表示・非表示はコンビニ名をクリックすることで切り替えることができる。
以下にソースコードを示しておく。
convenience.pde
String convData = "data.tsv.gz"; String japanMap = "japan.svg"; PShape mapShape; int totalCount; Place[] places; int placeCount = 0; final float minX = -0.19834; final float maxX = 0.2425; final float minY = -0.1875; final float maxY = 0.1845; PImage mapImage; PFont font; float offset_x = 0.0; float offset_y = 0.0; int center_x = 0; int center_y = 0; float zoom = 1.0; int mx_start, my_start, my_pos; String[] convName = { "Seven-Eleven", "LAWSON", "FamilyMart", "Circle K", "Sunkus", "Daily YAMAZAKI", "MINISTOP", "am/pm", "POPLAR", "Three F", "COMMUNITY STORE", "Cocostore", "SHOP99", "Seicomart", "SAVE ON" }; color[] convColor = { #ff0000, #007fff, #00ff00, #ffff00, #ff00ff, #00ffff, #7f0000, #0000ff, #007f00, #7f7f00, #7f007f, #007f7f, #7f7f7f, #ff7f00, #7f00ff }; boolean[] convIds = { true, true, false, false, false, false, false, false, false, false, false, false, false, false, false }; int numConv = 15; public void setup() { mapShape = loadShape(japanMap); smooth(); loop(); size(int(mapShape.width), int(mapShape.height), JAVA2D); shapeMode(CORNER); mapShape.disableStyle(); font = loadFont("CourierNewPSMT-14.vlw"); textMode(SCREEN); textFont(font); readData(); } public void draw() { background(0); noStroke(); fill(32); shape(mapShape, int(offset_x * zoom) + center_x, int(offset_y * zoom) + center_y); for (int i = 0; i < placeCount; i++) places[i].draw(); for (int i = 0; i < numConv; i++) { fill(convColor[i]); text(convName[i], 15, 15 * (i + 1)); if (convIds[i]) text("*", 5, 15 * (i + 1)); } } float TX(float x) { float offset = width * (1.0 - zoom) * 0.5; return map(x, minX, maxX, offset, width - offset); } float TY(float y) { float offset = height * (1.0 - zoom) * 0.5; return map(y, minY, maxY, height - offset, offset); } void mousePressed() { mx_start = int(mouseX - offset_x * zoom); my_start = int(mouseY - offset_y * zoom); loop(); for (int i = 0; i < numConv; i++) if (mouseX < convName[i].length() * 8 + 15 && mouseY > 15 * i && mouseY < 15 * (i + 1)) convIds[i] = !convIds[i]; my_pos = mouseY; } void mouseReleased() { noLoop(); } void mouseDragged() { if (mouseButton == LEFT) { offset_x = (mouseX - mx_start) / zoom; offset_y = (mouseY - my_start) / zoom; } else if (mouseButton == RIGHT) { if (mouseY - my_pos > 4) { float zoom_out = 9.8 / 10.0; mapShape.scale(zoom_out); zoom *= zoom_out; center_x = int(width * (1.0 - zoom) * 0.5); center_y = int(height * (1.0 - zoom) * 0.5); my_pos = mouseY; } else if (mouseY - my_pos < -4) { float zoom_in = 10.0 / 9.8; mapShape.scale(zoom_in); zoom *= zoom_in; center_x = int(width * (1.0 - zoom) * 0.5); center_y = int(height * (1.0 - zoom) * 0.5); my_pos = mouseY; } } } void readData() { new Slurper(); } Place parsePlace(String line) { String pieces[] = split(line, '\t'); int id = int(pieces[0]); String name = pieces[1]; float x = float(pieces[2]); float y = float(pieces[3]); return new Place(id, name, x, y); }
Place.pde
class Place { int id; String name; float x, y; public Place(int id, String name, float x, float y) { this.id = id; this.name = name; this.x = x; this.y = y; } public void draw() { if (convIds[this.id]) { int xx = (int)TX(x) + int(offset_x * zoom); int yy = (int)TY(y) + int(offset_y * zoom); set(xx, yy, convColor[this.id]); } } }
Slurper.pde
class Slurper implements Runnable { Slurper() { Thread thread = new Thread(this); thread.start(); } public void run() { try { BufferedReader reader = createReader(convData); String line = reader.readLine(); totalCount = int(line); places = new Place[totalCount]; while ((line = reader.readLine()) != null) { places[placeCount] = parsePlace(line); placeCount++; } } catch (IOException e) { e.printStackTrace(); } noLoop(); } }
まず、日本地図がPNG画像なのでこれをベクタ画像に変更する。フォーマットはSVGだ。前回と同様に「カビパン男と私」で提供されている日本地図のSVGファイルを若干加工して利用させてもらった。
次にこのSVGデータをProcessingで利用する方法について述べる。「ビジュアライジング・データ」にもSVGの利用方法(processing.candy.*)が書いてあるのだが、実はここに書いてある情報は古くて現在では使用できない。現在では、PShapeを使ってSVGを利用する。
PShape mapShape = loadShape("japan.svg");
こんな感じだ。画面の(x, y)座標に表示するにはdraw関数内で以下のように記述すればいい。
shape(mapShape, x, y);
詳しくはProcessingのリファレンスに書かれているので、下記のソースコードなどを参考にして調べてみて欲しい。
さて、これでSVG画像が利用できるようになったので、今度はこれを使って、マウスの左ボタンのドラッグで平行移動を行えるようにしてみる。やり方としてはmouseDragged関数を使う。移動したピクセル分だけ画像も移動する。mouseDragged関数内で移動分のオフセットを変数に入れるようにし、上述のshape関数でオフセット分だけ移動した位置に描画すればいい。
次に画像の拡大・縮小を行う。マウスの右ボタンのドラッグで操作する。マウスを上に移動すれば拡大、下に移動すれば縮小だ。SVG画像の縮小・拡大はscaleメソッドを使う。例えば、上記の例で元の画像を1.5倍にしたければ以下のようにする。
mapShape.scale(1.5);
scaleメソッドによる拡大・縮小は画像の位置やそのオフセットなどもちゃんと正しい位置になるようにしなければならないのだが、それらについてはソースコードを参考にして欲しい。
上記はSVG画像による日本地図の描画について説明したが、コンビニデータの描画についても位置のオフセットと拡大率・縮小率で行うことができ、Placeクラスのdrawメソッドをちょっといじればいい。
以下が完成版。最初に全部のコンビニを表示してしまうと分布が解りづらいので、今回はセブンイレブンとローソンのみを最初に表示するようにした。コンビニの表示・非表示はコンビニ名をクリックすることで切り替えることができる。
以下にソースコードを示しておく。
convenience.pde
String convData = "data.tsv.gz"; String japanMap = "japan.svg"; PShape mapShape; int totalCount; Place[] places; int placeCount = 0; final float minX = -0.19834; final float maxX = 0.2425; final float minY = -0.1875; final float maxY = 0.1845; PImage mapImage; PFont font; float offset_x = 0.0; float offset_y = 0.0; int center_x = 0; int center_y = 0; float zoom = 1.0; int mx_start, my_start, my_pos; String[] convName = { "Seven-Eleven", "LAWSON", "FamilyMart", "Circle K", "Sunkus", "Daily YAMAZAKI", "MINISTOP", "am/pm", "POPLAR", "Three F", "COMMUNITY STORE", "Cocostore", "SHOP99", "Seicomart", "SAVE ON" }; color[] convColor = { #ff0000, #007fff, #00ff00, #ffff00, #ff00ff, #00ffff, #7f0000, #0000ff, #007f00, #7f7f00, #7f007f, #007f7f, #7f7f7f, #ff7f00, #7f00ff }; boolean[] convIds = { true, true, false, false, false, false, false, false, false, false, false, false, false, false, false }; int numConv = 15; public void setup() { mapShape = loadShape(japanMap); smooth(); loop(); size(int(mapShape.width), int(mapShape.height), JAVA2D); shapeMode(CORNER); mapShape.disableStyle(); font = loadFont("CourierNewPSMT-14.vlw"); textMode(SCREEN); textFont(font); readData(); } public void draw() { background(0); noStroke(); fill(32); shape(mapShape, int(offset_x * zoom) + center_x, int(offset_y * zoom) + center_y); for (int i = 0; i < placeCount; i++) places[i].draw(); for (int i = 0; i < numConv; i++) { fill(convColor[i]); text(convName[i], 15, 15 * (i + 1)); if (convIds[i]) text("*", 5, 15 * (i + 1)); } } float TX(float x) { float offset = width * (1.0 - zoom) * 0.5; return map(x, minX, maxX, offset, width - offset); } float TY(float y) { float offset = height * (1.0 - zoom) * 0.5; return map(y, minY, maxY, height - offset, offset); } void mousePressed() { mx_start = int(mouseX - offset_x * zoom); my_start = int(mouseY - offset_y * zoom); loop(); for (int i = 0; i < numConv; i++) if (mouseX < convName[i].length() * 8 + 15 && mouseY > 15 * i && mouseY < 15 * (i + 1)) convIds[i] = !convIds[i]; my_pos = mouseY; } void mouseReleased() { noLoop(); } void mouseDragged() { if (mouseButton == LEFT) { offset_x = (mouseX - mx_start) / zoom; offset_y = (mouseY - my_start) / zoom; } else if (mouseButton == RIGHT) { if (mouseY - my_pos > 4) { float zoom_out = 9.8 / 10.0; mapShape.scale(zoom_out); zoom *= zoom_out; center_x = int(width * (1.0 - zoom) * 0.5); center_y = int(height * (1.0 - zoom) * 0.5); my_pos = mouseY; } else if (mouseY - my_pos < -4) { float zoom_in = 10.0 / 9.8; mapShape.scale(zoom_in); zoom *= zoom_in; center_x = int(width * (1.0 - zoom) * 0.5); center_y = int(height * (1.0 - zoom) * 0.5); my_pos = mouseY; } } } void readData() { new Slurper(); } Place parsePlace(String line) { String pieces[] = split(line, '\t'); int id = int(pieces[0]); String name = pieces[1]; float x = float(pieces[2]); float y = float(pieces[3]); return new Place(id, name, x, y); }
Place.pde
class Place { int id; String name; float x, y; public Place(int id, String name, float x, float y) { this.id = id; this.name = name; this.x = x; this.y = y; } public void draw() { if (convIds[this.id]) { int xx = (int)TX(x) + int(offset_x * zoom); int yy = (int)TY(y) + int(offset_y * zoom); set(xx, yy, convColor[this.id]); } } }
Slurper.pde
class Slurper implements Runnable { Slurper() { Thread thread = new Thread(this); thread.start(); } public void run() { try { BufferedReader reader = createReader(convData); String line = reader.readLine(); totalCount = int(line); places = new Place[totalCount]; while ((line = reader.readLine()) != null) { places[placeCount] = parsePlace(line); placeCount++; } } catch (IOException e) { e.printStackTrace(); } noLoop(); } }
コメント