001/* 002 * $URL: svn://svn.webarts.bc.ca/open/trunk/projects/WebARTS/ca/bc/webarts/tools/KmlToGeoJSON.java $ 003 * $Author: tgutwin $ 004 * $Revision: 1052 $ 005 * $Date: 2016-03-16 17:05:08 -0700 (Wed, 16 Mar 2016) $ 006*/ 007/* 008 * 009 * Written by Tom Gutwin - WebARTS Design. 010 * Copyright (C) 2016 WebARTS Design, North Vancouver Canada 011 * http://www.webarts.ca 012 * 013 * This program is free software; you can redistribute it and/or modify 014 * it under the terms of the GNU General Public License as published by 015 * the Free Software Foundation; version 3 of the License, or 016 * (at your option) any later version. 017 * 018 * This program is distributed in the hope that it will be useful, 019 * but WITHOUT ANY WARRANTY; without_ even the implied warranty of 020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 021 * GNU General Public License for more details. 022 * 023 * You should have received a copy of the GNU General Public License 024 * along with this program; if not, write to the Free Software 025 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 026*/ 027 028package ca.bc.webarts.tools; 029 030import java.io.BufferedOutputStream; 031import java.io.ByteArrayOutputStream; 032import java.io.File; 033import java.io.FileInputStream; 034import java.io.FileOutputStream; 035import java.io.IOException; 036import java.util.ArrayList; 037 038import org.geotools.data.DataUtilities; 039import org.geotools.data.simple.SimpleFeatureCollection; 040import org.geotools.geojson.feature.FeatureJSON; 041import org.geotools.kml.v22.KMLConfiguration; 042import org.geotools.xml.PullParser; 043 044import org.opengis.feature.simple.SimpleFeature; 045 046 047/** Wraps and automates conversion of kml files to GeoJSON format. 048 * It uses <a href="http://www.geotools.org">GeoTools</a>.<br /><b>NOTE:</b>geoJson does not have a <i>standard</i> 049 * way of including styling, so none of the KML stsyles are transfered with this class. 050 * <ul><li>You could use <a href="http://wiki.openstreetmap.org/wiki/Geojson_CSS">Geojson CSS</a> / 051 * SVG styling into the geojson, but its use in the implementation varies. 052 * <br />see:<ul><li><a href="http://geojson.org/geojson-spec.html">http://geojson.org/geojson-spec.html</a></li> 053 * and 054 * <li><a href="http://www.w3.org/TR/SVG/styling.html">http://www.w3.org/TR/SVG/styling.html</a></li></ul> 055 * </li> 056 * <li>another option, is using MapBox styling.</li> 057 * </ul> 058 **/ 059public class KmlToGeoJSON extends Object 060{ 061 /** the full classname as a String for convenience. **/ 062 protected static String CLASSNAME = "ca.bc.webarts.tools.KmlToGeoJSON"; // ca.bc.webarts.widgets.Util.getCurrentClassName(); 063 064 /** A holder for this clients System File Separator. */ 065 public final static String SYSTEM_FILE_SEPERATOR = File.separator; 066 067 /** A holder for this clients System line termination separator. */ 068 public final static String SYSTEM_LINE_SEPERATOR = 069 System.getProperty("line.separator"); 070 071 /** Empty constructor. **/ 072 public KmlToGeoJSON() 073 { } 074 075 076 /** This Outputs a SimpleFeatureCollection to a GeoJSON String (for easier saving and output). 077 * 078 * @param fc is the feature collection to serialize into a String 079 * @return the GeoJSON string of the SimpleFeatureCollection 080 * @throws IOException when 081 **/ 082 public static String featuresToString(SimpleFeatureCollection fc ) throws IOException 083 { 084 ByteArrayOutputStream baOs = new ByteArrayOutputStream(); 085 FeatureJSON fjson = new FeatureJSON(); 086 fjson.writeFeatureCollection(fc, baOs); 087 return baOs.toString(); 088 } 089 090 091 /** 092 * A simple String token replacement routine. Replaces all occurences of the 093 * token parameter with the replacement value in the passed in sentence 094 * parameter. 095 * 096 * @param sentence The String to perform the token replacement on 097 * @param token the token String to seartch for and replace 098 * @param replacement the tokens replacement value 099 * @return The new token replaced string 100 */ 101 public static String tokenReplace(String sentence, 102 String token, 103 String replacement) 104 { 105 String retVal = ""; 106 /* 107 int a = 0; 108 while ((a = sentence.indexOf(token)) > -1) 109 { 110 retVal += sentence.substring(0, a) + replacement; 111 sentence = sentence.substring(a + token.length()); 112 113 } 114 115 retVal += sentence; 116 */ 117 retVal = sentence.replace(token,replacement); 118 return retVal; 119 } 120 121 122 /** Very basic formatter to make a JSON string more readable. 123 **/ 124 public static String formatJsonString(String inStr) 125 { 126 String retVal = "\n"+inStr; 127 System.out.println("\n\nPretty Formatting the JSONString"); 128 retVal=tokenReplace(retVal,"{","{"+SYSTEM_LINE_SEPERATOR+" "); 129 retVal=tokenReplace(retVal,",",","+SYSTEM_LINE_SEPERATOR); 130 retVal=tokenReplace(retVal,"}",SYSTEM_LINE_SEPERATOR+"}"); 131 System.out.println(retVal); 132 133 return retVal; 134 } 135 136 137 /** Indents/spaces out an JSON result. **/ 138 public static StringBuilder jsonIndenter(String s) 139 { 140 StringBuilder retVal = new StringBuilder(""); 141 if (s!=null) 142 { 143 int indent = 0; 144 boolean opening = false; 145 boolean closing = false; 146 boolean lf = false; 147 char [] sbChar = s.toCharArray(); 148 149 for (int i=0; i< sbChar.length;i++) 150 { 151 opening = false; 152 closing = false; 153 lf = false; 154 if (sbChar[i]=='}' ) // closing 155 { 156 retVal.append("\n"); 157 for (int j=0;j<indent-1;j++) retVal.append(" "); 158 retVal.append(sbChar[i]); 159 indent--; //indent--; 160 if(i+1<sbChar.length && sbChar[i+1]!=',') 161 { 162 if(sbChar[i+1]!=']') indent--; 163 retVal.append("\n"); 164 for (int j=0;j<((sbChar[i+1]!=']')?indent:indent-1);j++) retVal.append(" "); 165 } 166 } 167 else if(sbChar[i]=='[') // opening 168 { 169 indent++; 170 retVal.append("\n"); 171 for (int j=0;j<indent-1;j++) retVal.append(" "); 172 retVal.append(sbChar[i]); 173 } 174 else if(sbChar[i]==']') // closing 175 { 176 indent--; 177 retVal.append(sbChar[i]); 178 } 179 else if(sbChar[i]=='{') // opening 180 { 181 indent++; 182 retVal.append("\n"); 183 for (int j=0;j<indent-1;j++) retVal.append(" "); 184 retVal.append(sbChar[i]); 185 retVal.append("\n"); 186 for (int j=0;j<indent;j++) retVal.append(" "); 187 } 188 else if(sbChar[i]==','&&!Character.isDigit(sbChar[i-1])) // continue attributes 189 { 190 //indent++; 191 retVal.append(sbChar[i]); 192 retVal.append("\n"); 193 for (int j=0;j<indent;j++) retVal.append(" "); 194 } 195 else if (sbChar[i]!='\n') 196 { 197 retVal.append(sbChar[i]); 198 } 199 } 200 } 201 return retVal; 202 } 203 204 205 /** Indents/spaces out an XML result. **/ 206 public static StringBuilder responseXMLIndenter(StringBuilder sb) 207 { 208 StringBuilder retVal = new StringBuilder(""); 209 if (sb!=null) 210 { 211 int indent = -1; 212 boolean opening = false; 213 boolean closing = false; 214 boolean lf = false; 215 char [] sbChar = sb.toString().toCharArray(); 216 217 for (int i=0; i< sbChar.length;i++) 218 { 219 opening = false; 220 closing = false; 221 lf = false; 222 if ((sbChar[i]=='<'&&sbChar[i+1]=='/') ) 223 { 224 retVal.append("\n"); 225 for (int j=0;j<indent;j++) retVal.append(" "); 226 retVal.append(sbChar[i]); 227 indent--; //indent--; 228 } 229 else if(sbChar[i]=='<') 230 { 231 indent++; 232 retVal.append("\n"); 233 for (int j=0;j<indent;j++) retVal.append(" "); 234 retVal.append(sbChar[i]); 235 } 236 else if ((sbChar[i]=='/'&&sbChar[i+1]=='>') ) 237 { 238 indent--; //indent--; 239 retVal.append(sbChar[i]); 240 } 241 else if (sbChar[i]=='>') 242 { 243 retVal.append(sbChar[i]); 244 //for (int j=0;j<indent;j++) retVal.append(" "); 245 } 246 else if (sbChar[i]!='\n') 247 { 248 retVal.append(sbChar[i]); 249 } 250 } 251 } 252 return retVal; 253 } 254 255 256 /** Takes a kml file and converts its geo features to a GeoTools SimpleFeatureCollection. Use the 257 * {@link #featuresToString(SimpleFeatureCollection) featuresToString} 258 * method to get a String version of it. 259 * 260 * @param kmlFile is a File to process 261 * @return a SimpleFeatureCollection holding all the geo features 262 **/ 263 public static SimpleFeatureCollection convertKmlFile(File kmlFile) throws Exception 264 { 265 FileInputStream reader = new FileInputStream(kmlFile); 266 PullParser parser = new PullParser(new KMLConfiguration(), reader, SimpleFeature.class); 267 268 ArrayList<SimpleFeature> features = new ArrayList<>(); 269 SimpleFeature simpleFeature = (SimpleFeature) parser.parse(); 270 while (simpleFeature != null) 271 { 272 System.out.println(simpleFeature); 273 features.add(simpleFeature); 274 simpleFeature = (SimpleFeature) parser.parse(); 275 } 276 SimpleFeatureCollection fc = DataUtilities.collection(features); 277 return fc; 278 } 279 280 281 /** Takes a kml file and converts it to a GeoJSON file holding all the Geo Features 282 * using GeoTools SimpleFeatureCollection. <br /> It saves the geojson file in the same 283 * dir with the same filename BUT with a ".geojson" extension.<br /> 284 * It uses {@link #convertKmlFile(File) convertKmlFile} to do the actual file processing. 285 * 286 * @param kmlDirFile is a directory File to recursivly process 287 **/ 288 public static void convertKmlFilesInDir(File kmlDirFile) throws Exception 289 { 290 String fName = ""; 291 String kmlFilenameNOExtension = ""; 292 String geoJsonFilename = ""; 293 if (kmlDirFile!=null && kmlDirFile.isDirectory()) 294 { 295 File[] fList = kmlDirFile.listFiles(); 296 for( int i=0; i< fList.length; i++) 297 { 298 if (fList[i].isDirectory()) 299 { 300 convertKmlFilesInDir(fList[i]); 301 } 302 else 303 { 304 if (fList[i].isFile()) 305 { 306 fName = fList[i].getAbsolutePath(); 307 if(fName.substring(fName.length()-3).equalsIgnoreCase("kml")) 308 { 309 kmlFilenameNOExtension = fName.substring(0,fName.length()-4); 310 geoJsonFilename = kmlFilenameNOExtension + ".geojson"; 311 System.out.println("\n --> "+ fName+" to "+geoJsonFilename); 312 SimpleFeatureCollection fc = convertKmlFile(fList[i]); 313 String geoJsonStr = featuresToString(fc); 314 writeStringToFile(jsonIndenter(geoJsonStr).toString(), geoJsonFilename); 315 } 316 } 317 } 318 } 319 } 320 } 321 322 323 /** 324 * Abstracts the writing of string to a file. 325 * 326 * @param s is the String to writeout 327 * @param fileName is the file name of the file to write the String into 328 * @return if success.. the full pathed filename is returned else null 329 **/ 330 public static String writeStringToFile(String s, String fileName) 331 {return writeStringToFile( s, fileName, false);} 332 333 334 /** 335 * Abstracts the writing of string to a (zip) file (Zip NOT IMPLEMENTED YET). 336 * 337 * @param s is the String to writeout 338 * @param fileName is the file name of the file to write the String into 339 * @param zipCompress boolean fall to compress with zip compression 340 * @return if success.. the full pathed filename is returned else null 341 **/ 342 public static String writeStringToFile(String s, String fileName, boolean zipCompress) 343 { 344 String retVal = fileName; 345 346 try 347 { 348 // FileWriter was not closing the stream 349 /* 350 FileWriter f = new FileWriter(fileName); 351 f.write(s); 352 f.flush(); 353 f.close(); 354 f = null; 355 */ 356 FileOutputStream fos = new FileOutputStream(fileName); 357 byte[] strBytes = s.getBytes(); 358 fos.write(strBytes); 359 fos.flush(); 360 fos.close(); 361 fos = null; 362 System.gc(); // this is required because a bug in Java won't realease 363 } 364 catch (IOException ioEx) 365 { 366 System.out.println("\nERROR Writing file: "+fileName); 367 retVal = null; 368 } 369 370 return retVal; 371 } 372 373 374 /** 375 * Class main commandLine entry method. <br />It takes <b>one</b> commandline parameter: a filename that points at EITHER a kml 376 * <b>file</b> OR a <b>directory</b> to recursivly process the kml files. 377 **/ 378 public static void main(String[] args) 379 { 380 final String methodName = CLASSNAME + ": main()"; 381 KmlToGeoJSON instance = new KmlToGeoJSON(); 382 383 if (args.length>0) 384 { 385 try 386 { 387 String kmlFilenameNOExtension = args[0].substring(0,args[0].length()-4); 388 String geoJsonFilename = kmlFilenameNOExtension + ".geojson"; 389 //System.out.println("\n --> "+ args[0]+" to "+geoJsonFilename); 390 391 File cmdFile = new File(args[0]); 392 if (cmdFile.isFile()) 393 { 394 SimpleFeatureCollection fc = convertKmlFile(cmdFile); 395 String geoJsonStr = featuresToString(fc); 396 writeStringToFile(jsonIndenter(geoJsonStr).toString(), geoJsonFilename); 397 } 398 else if (cmdFile.isDirectory()) 399 { 400 convertKmlFilesInDir(cmdFile); 401 } 402 403 404 } 405 catch (Exception ex) 406 { 407 System.out.println("Puked on the file"); 408 ex.printStackTrace(); 409 } 410 } 411 else 412 System.out.println("Please enter a kml filename."); 413 } 414 415}