Geocoding

 Google Maps APIGeocodingを使ってみる。
 前回作った天気予報のRSSデータについて、地点名からGeocodingで経度・緯度を取得して表に整理する。

ソースコード

○RssPanel.java(前回作ったRSSPanelクラス)

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Image;
import java.awt.geom.Point2D;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import de.nava.informa.core.ChannelIF;
import de.nava.informa.core.ItemIF;
import de.nava.informa.core.ParseException;
import de.nava.informa.impl.basic.ChannelBuilder;
import de.nava.informa.parsers.FeedParser;

public class RssPanel extends JPanel{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private DefaultTableModel model;
	private Map<String,ImageIcon> icons;
	private final String apiKey="GoogleのAPIキー"
	
	public RssPanel(){
		super(new BorderLayout());
		JTable table=new JTable();
		super.add(new JScrollPane(table),BorderLayout.CENTER);
		model=new DefaultTableModel(){
			private static final long serialVersionUID = 1L;
			@Override
			public boolean isCellEditable(int row, int column) {
				return false;
			}
		};
		model.setRowCount(0);
		model.setColumnCount(5);
		model.setColumnIdentifiers(new String[]{
				"都市名","予報","URL","経度","緯度"});
		table.setModel(model);
		table.setRowHeight(34);
		table.getColumnModel().getColumn(0).setMaxWidth(80);
		table.getColumnModel().getColumn(1).setMaxWidth(70);
		table.setDefaultRenderer(Object.class, new DefaultTableCellRenderer(){
			private static final long serialVersionUID = 1L;
			@Override
			public Component getTableCellRendererComponent(
			 JTable arg0, Object arg1, boolean arg2, boolean arg3,
			 int arg4, int arg5) {
				if(arg1 instanceof ImageIcon){
					JLabel ll=new JLabel((ImageIcon)arg1);
					return ll;
				}else if(arg1 instanceof URL){
					return super.getTableCellRendererComponent(
			 arg0, arg1, arg2, arg3, arg4, arg5);
				}else{
					return super.getTableCellRendererComponent(
			 arg0, arg1, arg2, arg3, arg4, arg5);
				}
			}
			
		});
		
		icons=new HashMap<String,ImageIcon>();
		Runnable r=new Runnable(){
			public void run(){
				List<Entry> urls=updateAreaCity();
				model.setRowCount(urls.size());
				updateModel(urls);
				getLatLng();
			}
		};
		new Thread(r).start();
	}
	
	private List<Entry> updateAreaCity(){
		try{
			List<Entry> ret=new ArrayList<Entry>();
			DocumentBuilderFactory dbf
			  =DocumentBuilderFactory.newInstance();
			DocumentBuilder db=dbf.newDocumentBuilder();
			URL url = new URL(
			  "http://weather.livedoor.com/forecast/rss/forecastmap.xml");
			Document doc=db.parse(url.openStream());
			NodeList list=doc.getChildNodes();
			Node n=list.item(0);
			parse(n,ret);
			return ret;
		}catch(Exception e){
			e.printStackTrace();
			return null;
		}
	}
	
	private void parse(Node n,List<Entry> ret){
		NodeList list=n.getChildNodes();
		for(int i=0;i<list.getLength();i++){
			Node tmp=list.item(i);
			if(tmp.getNodeType()==3)continue;
			if(tmp.getNodeName().equals("city")){
				NamedNodeMap map=tmp.getAttributes();
				Entry e=new Entry();
				e.id=Integer.parseInt(
			 		map.getNamedItem("id").getNodeValue());
				e.name=map.getNamedItem("title").getNodeValue();
				e.url=map.getNamedItem("source").getNodeValue();
				ret.add(e);
			}
			if(tmp.hasChildNodes()){
				parse(tmp,ret);
			}
		}
	}
	
	private void updateModel(List<Entry> urls){
		for(Entry et : urls){
			try{
				URL url=new URL(et.url);
				ChannelIF channel =
				FeedParser.parse(new ChannelBuilder(), url);
				process(channel,et);
			}catch(MalformedURLException e){
				e.printStackTrace();
			}catch (ParseException e){
				e.printStackTrace();
			}catch (IOException e){
				e.printStackTrace();
			}
		}
	}
	
	private void getLatLng(){
		GeoCoder gc=new GeoCoder();
		for(int i=0;i<model.getRowCount();i++){
			String name=(String)model.getValueAt(i, 0);
			GeoCoderEntry[] e=gc.geoCode(name, apiKey);
			if(e.length>0){
				Point2D c=e[0].getCoordinates();
				model.setValueAt(Double.toString(c.getX()),i,3);
				model.setValueAt(Double.toString(c.getY()),i,4);
			}
			try{Thread.sleep(300);}catch(Exception ex){}
		}
	}
	
	private void process(ChannelIF channel,Entry e)
			 throws ParseException,IOException{
		Set<ItemIF> list=channel.getItems();
		for(ItemIF item : list){
			if(item.getTitle().contains("PR"))continue;
			URL url=item.getLink();
			String is=item.getElementValues(
			"image", new String[]{"url"})[0];
			ImageIcon icon=icons.get(is);
			if(icon==null){
				URL u=new URL(is);
				Image im=ImageIO.read(u);
				icon=new ImageIcon(im);
			}
			model.setValueAt(e.name, e.id-1, 0);
			model.setValueAt(icon, e.id-1, 1);
			model.setValueAt(url, e.id-1, 2);
			break;
		}
	}
	
	private class Entry{
		int id;
		String name;
		String url;
	}
	
