// =====================================================================================
// Csv2TiledSVGMap
//
// This is a sample for study of the routine which generates the POI contents based on SVGMap by which tiling was performed. 
//
// Programmed by Satoru Takagi 2013.7.9
// Copright 2013 by Satoru Takagi @ KDDI All Rights Reserved
//
// Input data form :
// Assumed CSV format : Longitude, Latitude, metadata, ....
// It assumes that there is a header in the 1st line. 
// And the number of items of a header and the number of items of the real data after the 2nd line must be equal. 
//
// Data will be generated by the following commands. 
// java Csv2TiledSVGMap hoge.csv
//
// =====================================================================================
//
// This software is free software; you can redistribute it and/or modify it under 
// the terms of the GNU Lesser General Public License as published by  the  Free 
// Software Foundation; either version 2.1 of the License, or (at  your  option) 
// any later version.
//
// This library is distributed in the hope that it will be  useful,  but WITHOUT 
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for  more 
// details.
//
// You should have received a copy of the  GNU  Lesser  General  Public  License 
// along with this library; if not, write to the Free Software Foundation, Inc., 
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
//
// =====================================================================================



import java.io.*;
import java.util.*;
import java.awt.geom.*;
import java.awt.Point;

// A coordinates concept assumes longitude:x and latitude:y. 

public class Csv2TiledSVGMap {
	
	File csvFile; // Input CSV file 
	HashMap<String,TinyPoiSVGMap> tiles = new HashMap<String,TinyPoiSVGMap>(); // The associative array containing the tile of SVGMapPOI 
	
	String[] header;
	
	TinyPoiSVGMap container; // Container
	
	public static final String UTF_8 = "UTF-8";
	public static final String MS932 = "MS932"; // It is Japanese SHIFT-JIS char code
	String charset = MS932; // Set char code
	
	BufferedReader reader;
	
	public static void main(String[] args) throws Exception{
		// Put the path of csv into the first argument. 
		Csv2TiledSVGMap c2s = new Csv2TiledSVGMap();
		c2s.convert(args[0]);
	}
	
	public void convert( String filePath ) throws Exception{
		csvFile = new File(filePath);
		// Open a CSV file
		reader = new BufferedReader(new InputStreamReader(new FileInputStream(csvFile), charset ));
		
		String line = reader.readLine();
		header = line.split("\\,");
		int items = header.length;
		
		// Continue reading to a file end. 
		for (line = reader.readLine(); line != null; line = reader.readLine()) {
			String[] tokens = line.split("\\,"); // Since this implementation is simplified, a defect may occur. 
			Point2D.Double poi = new Point2D.Double(Double.parseDouble(tokens[0]),Double.parseDouble(tokens[1]));
			updateCsvArea(poi); // Update a data area. 
			// Add POI to a tiles
			addPoint(poi, tokens , tiles);
		}
		closeTiles(tiles);
		
		// Generate container
		buildContainer(tiles , new Rectangle2D.Double(poiMin.x , poiMin.y , poiMax.x - poiMin.x , poiMax.y - poiMin.y));
	}
	
	Point2D.Double poiMin = null;
	Point2D.Double poiMax = null;
	void updateCsvArea( Point2D.Double poi ){
		if ( poiMin == null ){
			poiMin = new Point2D.Double(poi.x, poi.y);
			poiMax = new Point2D.Double(poi.x, poi.y);
		}
		
		if ( poi.x > poiMax.x ){
			poiMax.x = poi.x;
		} else if ( poi.x < poiMin.x ){
			poiMin.x = poi.x;
		}
		
		if ( poi.y > poiMax.y ){
			poiMax.y = poi.y;
		} else if ( poi.y < poiMin.y ){
			poiMin.y = poi.y;
		}
	}
	
