// =====================================================================================
// Tilied and Meta-embedded SVG Map exporter example program
//
// Version 1: meta embedded version
//  Copyright (C) 2008 Satoru Takagi @ SVG Map Consortium <http://blog.svg-map.com/>
//
// Version 2: tiling version
//  Copyright (C) 2010 KDDI CORPORATION <http://www.kddi.com/>
//  Programmed by Sagtoru Takagi
//
// Version 2a: support geotools 2.6.5
// 
// Version 3: 7.3.2014 : Refactoring and another tiling method
//  Savie the resources by suppressing unnecessary tile generations
//  Add new tiling method based on the value of the specified attribute as a key
//
// Usage: java Shape2SvgMap (shapefile.shp) [("meta"|"none") [(xPart) (yPart) 
//  [("showTile"|"none") [(stroke-width) [(svgHeight) [(strokeColor) [(fillColor) ["TandL" [(attrbuteNumber)]]]]]]]]
//
// (shapefile.shp)     : input file
// ("meta"|"none")     : embed metadata to svg tiles
// (xPart) (yPart)     : The number of division for tiling
// ("showTile"|"none") : The flag for the bounding box indication on tiling 
// (stroke-width)      : stroke-width ( non scaling )
// (svgHeight)         : Height on SVG user coordinates corresponds shapefile's bounding box
// (strokeColor)       : stroke color
// (fillColor)         : fill color
// "TandL"             : generate contents based on tiling and layering module for SVG1.2
// (attrbuteNumber)    : attrbuteNumber for tiling method based on the value of the specified attribute
// 
// 
// The operation check of this program was carried out with geoTools2.7.5 and windows7.
// =====================================================================================
//
// 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.io.IOException;
import java.net.URL;
import java.awt.geom.*;
import java.util.*;
import java.text.NumberFormat;


import org.geotools.data.*;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.feature.*;
import org.geotools.geometry.jts.GeometryClipper;

import org.opengis.feature.simple.*;

import com.vividsolutions.jts.geom.*;
import com.vividsolutions.jts.operation.overlay.*;

public class Shape2SvgMap {
	
	// default parameters
	double  boundHeight = 800.0; // SVG Height
	String fillColor = "green";
	String strokeColor = "black";
	double strokeWidth = 1;
	String CRS = "http://purl.org/crs/84"; // CRS:84 (WGS84 Long-Lat)
	boolean embedMeta = false;
	int xPart = 1;
	int yPart = 1;
	boolean TandL = false;
	
	int layerAttr = -1;
	
	NumberFormat fmt;
	
	boolean showTileBounds = false;
	
	// common variables
	double  projCenter;
	double[] affineParam = new double[6];
	
	SimpleFeatureType readFT;
	Envelope srcEnv;
	
	public static void main(String[] args) {
		Shape2SvgMap s2sm = new Shape2SvgMap();
		try {
			
			if ( args.length == 0 ){
				System.out.println("Usage: java Shape2SvgMap (shapefile.shp) [(\"meta\"|\"none\") [(xPart) (yPart) ");
				System.out.println("  [(\"showTile\"|\"none\") [(stroke-width) [(svgHeight) [(strokeColor) [(fillColor) [\"TandL\" [(attrbuteNumber)]]]]]]]]");
				return;
			}
			
			// Shape file
			URL shapeURL = (new File(args[0])).toURL();
			
			if ( args.length > 1 && args[1].toLowerCase().indexOf("meta") >= 0 ){
				s2sm.embedMeta = true;
			}
			
			if ( args.length > 3 ){
				s2sm.xPart = Integer.parseInt(args[2]);
				s2sm.yPart = Integer.parseInt(args[3]);
			}
			
			if ( args.length > 4 && args[4].indexOf("show") >= 0 ){
				s2sm.showTileBounds = true;
			}
			
			if ( args.length > 5 ){
				s2sm.strokeWidth = Double.parseDouble(args[5]);
			}
			
			if ( args.length > 6 ){
				s2sm.boundHeight = Double.parseDouble(args[6]);
			}
			
			if ( args.length > 7 ){
				s2sm.strokeColor = args[7];
			}
			
			if ( args.length > 8 ){
				s2sm.fillColor = args[8];
			}
			
			if ( args.length > 9 && args[9].toLowerCase().indexOf("tandl") >= 0 ){
				s2sm.TandL = true;
			}
			
			if ( args.length > 10 ){
				s2sm.layerAttr = Integer.parseInt(args[10]);
			}
			
			s2sm.convert( shapeURL , args[0].substring(0,args[0].lastIndexOf(".")) );
			
		} catch ( Exception e ){
			e.printStackTrace();
		}
	}
	
