package com.google.maps.android.kml;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import static org.xmlpull.v1.XmlPullParser.END_TAG;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
/**
* Parses the feature of a given KML file into a KmlPlacemark or KmlGroundOverlay object
*/
/* package */ class KmlFeatureParser {
private final static String GEOMETRY_REGEX = "Point|LineString|Polygon|MultiGeometry";
private final static int LONGITUDE_INDEX = 0;
private final static int LATITUDE_INDEX = 1;
private final static String PROPERTY_REGEX = "name|description|visibility|open|address|phoneNumber";
private final static String BOUNDARY_REGEX = "outerBoundaryIs|innerBoundaryIs";
private final static String EXTENDED_DATA = "ExtendedData";
private final static String STYLE_URL_TAG = "styleUrl";
private final static String STYLE_TAG = "Style";
private final static String COMPASS_REGEX = "north|south|east|west";
/**
* Creates a new Placemark object (created if a Placemark start tag is read by the
* XmlPullParser and if a Geometry tag is contained within the Placemark tag)
* and assigns specific elements read from the parser to the Placemark.
*/
/* package */
static KmlPlacemark createPlacemark(XmlPullParser parser)
throws IOException, XmlPullParserException {
String styleId = null;
KmlStyle inlineStyle = null;
HashMap<String, String> properties = new HashMap<String, String>();
KmlGeometry geometry = null;
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals("Placemark"))) {
if (eventType == START_TAG) {
if (parser.getName().equals(STYLE_URL_TAG)) {
styleId = parser.nextText();
} else if (parser.getName().matches(GEOMETRY_REGEX)) {
geometry = createGeometry(parser, parser.getName());
} else if (parser.getName().matches(PROPERTY_REGEX)) {
properties.put(parser.getName(), parser.nextText());
} else if (parser.getName().equals(EXTENDED_DATA)) {
properties.putAll(setExtendedDataProperties(parser));
} else if (parser.getName().equals(STYLE_TAG)) {
inlineStyle = KmlStyleParser.createStyle(parser);
}
}
eventType = parser.next();
}
return new KmlPlacemark(geometry, styleId, inlineStyle, properties);
}
/**
* Creates a new GroundOverlay object (created if a GroundOverlay tag is read by the
* XmlPullParser) and assigns specific elements read from the parser to the GroundOverlay
*/
/* package */
static KmlGroundOverlay createGroundOverlay(XmlPullParser parser)
throws IOException, XmlPullParserException {
float drawOrder = 0.0f;
float rotation = 0.0f;
int visibility = 1;
String imageUrl = null;
LatLngBounds latLonBox;
HashMap<String, String> properties = new HashMap<String, String>();
HashMap<String, Double> compassPoints = new HashMap<String, Double>();
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals("GroundOverlay"))) {
if (eventType == START_TAG) {
if (parser.getName().equals("Icon")) {
imageUrl = getImageUrl(parser);
} else if (parser.getName().equals("drawOrder")) {
drawOrder = Float.parseFloat(parser.nextText());
} else if (parser.getName().equals("visibility")) {
visibility = Integer.parseInt(parser.nextText());
} else if (parser.getName().equals("ExtendedData")) {
properties.putAll(setExtendedDataProperties(parser));
} else if (parser.getName().equals("rotation")) {
rotation = getRotation(parser);
} else if (parser.getName().matches(PROPERTY_REGEX) || parser.getName().equals("color")) {
properties.put(parser.getName(), parser.nextText());
} else if (parser.getName().matches(COMPASS_REGEX)) {
compassPoints.put(parser.getName(), Double.parseDouble(parser.nextText()));
}
}
eventType = parser.next();
}
latLonBox = createLatLngBounds(compassPoints.get("north"), compassPoints.get("south"),
compassPoints.get("east"), compassPoints.get("west"));
return new KmlGroundOverlay(imageUrl, latLonBox, drawOrder, visibility, properties,
rotation);
}
private static float getRotation(XmlPullParser parser)
throws IOException, XmlPullParserException {
return -Float.parseFloat(parser.nextText());
}
/**
* Retrieves a url from the "href" tag nested within an "Icon" tag, read by
* the XmlPullParser.
*
* @return An image url
*/
private static String getImageUrl(XmlPullParser parser)
throws IOException, XmlPullParserException {
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals("Icon"))) {
if (eventType == START_TAG && parser.getName().equals("href")) {
return parser.nextText();
}
eventType = parser.next();
}
return null;
}
/**
* Creates a new KmlGeometry object (Created if "Point", "LineString", "Polygon" or
* "MultiGeometry" tag is detected by the XmlPullParser)
*
* @param geometryType Type of geometry object to create
*/
private static KmlGeometry createGeometry(XmlPullParser parser, String geometryType)
throws IOException, XmlPullParserException {
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals(geometryType))) {
if (eventType == START_TAG) {
if (parser.getName().equals("Point")) {
return createPoint(parser);
} else if (parser.getName().equals("LineString")) {
return createLineString(parser);
} else if (parser.getName().equals("Polygon")) {
return createPolygon(parser);
} else if (parser.getName().equals("MultiGeometry")) {
return createMultiGeometry(parser);
}
}
eventType = parser.next();
}
return null;
}
/**
* Adds untyped name value pairs parsed from the ExtendedData
*/
private static HashMap<String, String> setExtendedDataProperties(XmlPullParser parser)
throws XmlPullParserException, IOException {
HashMap<String, String> properties = new HashMap<String, String>();
String propertyKey = null;
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals(EXTENDED_DATA))) {
if (eventType == START_TAG) {
if (parser.getName().equals("Data")) {
propertyKey = parser.getAttributeValue(null, "name");
} else if (parser.getName().equals("value") && propertyKey != null) {
properties.put(propertyKey, parser.nextText());
propertyKey = null;
}
}
eventType = parser.next();
}
return properties;
}
/**
* Creates a new KmlPoint object
*
* @return KmlPoint object
*/
private static KmlPoint createPoint(XmlPullParser parser)
throws XmlPullParserException, IOException {
LatLng coordinate = null;
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals("Point"))) {
if (eventType == START_TAG && parser.getName().equals("coordinates")) {
coordinate = convertToLatLng(parser.nextText());
}
eventType = parser.next();
}
return new KmlPoint(coordinate);
}
/**
* Creates a new KmlLineString object
*
* @return KmlLineString object
*/
private static KmlLineString createLineString(XmlPullParser parser)
throws XmlPullParserException, IOException {
ArrayList<LatLng> coordinates = new ArrayList<LatLng>();
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals("LineString"))) {
if (eventType == START_TAG && parser.getName().equals("coordinates")) {
coordinates = convertToLatLngArray(parser.nextText());
}
eventType = parser.next();
}
return new KmlLineString(coordinates);
}
/**
* Creates a new KmlPolygon object. Parses only one outer boundary and no or many inner
* boundaries containing the coordinates.
*
* @return KmlPolygon object
*/
private static KmlPolygon createPolygon(XmlPullParser parser)
throws XmlPullParserException, IOException {
// Indicates if an outer boundary needs to be defined
Boolean isOuterBoundary = false;
ArrayList<LatLng> outerBoundary = new ArrayList<LatLng>();
ArrayList<ArrayList<LatLng>> innerBoundaries = new ArrayList<ArrayList<LatLng>>();
int eventType = parser.getEventType();
while (!(eventType == END_TAG && parser.getName().equals("Polygon"))) {
if (eventType == START_TAG) {
if (parser.getName().matches(BOUNDARY_REGEX)) {
isOuterBoundary = parser.getName().equals("outerBoundaryIs");
} else if (parser.getName().equals("coordinates")) {
if (isOuterBoundary) {
outerBoundary = convertToLatLngArray(parser.nextText());
} else {
innerBoundaries.add(convertToLatLngArray(parser.nextText()));
}
}
}
eventType = parser.next();
}
return new KmlPolygon(outerBoundary, innerBoundaries);
}
/**
* Creates a new KmlMultiGeometry object
*
* @return KmlMultiGeometry object
*/
private static KmlMultiGeometry createMultiGeometry(XmlPullParser parser)
throws XmlPullParserException, IOException {
ArrayList<KmlGeometry> geometries = new ArrayList<KmlGeometry>();
// Get next otherwise have an infinite loop
int eventType = parser.next();
while (!(eventType == END_TAG && parser.getName().equals("MultiGeometry"))) {
if (eventType == START_TAG && parser.getName().matches(GEOMETRY_REGEX)) {
geometries.add(createGeometry(parser, parser.getName()));
}
eventType = parser.next();
}
return new KmlMultiGeometry(geometries);
}
/**
* Convert a string of coordinates into an array of LatLngs
*
* @param coordinatesString coordinates string to convert from
* @return array of LatLng objects created from the given coordinate string array
*/
private static ArrayList<LatLng> convertToLatLngArray(String coordinatesString) {
ArrayList<LatLng> coordinatesArray = new ArrayList<LatLng>();
// Need to trim to avoid whitespace around the coordinates such as tabs
String[] coordinates = coordinatesString.trim().split("(\\s+)");
for (String coordinate : coordinates) {
coordinatesArray.add(convertToLatLng(coordinate));
}
return coordinatesArray;
}
/**
* Convert a string coordinate from a string into a LatLng object
*
* @param coordinateString coordinate string to convert from
* @return LatLng object created from given coordinate string
*/
private static LatLng convertToLatLng(String coordinateString) {
// Lat and Lng are separated by a ,
String[] coordinate = coordinateString.split(",");
Double lat = Double.parseDouble(coordinate[LATITUDE_INDEX]);
Double lon = Double.parseDouble(coordinate[LONGITUDE_INDEX]);
return new LatLng(lat, lon);
}
/**
* Given a set of four latLng coordinates, creates a LatLng Bound
*
* @param north North coordinate of the bounding box
* @param south South coordinate of the bounding box
* @param east East coordinate of the bounding box
* @param west West coordinate of the bounding box
*/
private static LatLngBounds createLatLngBounds(Double north, Double south, Double east,
Double west) {
LatLng southWest = new LatLng(south, west);
LatLng northEast = new LatLng(north, east);
return new LatLngBounds(southWest, northEast);
}
}