	// Distribute a POI to an appropriate tile. It may be the most important part of this program. 
	// The routine which generates the file of a tile when a tile has not been generated 
	// And the routine which adds it to an associative array
	// The global variable to be used: hedaer[]
	public boolean addPoint( Point2D.Double poi , String[] metadata , HashMap<String,TinyPoiSVGMap> tiles ) throws Exception{
		TinyPoiSVGMap tile;
		Point tnumb = getTileNumber(poi);
		String tname = getTileName(tnumb);
		if ( !tiles.containsKey(tname) ){
			// When there is no applicable tile in an associative array, establish newly, and add it to an associative array. 
			tile = new TinyPoiSVGMap( getTileFile( tname ) , getTileRect( tnumb ) ,  header );
			tile.setSymbol();
			tiles.put( tname , tile );
		} else {
			tile = tiles.get(tname);
		}
		tile.addPoint( poi , metadata[2] , metadata );
		return ( true );
	}
	
	public boolean buildContainer( HashMap<String,TinyPoiSVGMap> tiles , Rectangle2D.Double area ) throws Exception{
		String name0 = csvFile.getName();
		name0 = name0.substring(0,name0.lastIndexOf("."));
		TinyPoiSVGMap container = new TinyPoiSVGMap( getContainerFile( ) , area ,  null );
		for (String key : tiles.keySet()) {
			String href = name0 + "_" + key + ".svg";
			container.addTile( href ,  getTileRect( getTileNumber( key ) ) );
		}
		container.close();
		return ( true );
	}
	
	
	// Close all tiles
	public void closeTiles(HashMap<String,TinyPoiSVGMap> tiles) throws Exception {
		for (String key : tiles.keySet()) {
			TinyPoiSVGMap  tile = tiles.get(key);
			tile.close();
		}
	}
	
	
	// The constant for getTileNumber (used for a setup of the interval of a tile, etc.) 
	static double interval = 1; // interval of tile (in degrees)
	static double x0 = -180;
	static double y0 = -90;
	// Obtain the tile number of the specified latitude/longitude. 
	// Assume tile numbers to be a pair of values (Point type) in order of latitude (y) and longitude (x).
	Point getTileNumber(Point2D.Double geoP){ 
		double x = geoP.x - x0;
		double y = geoP.y - y0;
		int xn = (int)(x / interval);
		int yn = (int)(y / interval);
		return ( new Point( xn , yn ) );
	}
	
	Rectangle2D.Double getTileRect( Point tileNumb ){
		double tx0 = tileNumb.x * interval + x0;
		double ty0 = tileNumb.y * interval + y0;
		return ( new Rectangle2D.Double( tx0 , ty0 , (double)interval , (double)interval ));
	}
	
	// Obtain a tile number from a tile name. 
	Point getTileNumber(String tileName){
		String[] tileStr = tileName.split("_");
		return ( new Point(Integer.parseInt(tileStr[0]),Integer.parseInt(tileStr[1])));
	}
	// Obtain a tile name from a tile number. 
	String getTileName(Point TileNumber){
		return ( TileNumber.x + "_" + TileNumber.y);
	}
	
	// Obtain the file of a tile from a tile number. 
	// A tile is generated in the same directory as source csv.
	// File name : original file name + "_"+ tile name + ".svg"
	// The global variable to be used: csvFile 
	File getTileFile(String TileName){
		String cpath = csvFile.getAbsolutePath();
		String tpath = cpath.substring(0,cpath.lastIndexOf("."))+"_"+TileName+".svg";
		File tf = new File(tpath);
		return ( tf );
	}
	// Obtain the file of a container. 
	// A tile is generated in the same directory as source csv.
	// File name : original file name +"_contaner.svg"
	// The global variable to be used: csvFile 
	File getContainerFile(){
		String cpath = csvFile.getAbsolutePath();
		String tpath = cpath.substring(0,cpath.lastIndexOf("."))+"_container.svg";
		File tf = new File(tpath);
		return ( tf );
	}
	
	// SVGMap Class for making POI and the container for it 
	class TinyPoiSVGMap {
		double a = 100;
		double d = -100;
		Writer out;
		String path;
		