	String metaString = ""; // for storing each element's metadata
	public void convert( URL shapeURL , String outputFileName )  throws Exception {
		// set NumberFormatter
		fmt = NumberFormat.getNumberInstance();
		fmt.setMaximumFractionDigits(6);
		fmt.setGroupingUsed(false);
		
		// Initialize shape file reader
		ShapefileDataStore readStore = new ShapefileDataStore(shapeURL);
		FeatureSource<SimpleFeatureType, SimpleFeature> source = readStore.getFeatureSource();
		readFT = source.getSchema();
		
		// print out shaoefie's schema
		printSchema( readFT );
		
		// get shapefile's bounding box
		srcEnv = source.getBounds();
		
		// Build GEO to SVG transform matrix
		buildGeo2SVG( );
		
		// get FeatureCollection
		FeatureCollection fsShape = source.getFeatures();
		
		System.out.println("Number of geometries:"+fsShape.size() + "  .:100"); // comment
		
		// prepare HashMaps for storeing writer for SvgTiles and its bouding boxs
		HashMap<String,Writer> svgWriterMap = new HashMap<String,Writer>();
		HashMap<String,Envelope> svgEnvelopeMap = new HashMap<String,Envelope>();
		
		// Source data body
		FeatureIterator<SimpleFeature> reader = fsShape.features();
		SimpleFeature oneFeature;
		Geometry oneGeom;
		
		// convert geometries to svg tiles
		int count = 0;
		while (reader.hasNext()) {
			// progress dispay
			if ( count % 100 == 0){
				System.out.print(".");
			}
			++ count;
			
			oneFeature = reader.next();
			
			// Store each element's metadata
			if ( embedMeta ){
				metaString = getMetaString( oneFeature );
			}
			isChild = false;
			
			// Get attribute for attribute value based tling
			String partAttrVal = null;
			if ( layerAttr >=0 ){
				partAttrVal = oneFeature.getAttribute(layerAttr).toString();
			}
			
			// do actual conversion woth tiling processing
			oneGeom = (Geometry)oneFeature.getDefaultGeometry();
			convOneGeom2SvgTileImages( oneGeom, svgWriterMap, svgEnvelopeMap, xPart, yPart, srcEnv, partAttrVal, outputFileName);
		}
		reader.close();
		
		// close writing of each svg tiles
		closeTiles( svgWriterMap );
		
		// build boundary content
 		FileOutputStream boundaryFile = new FileOutputStream( getTilePath (outputFileName ,"_tileBbox" , false) );
		Writer boundaryWriter = new BufferedWriter(new OutputStreamWriter( boundaryFile , "UTF-8" ));
		buildBoundary( boundaryWriter , svgWriterMap, svgEnvelopeMap, srcEnv , xPart , yPart , outputFileName );
		boundaryWriter.close();
		
		// build svg container
		FileOutputStream containerFile = new FileOutputStream( outputFileName + ".svg" );
		Writer containerWriter = new BufferedWriter(new OutputStreamWriter( containerFile , "UTF-8" ));
		buildContainer( containerWriter , svgWriterMap, svgEnvelopeMap, srcEnv , xPart , yPart , outputFileName );
		containerWriter.close();
		
	}
	
	public void convOneGeom2SvgTileImages( Geometry oneGeom , HashMap<String,Writer> outList ,  HashMap<String,Envelope> envelopeMap, int xPart , int yPart , Envelope dataArea , String partAttr , String outputFileName ) throws Exception {
		// get Tile Size
		double tileW = ( dataArea.getMaxX() - dataArea.getMinX() ) / xPart;
		double tileH = ( dataArea.getMaxY() - dataArea.getMinY() ) / yPart;
		// tiling loop ( excluding attribute based tiling)
		for ( int i = 0 ; i < yPart ; i++ ){
			for ( int j = 0 ; j < xPart ; j++ ){
				// Envelope of tile
				Envelope clipBox = getTileEnvelope( j , i , dataArea , tileW , tileH );
				
				// clip
				GeometryClipper gc = new GeometryClipper( clipBox );
				Geometry clipResult = gc.clip( oneGeom , false );
				
				// If polygons or linestrings are clipped into points then suppress drawing.
				if ( clipResult != null && ! clipResult.isEmpty() &&
				! (! ( oneGeom instanceof Point || oneGeom instanceof MultiPoint ) && 
				( clipResult instanceof Point || clipResult instanceof MultiPoint ) ) ){
					Envelope oneEnv = clipResult.getEnvelopeInternal();
					// get each svg tile's writer
					Writer writer = getWriter( outList , envelopeMap , j , i , partAttr , clipBox , oneEnv , outputFileName );
					// convert one clipped geometry 
					parseGeometry( clipResult , writer );
				}
			}
		}
	}
	