    public static void main(String[] args) {
    	JFrame f=new JFrame();
		try {
			UIManager.setLookAndFeel(
			"com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
			SwingUtilities.updateComponentTreeUI(f);
		}catch(Exception e){}
    	f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    	f.getContentPane().setLayout(new BorderLayout());
    	f.setSize(480, 320);
    	f.getContentPane().add(new RssPanel(),BorderLayout.CENTER);
    	f.setVisible(true);
	}
}

○GeoCoder.java(Geocodingで緯度・経度を取得するクラス)

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class GeoCoder {
	private String proxy;
	private int port;
	
	public GeoCoder(){
		proxy=null;
	}
	
	public GeoCoder(String proxy,int port){
		this.proxy=proxy;
		this.port=port;
	}

	public GeoCoderEntry[] geoCode(String str,String apiKey){
		List<GeoCoderEntry> list=new ArrayList<GeoCoderEntry>();
		try{
			String urls="http://maps.google.com/maps/geo?";
			String query="q="+str+"&output=xml&oe=utf8&sensor=true_or_false&key="+apiKey;
			URL url;
			if(proxy==null){
				url=new URL(urls+query);
			}else{
				url=new URL("http",proxy,port,urls+query);
			}
			HttpURLConnection conn=(HttpURLConnection)url.openConnection();
			conn.setRequestMethod("GET");
			conn.setInstanceFollowRedirects(false);
			conn.setRequestProperty("Accept-Language", "ja;q=0.7,en;q=0.3");
			conn.connect();
			DocumentBuilderFactory dbf=DocumentBuilderFactory.newInstance();
			DocumentBuilder db=dbf.newDocumentBuilder();
			Document doc=db.parse(conn.getInputStream());
			parse(doc,list);
			try{Thread.sleep(500);}catch(Exception e){}
		}catch(MalformedURLException e){
			e.printStackTrace();
		}catch(IOException e){
			e.printStackTrace();
		}catch(ParserConfigurationException e){
			e.printStackTrace();
		}catch(SAXException e){
			e.printStackTrace();
		}
		return list.toArray(new GeoCoderEntry[list.size()]);
	}
	
	private void parse(Document doc,List<GeoCoderEntry> l){
		NodeList list=doc.getChildNodes();
		for(int i=0;i<list.getLength();i++){
			Node node=list.item(i);
			parseNode(node,l);
		}
	}
	
	private void parseNode(Node n,List<GeoCoderEntry> l){
		if(n.getNodeName().equals("Placemark")){
			l.add(new GeoCoderEntry(n));
		}else{
			NodeList list=n.getChildNodes();
			for(int i=0;i<list.getLength();i++){
				Node node=list.item(i);
				parseNode(node,l);
			}
		}
	}
	
}

○GeoCoderEntry.java

import java.awt.geom.Point2D;

import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class GeoCoderEntry {
	private String id;
	private String address;
	private String contryName;
	private String administrativeAreaName;
	private String addressLine;
	private Point2D coordinates;
	private Point2D[] rectNESW;
	
	public GeoCoderEntry(Node n)throws IllegalArgumentException{
		if(!n.getNodeName().equals("Placemark")){
			throw new IllegalArgumentException("Not Placemark Node");
		}
		NamedNodeMap nnp=n.getAttributes();
		Node p=nnp.getNamedItem("id");
		id=p.getNodeValue();
		NodeList list=n.getChildNodes();
		for(int i=0;i<list.getLength();i++){
			parseNode(list.item(i));
		}
	}
	
	private void parseNode(Node n){
		String name=n.getNodeName();
		if(name.equals("address")){
			address=n.getTextContent();
		}else if(name.equals("CountryName")){
			contryName=n.getTextContent();
		}else if(name.equals("AdministrativeAreaName")){
			administrativeAreaName=n.getTextContent();
		}else if(name.equals("AddressLine")){
			addressLine=n.getTextContent();
		}else if(name.equals("LatLonBox")){
			NamedNodeMap nnp=n.getAttributes();
			String norths=nnp.getNamedItem("north").getNodeValue();
			String souths=nnp.getNamedItem("south").getNodeValue();
			String easts=nnp.getNamedItem("east").getNodeValue();
			String wests=nnp.getNamedItem("west").getNodeValue();
			rectNESW=new Point2D[2];
			rectNESW[0]=new Point2D.Double(
					Double.parseDouble(norths),
					Double.parseDouble(easts)
			);
			rectNESW[1]=new Point2D.Double(
					Double.parseDouble(souths),
					Double.parseDouble(wests)
			);
		}else if(name.equals("coordinates")){
			String[] ll=n.getTextContent().split(",");
			double lat=Double.parseDouble(ll[0]);
			double lng=Double.parseDouble(ll[1]);
			coordinates=new Point2D.Double(lat,lng);
		}
		NodeList list=n.getChildNodes();
		for(int i=0;i<list.getLength();i++){
			parseNode(list.item(i));
		}
	}

	public String getId() {
		return id;
	}

	public String getAddress() {
		return address;
	}

	public String getContryName() {
		return contryName;
	}

	public String getAdministrativeAreaName() {
		return administrativeAreaName;
	}

	public String getAddressLine() {
		return addressLine;
	}

	public Point2D getCoordinates() {
		return coordinates;
	}

	public Point2D[] getRectNESW() {
		return rectNESW;
	}
	
	public String getOutline(){
		return "<"+address+">"+coordinates.getX()+","+coordinates.getY();
	}
}

■実行結果
 とりあえず、天気予報について、地名、予報、緯度・経度が取得できたので、次は、ちょっと簡単なマッシュアップってやつをやってみたい。