package org.osmdroid.bonuspack.kml;
import android.os.Environment;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import com.google.gson.stream.JsonWriter;
import org.osmdroid.bonuspack.utils.BonusPackHelper;
import org.osmdroid.bonuspack.utils.HttpConnection;
import org.osmdroid.util.GeoPoint;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
/**
* Object handling a whole KML document.
* This is the entry point to read, handle and save KML content. <br>
* Features are stored in the kmlRoot attribute, which is a KmlFolder. <br>
* Also contains the Shared Styles, referenced in Features using a styleId. <br>
*
* Supports the following KML Geometry: Point, LineString, Polygon and MultiGeometry. <br>
* Supports KML Document and Folder hierarchy. <br>
* Supports NetworkLink. <br>
* Supports GroundOverlay. <br>
* Supports LineStyle, PolyStyle and IconStyle - shared and inline. <br>
* Supports colorMode: normal, random<br>
* Supports ExtendedData inside Features, with support for Data elements and SimpleData elements.
* In all cases, values are stored as Java String, there is no handling of Schema definition. <br>
*
* @see KmlFeature
* @see Style
*
* @author M.Kergall
*/
public class KmlDocument implements Parcelable {
/** the root of KML features contained in this document */
public KmlFolder mKmlRoot;
/** Shared Styles in this document. String key is the styleId. */
protected HashMap<String, StyleSelector> mStyles;
protected int mMaxStyleId;
/** Local File that has been loaded. null if this is not a local file. */
protected File mLocalFile;
/** default constructor, with the kmlRoot as an empty Folder */
public KmlDocument(){
mStyles = new HashMap<String, StyleSelector>();
mMaxStyleId = 0;
mKmlRoot = new KmlFolder();
mLocalFile = null;
}
/** @return the Shared Styles */
public HashMap<String, StyleSelector> getStyles(){
return mStyles;
}
/** @return the list of all Shared Styles ids */
public String[] getStylesList(){
Set<String> set = mStyles.keySet();
String[] array = new String[0];
return set.toArray(array);
}
/** @return the Shared Style associated to the styleId, or null if none.
* If this is a StyleMap, returns its "normal" Style (if any). */
public Style getStyle(String styleId){
StyleSelector s = mStyles.get(styleId);
if (s == null)
return null;
else if (s instanceof StyleMap)
return ((StyleMap)s).getNormalStyle(this);
else //if (s instanceof Style)
return (Style)s;
}
/** put the StyleSelector (Style or StyleMap) in the list of Shared Styles, associated to its styleId */
public void putStyle(String styleId, StyleSelector styleSelector){
//Check if maxStyleId needs an update:
try {
int id = Integer.parseInt(styleId);
mMaxStyleId = Math.max(mMaxStyleId, id);
} catch (NumberFormatException e){
//styleId was not a number: nothing to do
}
mStyles.put(styleId, styleSelector);
}
/**
* Add the StyleSelector in the Shared Styles
* @param styleSelector to add
* @return the unique styleId assigned for this style
*/
public String addStyle(StyleSelector styleSelector){
mMaxStyleId++;
String newId = ""+mMaxStyleId;
putStyle(newId, styleSelector);
return newId;
}
/** @return the local File that has been opened (KML, KMZ or GeoJSON), or null if this is not a local file */
public File getLocalFile(){
return mLocalFile;
}
/** similar to GeoPoint.fromInvertedDoubleString, with exceptions handling */
protected static GeoPoint parseKmlCoord(String input){
int end1 = input.indexOf(',');
int end2 = input.indexOf(',', end1+1);
try {
if (end2 == -1){
double lon = Double.parseDouble(input.substring(0, end1));
double lat = Double.parseDouble(input.substring(end1+1, input.length()));
return new GeoPoint(lat, lon);
} else {
double lon = Double.parseDouble(input.substring(0, end1));
double lat = Double.parseDouble(input.substring(end1+1, end2));
double alt = Double.parseDouble(input.substring(end2+1, input.length()));
return new GeoPoint(lat, lon, alt);
}
} catch (NumberFormatException e) {
return null;
} catch (IndexOutOfBoundsException e) {
return null;
}
}
/** KML coordinates are: lon,lat{,alt} tuples separated by separators (space, tab, cr). */
protected static ArrayList<GeoPoint> parseKmlCoordinates(String input){
LinkedList<GeoPoint> tmpCoords = new LinkedList<GeoPoint>();
int i = 0;
int tupleStart = 0;
int length = input.length();
boolean startReadingTuple = false;
while (i<length){
char c = input.charAt(i);
if (c==' '|| c=='\n' || c=='\t'){
if (startReadingTuple){ //just ending coords portion:
String tuple = input.substring(tupleStart, i);
GeoPoint p = parseKmlCoord(tuple);
if (p != null)
tmpCoords.add(p);
startReadingTuple = false;
}
} else { //data
if (!startReadingTuple){ //just ending space portion
startReadingTuple = true;
tupleStart = i;
}
if (i == length-1){ //at the end => handle last tuple:
String tuple = input.substring(tupleStart, i+1);
GeoPoint p = parseKmlCoord(tuple);
if (p != null)
tmpCoords.add(p);
}
}
i++;
}
ArrayList<GeoPoint> coordinates = new ArrayList<GeoPoint>(tmpCoords.size());
coordinates.addAll(tmpCoords);
return coordinates;
}
/**
* Parse a KML document from a url, and build the KML structure in kmlRoot.
* If the KML file has a "Document" node, kmlRoot will be a Folder "mapping" to this Document.
* In all other cases, kmlRoot will be a Folder, containing the features of the KML file.
* @param url
* @return true if OK, false if any error.
*/
public boolean parseKMLUrl(String url){
Log.d(BonusPackHelper.LOG_TAG, "KmlProvider.parseKMLUrl:"+url);
HttpConnection connection = new HttpConnection();
connection.doGet(url);
InputStream stream = connection.getStream();
boolean ok;
if (stream == null){
ok = false;
} else {
ok = parseKMLStream(stream, null);
}
connection.close();
//Log.d(BonusPackHelper.LOG_TAG, "KmlProvider.parseKMLUrl - end");
return ok;
}
/**
* Get the default path for KML file on Android: on the external storage, in a "kml" directory.
* Creates the directory if necessary.
* @param fileName
* @return full path, as a File, or null if error.
*/
public File getDefaultPathForAndroid(String fileName){
try {
File path = new File(Environment.getExternalStorageDirectory(), "kml");
path.mkdir();
return new File(path.getAbsolutePath(), fileName);
} catch (NullPointerException e){
e.printStackTrace();
return null;
}
}
/**
* Parse a KML document from a file, to build the KML structure.
* @param file full file path
* @return true if OK, false if any error.
* @see #parseKMLUrl
*/
public boolean parseKMLFile(File file){
mLocalFile = file;
Log.d(BonusPackHelper.LOG_TAG, "KmlProvider.parseKMLFile:"+mLocalFile.getAbsolutePath());
InputStream stream;
boolean ok;
try {
stream = new BufferedInputStream(new FileInputStream(mLocalFile));
ok = parseKMLStream(stream, null);
stream.close();
} catch (Exception e){
e.printStackTrace();
ok = false;
}
Log.d(BonusPackHelper.LOG_TAG, "KmlProvider.parseFile - end");
return ok;
}
/**
* Parse a local KMZ document.
* @param file full file path
* @return true if OK.
*/
public boolean parseKMZFile(File file){
mLocalFile = file;
Log.d(BonusPackHelper.LOG_TAG, "KmlProvider.parseKMZFile:"+mLocalFile.getAbsolutePath());
try {
ZipFile kmzFile = new ZipFile(mLocalFile);
String rootFileName = null;
//Iterate in the KMZ to find the first ".kml" file:
Enumeration<? extends ZipEntry> list = kmzFile.entries();
while (list.hasMoreElements() && rootFileName == null){
ZipEntry ze = list.nextElement();
String name = ze.getName();
if (name.endsWith(".kml") && !name.contains("/"))
rootFileName = name;
}
boolean result;
if (rootFileName != null){
ZipEntry rootEntry = kmzFile.getEntry(rootFileName);
InputStream stream = kmzFile.getInputStream(rootEntry);
Log.d(BonusPackHelper.LOG_TAG, "KML root:"+rootFileName);
result = parseKMLStream(stream, kmzFile);
} else {
Log.d(BonusPackHelper.LOG_TAG, "No .kml entry found.");
result = false;
}
kmzFile.close();
return result;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Parse a KML content from an InputStream.
* @param stream the InputStream
* @param kmzContainer KMZ file containing this KML file - or null if not applicable.
* @return true if OK, false if any error.
*/
public boolean parseKMLStream(InputStream stream, ZipFile kmzContainer){
KmlSaxHandler handler = new KmlSaxHandler(mLocalFile, kmzContainer);
boolean ok;
try {
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
parser.parse(stream, handler);
mKmlRoot = handler.mKmlRoot;
ok = true;
} catch (Exception e) {
e.printStackTrace();
ok = false;
}
return ok;
}
// KmlSaxHandler -------------
protected enum KmlKeywords {Document, Folder, NetworkLink, GroundOverlay, Placemark, Point, LineString, gx_Track,
Polygon, innerBoundaryIs, MultiGeometry, Style, StyleMap, LineStyle, PolyStyle, IconStyle, hotSpot,
Data, SimpleData, name, description, visibility, open, coordinates, gx_coord, when, styleUrl, key, color,
colorMode, width, scale, heading, href, north, south, east, west, rotation, LatLonBox, value}
protected static HashMap<String, KmlKeywords> KEYWORDS_DICTIONARY;
static {
KEYWORDS_DICTIONARY = new HashMap<String, KmlKeywords>();
KEYWORDS_DICTIONARY.put("Document", KmlKeywords.Document);
KEYWORDS_DICTIONARY.put("Folder", KmlKeywords.Folder);
KEYWORDS_DICTIONARY.put("NetworkLink", KmlKeywords.NetworkLink);
KEYWORDS_DICTIONARY.put("GroundOverlay", KmlKeywords.GroundOverlay);
KEYWORDS_DICTIONARY.put("Placemark", KmlKeywords.Placemark);
KEYWORDS_DICTIONARY.put("Point", KmlKeywords.Point);
KEYWORDS_DICTIONARY.put("LineString", KmlKeywords.LineString);
KEYWORDS_DICTIONARY.put("gx:Track", KmlKeywords.gx_Track);
KEYWORDS_DICTIONARY.put("Polygon", KmlKeywords.Polygon);
KEYWORDS_DICTIONARY.put("innerBoundaryIs", KmlKeywords.innerBoundaryIs);
KEYWORDS_DICTIONARY.put("MultiGeometry", KmlKeywords.MultiGeometry);
KEYWORDS_DICTIONARY.put("Style", KmlKeywords.Style);
KEYWORDS_DICTIONARY.put("StyleMap", KmlKeywords.StyleMap);
KEYWORDS_DICTIONARY.put("LineStyle", KmlKeywords.LineStyle);
KEYWORDS_DICTIONARY.put("PolyStyle", KmlKeywords.PolyStyle);
KEYWORDS_DICTIONARY.put("IconStyle", KmlKeywords.IconStyle);
KEYWORDS_DICTIONARY.put("hotSpot", KmlKeywords.hotSpot);
KEYWORDS_DICTIONARY.put("Data", KmlKeywords.Data);
KEYWORDS_DICTIONARY.put("SimpleData", KmlKeywords.SimpleData);
KEYWORDS_DICTIONARY.put("name", KmlKeywords.name);
KEYWORDS_DICTIONARY.put("description", KmlKeywords.description);
KEYWORDS_DICTIONARY.put("visibility", KmlKeywords.visibility);
KEYWORDS_DICTIONARY.put("open", KmlKeywords.open);
KEYWORDS_DICTIONARY.put("coordinates", KmlKeywords.coordinates);
KEYWORDS_DICTIONARY.put("gx:coord", KmlKeywords.gx_coord);
KEYWORDS_DICTIONARY.put("when", KmlKeywords.when);
KEYWORDS_DICTIONARY.put("styleUrl", KmlKeywords.styleUrl);
KEYWORDS_DICTIONARY.put("key", KmlKeywords.key);
KEYWORDS_DICTIONARY.put("color", KmlKeywords.color);
KEYWORDS_DICTIONARY.put("colorMode", KmlKeywords.colorMode);
KEYWORDS_DICTIONARY.put("width", KmlKeywords.width);
KEYWORDS_DICTIONARY.put("scale", KmlKeywords.scale);
KEYWORDS_DICTIONARY.put("heading", KmlKeywords.heading);
KEYWORDS_DICTIONARY.put("href", KmlKeywords.href);
KEYWORDS_DICTIONARY.put("north", KmlKeywords.north);
KEYWORDS_DICTIONARY.put("south", KmlKeywords.south);
KEYWORDS_DICTIONARY.put("east", KmlKeywords.east);
KEYWORDS_DICTIONARY.put("west", KmlKeywords.west);
KEYWORDS_DICTIONARY.put("rotation", KmlKeywords.rotation);
KEYWORDS_DICTIONARY.put("LatLonBox", KmlKeywords.LatLonBox);
KEYWORDS_DICTIONARY.put("value", KmlKeywords.value);
}
protected class KmlSaxHandler extends DefaultHandler {
private StringBuilder mStringBuilder = new StringBuilder(1024);
private KmlFeature mKmlCurrentFeature;
private KmlGroundOverlay mKmlCurrentGroundOverlay; //if GroundOverlay, pointer to mKmlCurrentFeature
private ArrayList<KmlFeature> mKmlFeatureStack;
private KmlGeometry mKmlCurrentGeometry;
private ArrayList<KmlGeometry> mKmlGeometryStack;
public KmlFolder mKmlRoot;
Style mCurrentStyle;
String mCurrentStyleId;
StyleMap mCurrentStyleMap; //for StyleSelector: "normal" or "highlight"
String mCurrentStyleKey;
ColorStyle mColorStyle;
String mDataName;
boolean mIsNetworkLink;
boolean mIsInnerBoundary;
File mFile; //to get the path of relative sub-files
ZipFile mKMZFile;
double mNorth, mEast, mSouth, mWest;
public KmlSaxHandler(File file, ZipFile kmzContainer){
mFile = file;
mKMZFile = kmzContainer;
mKmlRoot = new KmlFolder();
mKmlFeatureStack = new ArrayList<KmlFeature>();
mKmlFeatureStack.add(mKmlRoot);
mKmlGeometryStack = new ArrayList<KmlGeometry>();
mIsNetworkLink = false;
mIsInnerBoundary = false;
}
protected void loadNetworkLink(String href, ZipFile kmzContainer){
KmlDocument subDocument = new KmlDocument();
boolean ok;
if (href.startsWith("http://") || href.startsWith("https://") )
ok = subDocument.parseKMLUrl(href);
else if (kmzContainer == null){
File subFile = new File(mFile.getParent()+'/'+href);
ok = subDocument.parseKMLFile(subFile);
} else {
try {
final ZipEntry fileEntry = kmzContainer.getEntry(href);
InputStream stream = kmzContainer.getInputStream(fileEntry);
Log.d(BonusPackHelper.LOG_TAG, "Load NetworkLink:"+href);
ok = subDocument.parseKMLStream(stream, kmzContainer);
} catch (Exception e) {
ok = false;
}
}
if (ok){
//add subDoc root to the current feature, which is -normally- the NetworkLink:
((KmlFolder)mKmlCurrentFeature).add(subDocument.mKmlRoot);
//add all subDocument styles to mStyles:
mStyles.putAll(subDocument.mStyles);
} else {
Log.e(BonusPackHelper.LOG_TAG, "Error reading NetworkLink:"+href);
}
}
/*
public void startElementOld(String uri, String localName, String name,
Attributes attributes) throws SAXException {
if (name.equals("Document")) {
mKmlCurrentFeature = mKmlRoot; //If there is a Document, it will be the root.
mKmlCurrentFeature.mId = attributes.getValue("id");
} else if (name.equals("Folder")) {
mKmlCurrentFeature = new KmlFolder();
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on stack
} else if (name.equals("NetworkLink")) {
mKmlCurrentFeature = new KmlFolder();
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on stack
mIsNetworkLink = true;
} else if (name.equals("GroundOverlay")) {
mKmlCurrentGroundOverlay = new KmlGroundOverlay();
mKmlCurrentFeature = mKmlCurrentGroundOverlay;
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on stack
} else if (name.equals("Placemark")) {
mKmlCurrentFeature = new KmlPlacemark();
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on Feature stack
} else if (name.equals("Point")) {
mKmlCurrentGeometry = new KmlPoint();
mKmlGeometryStack.add(mKmlCurrentGeometry); //push on Geometry stack
} else if (name.equals("LineString")) {
mKmlCurrentGeometry = new KmlLineString();
mKmlGeometryStack.add(mKmlCurrentGeometry);
} else if (name.equals("gx:Track")) {
mKmlCurrentGeometry = new KmlTrack();
mKmlGeometryStack.add(mKmlCurrentGeometry);
} else if (name.equals("Polygon")) {
mKmlCurrentGeometry = new KmlPolygon();
mKmlGeometryStack.add(mKmlCurrentGeometry);
} else if (name.equals("innerBoundaryIs")) {
mIsInnerBoundary = true;
} else if (name.equals("MultiGeometry")) {
mKmlCurrentGeometry = new KmlMultiGeometry();
mKmlGeometryStack.add(mKmlCurrentGeometry);
} else if (name.equals("Style")) {
mCurrentStyle = new Style();
mCurrentStyleId = attributes.getValue("id");
} else if (name.equals("StyleMap")) {
mCurrentStyleMap = new StyleMap();
mCurrentStyleId = attributes.getValue("id");
} else if (name.equals("LineStyle")) {
mCurrentStyle.mLineStyle = new LineStyle();
mColorStyle = mCurrentStyle.mLineStyle;
} else if (name.equals("PolyStyle")) {
mCurrentStyle.mPolyStyle = new ColorStyle();
mColorStyle = mCurrentStyle.mPolyStyle;
} else if (name.equals("IconStyle")) {
mCurrentStyle.mIconStyle = new IconStyle();
mColorStyle = mCurrentStyle.mIconStyle;
} else if (name.equals("hotSpot")) {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle){
mCurrentStyle.mIconStyle.mHotSpot = new HotSpot(
Float.parseFloat(attributes.getValue("x")),
Float.parseFloat(attributes.getValue("y")),
attributes.getValue("xunits"),
attributes.getValue("yunits")
);
//if ("fraction".equals(attributes.getValue("xunits")))
// mCurrentStyle.mIconStyle.mHotSpotX = Float.parseFloat(attributes.getValue("x"));
//if ("fraction".equals(attributes.getValue("yunits")))
// mCurrentStyle.mIconStyle.mHotSpotY = Float.parseFloat(attributes.getValue("y"));
}
} else if (name.equals("Data") || name.equals("SimpleData")) {
mDataName = attributes.getValue("name");
}
mStringBuilder.setLength(0);
}
*/
public void startElement(String uri, String localName, String name,
Attributes attributes) throws SAXException {
KmlKeywords element = KEYWORDS_DICTIONARY.get(name);
if (element != null) {
switch (element) {
case Document: {
mKmlCurrentFeature = mKmlRoot; //If there is a Document, it will be the root.
mKmlCurrentFeature.mId = attributes.getValue("id");
break;
}
case Folder: {
mKmlCurrentFeature = new KmlFolder();
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on stack
break;
}
case NetworkLink: {
mKmlCurrentFeature = new KmlFolder();
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on stack
mIsNetworkLink = true;
break;
}
case GroundOverlay: {
mKmlCurrentGroundOverlay = new KmlGroundOverlay();
mKmlCurrentFeature = mKmlCurrentGroundOverlay;
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on stack
break;
}
case Placemark: {
mKmlCurrentFeature = new KmlPlacemark();
mKmlCurrentFeature.mId = attributes.getValue("id");
mKmlFeatureStack.add(mKmlCurrentFeature); //push on Feature stack
break;
}
case Point: {
mKmlCurrentGeometry = new KmlPoint();
mKmlGeometryStack.add(mKmlCurrentGeometry); //push on Geometry stack
break;
}
case LineString: {
mKmlCurrentGeometry = new KmlLineString();
mKmlGeometryStack.add(mKmlCurrentGeometry);
break;
}
case gx_Track: {
mKmlCurrentGeometry = new KmlTrack();
mKmlGeometryStack.add(mKmlCurrentGeometry);
break;
}
case Polygon: {
mKmlCurrentGeometry = new KmlPolygon();
mKmlGeometryStack.add(mKmlCurrentGeometry);
break;
}
case innerBoundaryIs: {
mIsInnerBoundary = true;
break;
}
case MultiGeometry: {
mKmlCurrentGeometry = new KmlMultiGeometry();
mKmlGeometryStack.add(mKmlCurrentGeometry);
break;
}
case Style: {
mCurrentStyle = new Style();
mCurrentStyleId = attributes.getValue("id");
break;
}
case StyleMap: {
mCurrentStyleMap = new StyleMap();
mCurrentStyleId = attributes.getValue("id");
break;
}
case LineStyle: {
mCurrentStyle.mLineStyle = new LineStyle();
mColorStyle = mCurrentStyle.mLineStyle;
break;
}
case PolyStyle: {
mCurrentStyle.mPolyStyle = new ColorStyle();
mColorStyle = mCurrentStyle.mPolyStyle;
break;
}
case IconStyle: {
mCurrentStyle.mIconStyle = new IconStyle();
mColorStyle = mCurrentStyle.mIconStyle;
break;
}
case hotSpot: {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle) {
mCurrentStyle.mIconStyle.mHotSpot = new HotSpot(
Float.parseFloat(attributes.getValue("x")),
Float.parseFloat(attributes.getValue("y")),
attributes.getValue("xunits"),
attributes.getValue("yunits")
);
/*
if ("fraction".equals(attributes.getValue("xunits")))
mCurrentStyle.mIconStyle.mHotSpotX = Float.parseFloat(attributes.getValue("x"));
if ("fraction".equals(attributes.getValue("yunits")))
mCurrentStyle.mIconStyle.mHotSpotY = Float.parseFloat(attributes.getValue("y"));
*/
}
break;
}
case Data:
case SimpleData: {
mDataName = attributes.getValue("name");
break;
}
default:
break;
} //switch
} //if element not null
mStringBuilder.setLength(0);
}
public @Override void characters(char[] ch, int start, int length)
throws SAXException {
mStringBuilder.append(ch, start, length);
}
/*
public void endElementOld(String uri, String localName, String name)
throws SAXException {
if (name.equals("Document")) {
//Document is the root, nothing to do.
} else if (name.equals("Folder") || name.equals("Placemark")
|| name.equals("NetworkLink") || name.equals("GroundOverlay")) {
//this was a Feature:
KmlFolder parent = (KmlFolder)mKmlFeatureStack.get(mKmlFeatureStack.size()-2); //get parent
parent.add(mKmlCurrentFeature); //add current in its parent
mKmlFeatureStack.remove(mKmlFeatureStack.size()-1); //pop current from stack
mKmlCurrentFeature = mKmlFeatureStack.get(mKmlFeatureStack.size()-1); //set current to top of stack
if (name.equals("NetworkLink"))
mIsNetworkLink = false;
else if (name.equals("GroundOverlay"))
mKmlCurrentGroundOverlay = null;
} else if (name.equals("innerBoundaryIs")) {
mIsInnerBoundary = false;
} else if (name.equals("Point") || name.equals("LineString") || name.equals("Polygon")
|| name.equals("MultiGeometry") || name.equals("gx:Track")) {
//this was a Geometry:
if (mKmlGeometryStack.size() == 1){
//no MultiGeometry parent: add this Geometry in the current Feature:
((KmlPlacemark)mKmlCurrentFeature).mGeometry = mKmlCurrentGeometry;
mKmlGeometryStack.remove(mKmlGeometryStack.size()-1); //pop current from stack
mKmlCurrentGeometry = null;
} else {
KmlMultiGeometry parent = (KmlMultiGeometry)mKmlGeometryStack.get(mKmlGeometryStack.size()-2); //get parent
parent.addItem(mKmlCurrentGeometry); //add current in its parent
mKmlGeometryStack.remove(mKmlGeometryStack.size()-1); //pop current from stack
mKmlCurrentGeometry = mKmlGeometryStack.get(mKmlGeometryStack.size()-1); //set current to top of stack
}
} else if (name.equals("name")) {
mKmlCurrentFeature.mName = mStringBuilder.toString();
} else if (name.equals("description")) {
mKmlCurrentFeature.mDescription = mStringBuilder.toString();
} else if (name.equals("visibility")) {
mKmlCurrentFeature.mVisibility = ("1".equals(mStringBuilder.toString()));
} else if (name.equals("open")) {
mKmlCurrentFeature.mOpen = ("1".equals(mStringBuilder.toString()));
} else if (name.equals("coordinates")) {
if (mKmlCurrentFeature instanceof KmlPlacemark) {
if (!mIsInnerBoundary) {
mKmlCurrentGeometry.mCoordinates = parseKmlCoordinates(mStringBuilder.toString());
} else { //inside a Polygon innerBoundaryIs element: new hole
KmlPolygon polygon = (KmlPolygon) mKmlCurrentGeometry;
if (polygon.mHoles == null)
polygon.mHoles = new ArrayList<ArrayList<GeoPoint>>();
ArrayList<GeoPoint> hole = parseKmlCoordinates(mStringBuilder.toString());
polygon.mHoles.add(hole);
}
}
} else if (name.equals("gx:coord")) {
if (mKmlCurrentGeometry != null && mKmlCurrentGeometry instanceof KmlTrack)
((KmlTrack) mKmlCurrentGeometry).addGxCoord(mStringBuilder.toString());
} else if (name.equals("when")) {
if (mKmlCurrentGeometry != null && mKmlCurrentGeometry instanceof KmlTrack)
((KmlTrack) mKmlCurrentGeometry).addWhen(mStringBuilder.toString());
} else if (name.equals("styleUrl")) {
String styleUrl;
if (mStringBuilder.charAt(0) == '#')
styleUrl = mStringBuilder.substring(1); //remove the #
else //external url: keep as is:
styleUrl = mStringBuilder.toString();
if (mCurrentStyleMap != null){
mCurrentStyleMap.setPair(mCurrentStyleKey, styleUrl);
} else if (mKmlCurrentFeature != null){
mKmlCurrentFeature.mStyle = styleUrl;
}
} else if (name.equals("key")) {
mCurrentStyleKey = mStringBuilder.toString();
} else if (name.equals("color")) {
if (mCurrentStyle != null) {
if (mColorStyle != null)
mColorStyle.mColor = ColorStyle.parseKMLColor(mStringBuilder.toString());
} else if (mKmlCurrentGroundOverlay != null){
mKmlCurrentGroundOverlay.mColor = ColorStyle.parseKMLColor(mStringBuilder.toString());
}
} else if (name.equals("colorMode")) {
if (mCurrentStyle != null && mColorStyle != null)
mColorStyle.mColorMode = (mStringBuilder.toString().equals("random")?ColorStyle.MODE_RANDOM:ColorStyle.MODE_NORMAL);
} else if (name.equals("width")) {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof LineStyle)
mCurrentStyle.mLineStyle.mWidth = Float.parseFloat(mStringBuilder.toString());
} else if (name.equals("scale")) {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle){
mCurrentStyle.mIconStyle.mScale = Float.parseFloat(mStringBuilder.toString());
}
} else if (name.equals("heading")) {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle){
mCurrentStyle.mIconStyle.mHeading = Float.parseFloat(mStringBuilder.toString());
}
} else if (name.equals("href")) {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle) {
//href of an Icon in an IconStyle:
String href = mStringBuilder.toString();
mCurrentStyle.setIcon(href, mFile, mKMZFile);
} else if (mIsNetworkLink) {
//href of a NetworkLink:
String href = mStringBuilder.toString();
loadNetworkLink(href, mKMZFile);
} else if (mKmlCurrentGroundOverlay != null) {
//href of a GroundOverlay Icon:
mKmlCurrentGroundOverlay.setIcon(mStringBuilder.toString(), mFile, mKMZFile);
}
} else if (name.equals("LineStyle")) {
mColorStyle = null;
} else if (name.equals("PolyStyle")) {
mColorStyle = null;
} else if (name.equals("IconStyle")) {
mColorStyle = null;
} else if (name.equals("Style")) {
if (mCurrentStyleId != null)
putStyle(mCurrentStyleId, mCurrentStyle);
else {
mCurrentStyleId = addStyle(mCurrentStyle);
}
if (mKmlCurrentFeature != null && mKmlCurrentFeature != mKmlRoot){
//this is an inline style. Set its style id to the KmlObject container:
mKmlCurrentFeature.mStyle = mCurrentStyleId;
}
mCurrentStyle = null;
mCurrentStyleId = null;
} else if (name.equals("StyleMap")) {
if (mCurrentStyleId != null)
putStyle(mCurrentStyleId, mCurrentStyleMap);
//TODO: inline StyleMap ???
mCurrentStyleMap = null;
mCurrentStyleId = null;
mCurrentStyleKey = null;
} else if (name.equals("north")) {
mNorth = Double.parseDouble(mStringBuilder.toString());
} else if (name.equals("south")) {
mSouth = Double.parseDouble(mStringBuilder.toString());
} else if (name.equals("east")) {
mEast = Double.parseDouble(mStringBuilder.toString());
} else if (name.equals("west")) {
mWest = Double.parseDouble(mStringBuilder.toString());
} else if (name.equals("rotation")) {
mKmlCurrentGroundOverlay.mRotation = Float.parseFloat(mStringBuilder.toString());
} else if (name.equals("LatLonBox")) {
if (mKmlCurrentGroundOverlay != null){
mKmlCurrentGroundOverlay.setLatLonBox(mNorth, mSouth, mEast, mWest);
}
} else if (name.equals("SimpleData")) {
//We don't check the schema from SchemaData. We just pick the name and the value from SimpleData:
mKmlCurrentFeature.setExtendedData(mDataName, mStringBuilder.toString());
mDataName = null;
} else if (name.equals("value")) {
mKmlCurrentFeature.setExtendedData(mDataName, mStringBuilder.toString());
mDataName = null;
}
}
*/
public void endElement(String uri, String localName, String name)
throws SAXException {
KmlKeywords element = KEYWORDS_DICTIONARY.get(name);
if (element == null)
return;
switch (element) {
case Document: {
//Document is the root, nothing to do.
break;
}
case Folder:
case Placemark:
case NetworkLink:
case GroundOverlay: {
//this was a Feature:
KmlFolder parent = (KmlFolder) mKmlFeatureStack.get(mKmlFeatureStack.size() - 2); //get parent
parent.add(mKmlCurrentFeature); //add current in its parent
mKmlFeatureStack.remove(mKmlFeatureStack.size() - 1); //pop current from stack
mKmlCurrentFeature = mKmlFeatureStack.get(mKmlFeatureStack.size() - 1); //set current to top of stack
if (element == KmlKeywords.NetworkLink)
mIsNetworkLink = false;
else if (element == KmlKeywords.GroundOverlay)
mKmlCurrentGroundOverlay = null;
break;
}
case innerBoundaryIs: {
mIsInnerBoundary = false;
break;
}
case Point:
case LineString:
case Polygon:
case MultiGeometry:
case gx_Track: {
//this was a Geometry:
if (mKmlGeometryStack.size() == 1) {
//no MultiGeometry parent: add this Geometry in the current Feature:
((KmlPlacemark) mKmlCurrentFeature).mGeometry = mKmlCurrentGeometry;
mKmlGeometryStack.remove(mKmlGeometryStack.size() - 1); //pop current from stack
mKmlCurrentGeometry = null;
} else {
KmlMultiGeometry parent = (KmlMultiGeometry) mKmlGeometryStack.get(mKmlGeometryStack.size() - 2); //get parent
parent.addItem(mKmlCurrentGeometry); //add current in its parent
mKmlGeometryStack.remove(mKmlGeometryStack.size() - 1); //pop current from stack
mKmlCurrentGeometry = mKmlGeometryStack.get(mKmlGeometryStack.size() - 1); //set current to top of stack
}
break;
}
case name: {
mKmlCurrentFeature.mName = mStringBuilder.toString();
break;
}
case description: {
mKmlCurrentFeature.mDescription = mStringBuilder.toString();
break;
}
case visibility: {
mKmlCurrentFeature.mVisibility = ("1".equals(mStringBuilder.toString()));
break;
}
case open: {
mKmlCurrentFeature.mOpen = ("1".equals(mStringBuilder.toString()));
break;
}
case coordinates: {
if (mKmlCurrentFeature instanceof KmlPlacemark) {
if (!mIsInnerBoundary) {
mKmlCurrentGeometry.mCoordinates = parseKmlCoordinates(mStringBuilder.toString());
} else { //inside a Polygon innerBoundaryIs element: new hole
KmlPolygon polygon = (KmlPolygon) mKmlCurrentGeometry;
if (polygon.mHoles == null)
polygon.mHoles = new ArrayList<ArrayList<GeoPoint>>();
ArrayList<GeoPoint> hole = parseKmlCoordinates(mStringBuilder.toString());
polygon.mHoles.add(hole);
}
}
break;
}
case gx_coord: {
if (mKmlCurrentGeometry != null && mKmlCurrentGeometry instanceof KmlTrack)
((KmlTrack) mKmlCurrentGeometry).addGxCoord(mStringBuilder.toString());
break;
}
case when: {
if (mKmlCurrentGeometry != null && mKmlCurrentGeometry instanceof KmlTrack)
((KmlTrack) mKmlCurrentGeometry).addWhen(mStringBuilder.toString());
break;
}
case styleUrl: {
String styleUrl;
if (mStringBuilder.charAt(0) == '#')
styleUrl = mStringBuilder.substring(1); //remove the #
else //external url: keep as is:
styleUrl = mStringBuilder.toString();
if (mCurrentStyleMap != null) {
mCurrentStyleMap.setPair(mCurrentStyleKey, styleUrl);
} else if (mKmlCurrentFeature != null) {
mKmlCurrentFeature.mStyle = styleUrl;
}
break;
}
case key: {
mCurrentStyleKey = mStringBuilder.toString();
break;
}
case color: {
if (mCurrentStyle != null) {
if (mColorStyle != null)
mColorStyle.mColor = ColorStyle.parseKMLColor(mStringBuilder.toString());
} else if (mKmlCurrentGroundOverlay != null) {
mKmlCurrentGroundOverlay.mColor = ColorStyle.parseKMLColor(mStringBuilder.toString());
}
break;
}
case colorMode: {
if (mCurrentStyle != null && mColorStyle != null)
mColorStyle.mColorMode = (mStringBuilder.toString().equals("random")
? ColorStyle.MODE_RANDOM
: ColorStyle.MODE_NORMAL);
break;
}
case width: {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof LineStyle)
mCurrentStyle.mLineStyle.mWidth = Float.parseFloat(mStringBuilder.toString());
break;
}
case scale: {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle) {
mCurrentStyle.mIconStyle.mScale = Float.parseFloat(mStringBuilder.toString());
}
break;
}
case heading: {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle) {
mCurrentStyle.mIconStyle.mHeading = Float.parseFloat(mStringBuilder.toString());
}
break;
}
case href: {
if (mCurrentStyle != null && mColorStyle != null && mColorStyle instanceof IconStyle) {
//href of an Icon in an IconStyle:
String href = mStringBuilder.toString();
mCurrentStyle.setIcon(href, mFile, mKMZFile);
} else if (mIsNetworkLink) {
//href of a NetworkLink:
String href = mStringBuilder.toString();
loadNetworkLink(href, mKMZFile);
} else if (mKmlCurrentGroundOverlay != null) {
//href of a GroundOverlay Icon:
mKmlCurrentGroundOverlay.setIcon(mStringBuilder.toString(), mFile, mKMZFile);
}
break;
}
case LineStyle:
case PolyStyle:
case IconStyle: {
mColorStyle = null;
break;
}
case Style: {
if (mCurrentStyleId != null)
putStyle(mCurrentStyleId, mCurrentStyle);
else {
mCurrentStyleId = addStyle(mCurrentStyle);
}
if (mKmlCurrentFeature != null && mKmlCurrentFeature != mKmlRoot) {
//this is an inline style. Set its style id to the KmlObject container:
mKmlCurrentFeature.mStyle = mCurrentStyleId;
}
mCurrentStyle = null;
mCurrentStyleId = null;
break;
}
case StyleMap: {
if (mCurrentStyleId != null)
putStyle(mCurrentStyleId, mCurrentStyleMap);
//TODO: inline StyleMap ???
mCurrentStyleMap = null;
mCurrentStyleId = null;
mCurrentStyleKey = null;
break;
}
case north: {
mNorth = Double.parseDouble(mStringBuilder.toString());
break;
}
case south: {
mSouth = Double.parseDouble(mStringBuilder.toString());
break;
}
case east: {
mEast = Double.parseDouble(mStringBuilder.toString());
break;
}
case west: {
mWest = Double.parseDouble(mStringBuilder.toString());
break;
}
case rotation: {
if (mKmlCurrentGroundOverlay != null) {
mKmlCurrentGroundOverlay.mRotation = Float.parseFloat(mStringBuilder.toString());
}
break;
}
case LatLonBox: {
if (mKmlCurrentGroundOverlay != null) {
mKmlCurrentGroundOverlay.setLatLonBox(mNorth, mSouth, mEast, mWest);
}
break;
}
case SimpleData: {
//We don't check the schema from SchemaData. We just pick the name and the value from SimpleData:
mKmlCurrentFeature.setExtendedData(mDataName, mStringBuilder.toString());
mDataName = null;
break;
}
case value: {
mKmlCurrentFeature.setExtendedData(mDataName, mStringBuilder.toString());
mDataName = null;
break;
}
default: break;
} //switch
} //endElement
} //KmlSaxHandler class
/**
* save the document as a KML file on writer
* @param writer
* @return false if error
*/
public boolean saveAsKML(Writer writer){
try {
writer.write("<?xml version='1.0' encoding='UTF-8'?>\n");
writer.write("<kml xmlns='http://www.opengis.net/kml/2.2' xmlns:gx='http://www.google.com/kml/ext/2.2'>\n");
boolean result = true;
if (mKmlRoot != null)
result = mKmlRoot.writeAsKML(writer, true, this);
writer.write("</kml>\n");
return result;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public void writeKMLStyles(Writer writer){
for (Map.Entry<String, StyleSelector> entry : mStyles.entrySet()) {
String styleId = entry.getKey();
StyleSelector styleSelector = entry.getValue();
styleSelector.writeAsKML(writer, styleId);
}
}
/**
* Save the document as a KML file
* @param file full path of the destination file
* @return false if error
*/
public boolean saveAsKML(File file){
try {
Log.d(BonusPackHelper.LOG_TAG, "Saving "+file.getAbsolutePath());
//FileWriter fw = new FileWriter(file);
OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
BufferedWriter writer = new BufferedWriter(out, 8192);
boolean result = saveAsKML(writer);
writer.close();
Log.d(BonusPackHelper.LOG_TAG, "Saved.");
return result;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public boolean saveAsGeoJSON(Writer writer){
JsonObject json = mKmlRoot.asGeoJSON(true);
if (json == null)
return false;
try {
Gson gson = new GsonBuilder().create();
JsonWriter jsonWriter = new JsonWriter(writer);
gson.toJson(json, jsonWriter);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* Save the document as a GeoJSON file
* @param file full path of the destination file
* @return false if error
* @see <a href="http://geojson.org">GeoJSON</a>
*/
public boolean saveAsGeoJSON(File file){
try {
FileWriter fw = new FileWriter(file);
BufferedWriter writer = new BufferedWriter(fw, 8192);
boolean result = saveAsGeoJSON(writer);
writer.close();
return result;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/** Parse a GeoJSON object. */
public boolean parseGeoJSON(JsonObject json){
KmlFeature feature = KmlFeature.parseGeoJSON(json);
if (feature instanceof KmlFolder)
mKmlRoot = (KmlFolder)feature;
else {
mKmlRoot = new KmlFolder();
mKmlRoot.add(feature);
}
return true;
}
/** Parse a GeoJSON String */
public boolean parseGeoJSON(String jsonString){
try {
JsonParser parser = new JsonParser();
JsonElement json = parser.parse(jsonString);
return parseGeoJSON(json.getAsJsonObject());
} catch (JsonSyntaxException e) {
e.printStackTrace();
return false;
}
}
/** Parse a GeoJSON File */
public boolean parseGeoJSON(File file){
mLocalFile = file;
try {
FileInputStream input = new FileInputStream(mLocalFile);
JsonParser parser = new JsonParser();
JsonElement json = parser.parse(new InputStreamReader(input));
input.close();
return parseGeoJSON(json.getAsJsonObject());
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//Parcelable implementation ------------
@Override public int describeContents() {
return 0;
}
/** WARNING - Parcel mechanism doesn't work with very large objects. Refer to Android doc, and use carefully. */
@Override public void writeToParcel(Parcel out, int flags) {
out.writeParcelable(mKmlRoot, flags);
//write styles map:
//out.writeMap(mStyles); - not recommended in the Google JavaDoc, for mysterious reasons, so:
out.writeInt(mStyles.size());
for(String key : mStyles.keySet()){
out.writeString(key);
out.writeParcelable(mStyles.get(key), flags);
}
out.writeInt(mMaxStyleId);
if (mLocalFile != null)
out.writeString(mLocalFile.getAbsolutePath());
else
out.writeString("");
}
public static final Creator<KmlDocument> CREATOR = new Creator<KmlDocument>() {
@Override public KmlDocument createFromParcel(Parcel source) {
return new KmlDocument(source);
}
@Override public KmlDocument[] newArray(int size) {
return new KmlDocument[size];
}
};
public KmlDocument(Parcel in){
mKmlRoot = in.readParcelable(KmlFeature.class.getClassLoader());
//mStyles = in.readHashMap(Style.class.getClassLoader());
int size = in.readInt();
mStyles = new HashMap<String, StyleSelector>(size);
for(int i=0; i<size; i++){
String key = in.readString();
Style value = in.readParcelable(Style.class.getClassLoader());
mStyles.put(key,value);
}
mMaxStyleId = in.readInt();
String filePath = in.readString();
if (filePath.equals(""))
mLocalFile = null;
else
mLocalFile = new File(filePath);
}
}