	Envelope getTileEnvelope(int xp , int yp , Envelope dataArea , double tileW , double tileH ){
		Envelope clipBox = new Envelope(
			dataArea.getMinX() + tileW * xp ,
			dataArea.getMinX() + tileW * ( xp + 1 ) ,
			dataArea.getMinY() + tileH * yp,
			dataArea.getMinY() + tileH * ( yp + 1 ) );
		return ( clipBox );
	}
	
	public Writer getWriter( HashMap<String,Writer> outList , HashMap<String,Envelope> envelopeMap , int xp , int yp , String partAttr , Envelope clipBox , Envelope oneEnv , String outputFileName )throws Exception{
		String key = getTileName( xp , yp , partAttr , outputFileName );
		Writer ans = null;
		
		// If applicable tile has not been generated yet, then generate newly.
		// Otherwise, return the existing svg tile writer. 
		if ( outList.containsKey(key) ){
			ans = outList.get(key);
			if ( partAttr != null ){
				Envelope sumEnv = envelopeMap.get(key);
				sumEnv.expandToInclude( oneEnv );
			}
		} else {
			ans = getSvgMapWriter( key , clipBox , outputFileName );
			outList.put(key,ans);
			
			if ( partAttr == null ){
				envelopeMap.put( key,clipBox );
			} else {
				envelopeMap.put( key,oneEnv );
			}
		}
		return ( ans );
	}
	
	String getTileName( int tileX , int tileY , String tileLayer , String outputFileName ){
		if ( tileLayer != null ){
			return ( tileLayer + "_" + tileX + "_" + tileY  );
		} else {
			return ( tileX + "_" + tileY  );
		}
	}
	
	public Writer getSvgMapWriter( String key , Envelope clipBox, String outputFileName )throws Exception{
		// build writer
		FileOutputStream osFile = new FileOutputStream( getTilePath( outputFileName , key , false) );
		Writer fos = new BufferedWriter(new OutputStreamWriter( osFile , "UTF-8" ));
		
		// Make a ViewBox of SVG Map tile
		Rectangle2D.Double viewBox = getSvgViewBox( clipBox );
		
		// Write SVG Header part
		writeSvgHeader( viewBox , fos );
		
		// Write icon defs
		writeIconDef( viewBox.width / 300 , fos );
		
		// set default style using 'g' element
		fos.write("<g fill-rule=\"evenodd\" stroke-width=\"" + strokeWidth + "\" stroke-linejoin=\"bevel\" vector-effect=\"non-scaling-stroke\">\n");
		
		return ( fos );
	}
	
	public String getTilePath (String outputFileName ,String key , boolean relative){
		String opf = outputFileName;
		if ( relative ){
			opf = outputFileName.substring(outputFileName.lastIndexOf(File.separator)+1);
		}
		return ( opf + "_" + key + ".svg" );
	}
	
	public void closeTiles( HashMap<String,Writer> outList )throws Exception{
		Iterator it = outList.keySet().iterator();
		while (it.hasNext()){
			Object o = it.next();
			Writer w = outList.get(o);
			w.write("</g>\n");
			w.write("</svg>\n");
			w.close();
		}
	}
	