		// The constructor of SVGMap (attach a header etc.) 
		TinyPoiSVGMap( File svgFile , Rectangle2D.Double geoViewbox , String[] header) throws Exception{ // Constructor 
			Rectangle2D.Double sv = geo2SvgRect(geoViewbox);
			//Prepare the writer of SVG (charset is UTF8). 
			path = svgFile.getAbsolutePath();
			FileOutputStream osFile = new FileOutputStream( svgFile );
			out = new BufferedWriter(new OutputStreamWriter( osFile , "UTF-8" ));
			
			out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
			out.write("<svg  xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" ");
			out.write("viewBox=\"" + sv.x + " " + sv.y + " " + sv.width + " " + sv.height +"\" ");
			if ( header != null ){
				out.write("property=\"");
				for ( int i = 0 ; i < header.length ; i++ ){
					if ( i == header.length -1 ){
						out.write( header[i] );
					} else {
						out.write( header[i] + "," );
					}
				}
				out.write("\" ");
			}
			out.write(">\n");
			out.write(" <globalCoordinateSystem srsName=\"http://purl.org/crs/84\" ");
			out.write("transform=\"matrix(" + a + "," + 0 + "," + 0 + "," + d + "," + 0 + "," + 0 +")\" />\n");
			out.write("\n");
			
			
		}
		
		// Close SVGMap data. 
		public void close() throws Exception{
			out.write("</svg>\n");
			out.close();
		}
		
		// Attach the declaration for a symbol. 
		public void setSymbol() throws Exception{
			out.write(" <defs>\n");
			out.write("  <g id=\"syl0\" >\n");
			out.write("   <image xlink:href=\"mappin.png\" preserveAspectRatio=\"none\" x=\"-8\" y=\"-25\" width=\"19\" height=\"27\"/>\n");
			out.write("  </g>\n");
			out.write(" </defs>\n");
			out.write("\n");
		}
		
		// register POI.
		public void addPoint( Point2D.Double geoPoint , String caption , String[] metadata ) throws Exception{
			Point2D.Double sp = geo2SvgPoint(geoPoint);
			out.write(" <use transform=\"ref(svg," + sp.x +"," + sp.y + ")\" x=\"0\" y=\"0\" xlink:href=\"#syl0\" ");
			if ( caption != null && caption !=""){
				out.write("xlink:title=\"" + escape(caption) + "\" ");
			}
			if ( metadata !=null){
				out.write("content=\"");
				for ( int i = 0 ; i < metadata.length ; i++ ){
					if ( i == metadata.length -1 ){
						out.write(escape(metadata[i]) );
					} else {
						out.write(escape(metadata[i]) +",");
					}
				}
				out.write("\" ");
			}
			out.write("/>\n");
		}
		
		// It organizes the animation sentence as a container. 
		public void addTile( String href , Rectangle2D.Double geoRect ) throws Exception{
			Rectangle2D.Double sr = geo2SvgRect(geoRect);
			out.write(" <animation x=\"" + sr.x + "\" y=\"" + sr.y + "\" width=\"" + sr.width + "\" height=\"" + sr.height + "\" xlink:href=\"" + href + "\" />\n");
		}
		
		// Geographic-coordinate -> SVG coordinate transformation (for Point)
		Point2D.Double geo2SvgPoint( Point2D.Double geoPoint ){
			return ( new Point2D.Double( geoPoint.x * a , geoPoint.y * d ) );
		}
		
		// Geographic-coordinate -> SVG coordinate transformation (for Rect.) 
		Rectangle2D.Double geo2SvgRect( Rectangle2D.Double geoRect ){
			return ( 
				new Rectangle2D.Double(
					Math.min( geoRect.x * a , (geoRect.x + geoRect.width ) * a ) ,
					Math.min( geoRect.y * d , (geoRect.y + geoRect.height) * d ) ,
					Math.abs( geoRect.width  * a ) ,
					Math.abs( geoRect.height * d ) 
				)
			);
		}
		
		// The character escape for XML 
		public String escape(String str) {
			if (str == null) {
				return null;
			}
			StringBuffer escape = new StringBuffer();
			for (int i = 0; i < str.length(); i++) {
				char c = str.charAt(i);
				if (c == '"') {
					escape.append("&quot;");
				} else if (c == '\'') {
					escape.append("&apos;");
				} else if (c == '<') {
					escape.append("&lt;");
				} else if (c == '>') {
					escape.append("&gt;");
				} else if (c == '&') {
					escape.append("&amp;");
				} else {
					escape.append(c);
				}
			}
			return escape.toString();
		}
	}
	
}