	boolean isChild;
	private void parseGeometry(Geometry geom , Writer out ) throws Exception {
		Coordinate[] coord;
		Coordinate oneCrd = new Coordinate();
		// Convert a geometry into a corresponding SVG element. 
		if (geom instanceof Polygon ){
			out.write("<path ");
			if ( ! isChild ){
				out.write(metaString);
			}
			out.write("fill=\"" + fillColor + "\" ");
			if ( strokeColor.indexOf("none")<0){
				out.write("stroke=\"" + strokeColor + "\" ");
			}
			out.write("d=\"M");
			coord = (((Polygon)geom).getExteriorRing()).getCoordinates();
			for ( int i = 0 ; i < coord.length ; i++ ){
				if ( i!= 0 ){
					out.write( "L" );
				}
				oneCrd = transform2D(coord[i] );
				out.write( fmt.format(oneCrd.x) + "," + fmt.format(oneCrd.y) + " " );
			}
			out.write ("Z ");
			
			for ( int j = 0 ; j < ((Polygon)geom).getNumInteriorRing() ; j++ ){
				coord = (((Polygon)geom).getInteriorRingN(j)).getCoordinates();
				for ( int i = 0 ; i < coord.length ; i++ ){
					if ( i!= 0 ){
						out.write( "L" );
					} else {
						out.write( "M" );
					}
					oneCrd = transform2D(coord[i] );
					out.write( fmt.format(oneCrd.x) + "," + fmt.format(oneCrd.y) + " " );
				}
				out.write ("Z ");
			}
			
			out.write ("\"/>\n");
		} else if (geom instanceof LineString ){
			out.write("<polyline ");
			if ( ! isChild ){
				out.write(metaString);
			}
			out.write("fill=\"none\" stroke=\"" + strokeColor + "\" points=\"");
			coord = ((LineString)geom).getCoordinates();
			for ( int i = 0 ; i < coord.length ; i++ ){
				oneCrd = transform2D(coord[i] );
				out.write( fmt.format(oneCrd.x) + "," + fmt.format(oneCrd.y) + " " );
			}
			out.write ("\"/>\n");
		} else if (geom instanceof Point ){
			oneCrd = transform2D(((Point)geom).getCoordinate() );
			out.write("<use ");
			if ( ! isChild ){
				out.write(metaString);
			}
			out.write("xlink:href=\"#p0\" fill=\"" + fillColor + "\"");
			out.write(" x=\"" + fmt.format(oneCrd.x) + "\"" + " y=\"" + fmt.format(oneCrd.y) + "\"/>\n");
		} else if (geom instanceof GeometryCollection ){
			//   out.write("<g ");
			//   if ( ! isChild ){ out.write(metaString);}
			//   out.write(">\n");
			//   isChild = true;
			for ( int j = 0 ; j < ((GeometryCollection)geom).getNumGeometries() ; j++){
				Geometry childGeom = ((GeometryCollection)geom).getGeometryN(j);
				parseGeometry(childGeom , out);
			}
			//   out.write("</g>\n");
		} else if (geom instanceof Geometry ){
			out.write("<!-- Type: Other Geometry...." + geom + "-->\n");
		} else if (geom instanceof Object){
			out.write("<!-- Type: Other Object...." + geom + "-->\n");
		}
	}
	
	public void buildContainer( Writer out , HashMap<String,Writer> tileWriter,HashMap<String,Envelope> tileEnvelope , Envelope dataArea , int xPart , int yPart , String outputFileName ) throws Exception {
		buildContainerOrBoundary( out , tileWriter, tileEnvelope , dataArea , xPart , yPart , outputFileName , false );
	}
		
	public void buildBoundary( Writer out , HashMap<String,Writer> tileWriter,HashMap<String,Envelope> tileEnvelope , Envelope dataArea , int xPart , int yPart , String outputFileName ) throws Exception {
		buildContainerOrBoundary( out , tileWriter, tileEnvelope , dataArea , xPart , yPart , outputFileName , true );
	}
		
	public void buildContainerOrBoundary( Writer out , HashMap<String,Writer> tileWriter,HashMap<String,Envelope> tileEnvelope , Envelope dataArea , int xPart , int yPart , String outputFileName , boolean boundaryContent ) throws Exception {
		// Set initial viewBox to source data area
		Rectangle2D.Double shapeBBox = getSvgViewBox( srcEnv );
		
		double tileW = ( dataArea.getMaxX() - dataArea.getMinX() ) / xPart;
		double tileH = ( dataArea.getMaxY() - dataArea.getMinY() ) / yPart;
		
		Iterator it = tileWriter.keySet().iterator();
		
		boolean isFirst = true;
		while (it.hasNext()){
			String key = (String)it.next();
			Envelope tileBox = tileEnvelope.get(key);
			Rectangle2D.Double imgArea = getSvgViewBox( tileBox );
			// When adding reference of the first tile, set a SVG root tag and describe the area of the first tile as that viewBox. 
			if ( isFirst ){
				Rectangle2D.Double viewBox = null;
				if ( !boundaryContent ){
					viewBox = imgArea;
				} else {
					viewBox = shapeBBox;
				}
				writeSvgHeader( viewBox , out );
				out.write("<!-- bbox of shapefile:" + shapeBBox.x + "," + shapeBBox.y + "," + shapeBBox.width + "," + shapeBBox.height + " -->\n");
				isFirst = false;
			}
			if ( boundaryContent ){
				// boundaryContent mode: write each tile's boundary rectangle element
				out.write(" <rect x=\"" + imgArea.x + "\" y=\"" + imgArea.y + "\" width=\"" + imgArea.width + "\" height=\"" + imgArea.height + "\" fill=\"none\" stroke-width=\"" + strokeWidth + "\" stroke=\"red\" />\n");
			} else {
				// container mode: write reference for each tile
				if ( TandL){
					// for SVG 1.2 T&L spec
					out.write(" <animation opacity=\"0.5\" xlink:href=\"" + getTilePath( outputFileName , key , true) + "\" x=\"" + fmt.format(imgArea.x) + "\" y=\"" + fmt.format(imgArea.y) + "\" width=\"" + fmt.format(imgArea.width) + "\" height=\"" + fmt.format(imgArea.height) + "\" />\n");
				} else {
					// for SVG Map 1.0 spec
					out.write(" <image opacity=\"0.5\" xlink:href=\"" + getTilePath( outputFileName , key , true) + "\" x=\"" + fmt.format(imgArea.x) + "\" y=\"" + fmt.format(imgArea.y) + "\" width=\"" + fmt.format(imgArea.width) + "\" height=\"" + fmt.format(imgArea.height) + "\" />\n");
				}
			}
		}
		
		if ( showTileBounds && !boundaryContent ){
			if ( TandL){
					out.write(" <animation opacity=\"0.5\" xlink:href=\"" + getTilePath( outputFileName , "_tileBbox" , true) + "\" x=\"" + fmt.format(shapeBBox.x) + "\" y=\"" + fmt.format(shapeBBox.y) + "\" width=\"" + fmt.format(shapeBBox.width) + "\" height=\"" + fmt.format(shapeBBox.height) + "\" />\n");
			} else {
				out.write(" <image opacity=\"0.5\" xlink:href=\"" + getTilePath( outputFileName , "_tileBbox" , true) + "\" x=\"" + fmt.format(shapeBBox.x) + "\" y=\"" + fmt.format(shapeBBox.y) + "\" width=\"" + fmt.format(shapeBBox.width) + "\" height=\"" + fmt.format(shapeBBox.height) + "\" />\n");
			}
		}
		
		out.write("</svg>\n");
	}
	
	public void writeSvgHeader( Rectangle2D.Double viewBox , Writer out ) throws Exception {
		// Start SVG Map output
		// SVG header etc.
		out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		out.write("<svg  xmlns=\"http://www.w3.org/2000/svg\" ");
		out.write("xmlns:xlink=\"http://www.w3.org/1999/xlink\" ");
		if ( useMeta2 && embedMeta ){
			out.write("property=\"");
			for ( int i = 0 ; i < readFT.getAttributeCount() ; i++){
				if (readFT.getDescriptor(i).getLocalName().indexOf("the_geom")==-1){
					out.write( readFT.getDescriptor(i).getLocalName());
					if ( i < readFT.getAttributeCount() -1 ){
						out.write(",");
					} else {
						out.write( "\" ");
					}
				}
			}
		} else {
			out.write("xmlns:lm=\"http://www.svg-map.org/svgmap/localmetadata/\" ");
		}
		out.write("viewBox=\"" + viewBox.x + " " + viewBox.y + " " + viewBox.width + " " + viewBox.height + "\" >\n");
		
		
		// for SVG1.2  T&L spec
		if ( TandL ){
			out.write("<globalCoordinateSystem srsName=\"" + CRS + "\" ");
			out.write("transform=\"matrix(" + affineParam[0] + "," + affineParam[1] + "," + affineParam[2]
			+ "," + affineParam[3] + "," + affineParam[4] + "," + affineParam[5] +")\" />\n");
		} else {
			// metadata for SVG Map
			out.write("<metadata>\n");
			// CRS metadata
			out.write(" <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" ");
			out.write("xmlns:crs=\"http://opengis.org/xmldtds/transformations.dtd\" xmlns:svg=\"http://www.w3.org/2000/svg\">\n");
			out.write("  <rdf:Description>\n");
			out.write("   <crs:CoordinateReferenceSystem rdf:resource=\"" + CRS + "\"  ");
			out.write("svg:transform=\"matrix(" + affineParam[0] + "," + affineParam[1] + "," + affineParam[2]
			+ "," + affineParam[3] + "," + affineParam[4] + "," + affineParam[5] +")\" />\n");
			out.write("  </rdf:Description>\n");
			out.write(" </rdf:RDF>\n");
			out.write("</metadata>\n");
		}
	}
	
	public void writeIconDef( double size , Writer out  ) throws Exception {
		// Icon for Point
		out.write("<defs>\n");
		out.write(" <g id=\"p0\" >\n"); 
		out.write("  <circle cx=\"0.0\" cy=\"0.0\" r=\"" + size + "\" stroke=\"none\" />\n");
		out.write(" </g>\n");
		out.write("</defs>\n");
	}
	
	public Rectangle2D.Double getSvgViewBox( Envelope clipBB ){
		Rectangle2D.Double viewBox = new Rectangle2D.Double();
		viewBox.width  = Math.abs( ( clipBB.getMaxX() - clipBB.getMinX() ) * affineParam[0] ); 
		viewBox.height = Math.abs( ( clipBB.getMaxY() - clipBB.getMinY() ) * affineParam[3] ); 
		viewBox.x = affineParam[0] * clipBB.getMinX() + affineParam[4];
		viewBox.y = affineParam[3] * clipBB.getMaxY() + affineParam[5];
		return ( viewBox );
	}
	
	public void buildGeo2SVG( ){
		// Make affine parameters for CRS metadata of SVG Map
		// Equirectangular Projection (Standard parallel = Center of map)
		//
		// SVG_X = a * GEO_X + c * GEO_Y + e
		// SVG_Y = b * GEO_X + d * GEO_Y + f
		affineParam[1] = 0.0; // b
		affineParam[2] = 0.0; // c
		
		affineParam[3] = - boundHeight / ( srcEnv.getMaxY() - srcEnv.getMinY() ); // d
		
		projCenter = ( srcEnv.getMaxY() + srcEnv.getMinY() ) / 2.0;
		affineParam[0] = - affineParam[3] * Math.cos( projCenter * Math.PI / 180.0 ); // a
		
		affineParam[4] = -affineParam[0] * srcEnv.getMinX(); // e
		affineParam[5] = -affineParam[3] * srcEnv.getMaxY(); // f
	}
	
	
	static boolean useMeta2 = true;
	private String getMetaString( SimpleFeature oneFeature )throws Exception{
		StringBuffer metaString = new StringBuffer();
		if ( useMeta2 ){
			metaString.append("content=\"");
			for ( int i = 0 ; i < readFT.getAttributeCount() ; i++){
				if (oneFeature.getAttribute(i) instanceof Geometry == false ){
					String oneAttrValue = oneFeature.getAttribute(i).toString();
					metaString.append( (new String(oneAttrValue.getBytes("iso-8859-1"),"Windows-31J")).trim() );
					if ( i < readFT.getAttributeCount() -1){
						metaString.append( ",");
					} else {
						metaString.append( "\" ");
					}
				}
			}
		} else {
			for ( int i = 0 ; i < readFT.getAttributeCount() ; i++){
				if (oneFeature.getAttribute(i) instanceof Geometry == false ){
					metaString.append("lm:");
	//				metaString.append( readFT.getAttributeType(i).getName());
					metaString.append( readFT.getDescriptor(i).getLocalName());
					metaString.append("=\"");
					metaString.append( oneFeature.getAttribute(i));
					metaString.append( "\" ");
				}
			}
		}
		return ( metaString.toString() );
	}
	
	private  Coordinate transform2D( Coordinate inCrd ){
	  Coordinate outCrd = new Coordinate(
	   affineParam[0] * inCrd.x + affineParam[2] * inCrd.y + affineParam[4] ,
	   affineParam[1] * inCrd.x + affineParam[3] * inCrd.y + affineParam[5] );
	  return (outCrd);
	}
	
	public void printSchema ( SimpleFeatureType readFT ){
		for ( int i = 0 ; i < readFT.getAttributeCount() ; i++){
			System.out.println( "attrNo:"+i + "  attrName:" + readFT.getDescriptor(i).getLocalName() + "  attrType:" + readFT.getDescriptor(i).getType());
		}
	}
}

