package com.nutiteq.kml; import java.io.IOException; import java.io.Reader; import java.util.Vector; import javax.microedition.lcdui.Image; import org.kxml2.io.KXmlParser; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import com.java4ever.apime.io.GZIP; import com.nutiteq.cache.Cache; import com.nutiteq.cache.ImageWaiter; import com.nutiteq.components.ExtendedDataMap; import com.nutiteq.components.KmlPlace; import com.nutiteq.components.Line; import com.nutiteq.components.LineStyle; import com.nutiteq.components.OnMapElement; import com.nutiteq.components.Place; import com.nutiteq.components.PlaceIcon; import com.nutiteq.components.PolyStyle; import com.nutiteq.components.Polygon; import com.nutiteq.components.WgsPoint; import com.nutiteq.io.ResourceDataWaiter; import com.nutiteq.io.ResourceRequestor; import com.nutiteq.log.Log; import com.nutiteq.net.ImageWaitingDownloadable; import com.nutiteq.task.TasksRunner; import com.nutiteq.utils.IOUtils; import com.nutiteq.utils.Utils; public class KmlReader implements ResourceRequestor, ResourceDataWaiter { private static final String PLACEMARK_TAG = "Placemark"; private static final String PLACEMARK_NAME_TAG = "name"; private static final String PLACEMARK_POINT_TAG = "Point"; private static final String PLACEMARK_POINT_COORDINATES_TAG = "coordinates"; private static final String STYLE_TAG = "Style"; private static final String STYLE_MAP_TAG = "StyleMap"; private static final String STYLE_ICON_SCALE_TAG = "scale"; private static final String STYLE_ICON_HREF_TAG = "href"; private static final String PLACEMARK_DESCRIPTION_TAG = "description"; private static final String PLACEMARK_ADDRESS_TAG = "address"; private static final String PLACEMARK_SNIPPET_TAG = "Snippet"; private static final String PLACEMARK_XDATA_TAG = "ExtendedData"; private static final String PLACEMARK_STYLE_URL_TAG = "styleUrl"; private static final String STYLE_MAP_PAIR_TAG = "Pair"; private static final String KEY_TAG = "key"; private static final String POLYGON_TAG = "Polygon"; public static final String LINE_STRING_TAG = "LineString"; private static final String LINE_STYLE_TAG = "LineStyle"; private static final String POLY_STYLE_TAG = "PolyStyle"; private static final String COLOR_TAG = "color"; private static final String WIDTH_TAG = "width"; private static final String DATA_TAG = "Data"; private static final String VALUE_TAG = "value"; private static Image defaultKmlIcon = Image.createImage(18, 18); private final KmlService service; private final KmlElementsWaiter servicesHandler; private final KmlStylesCache stylesCache; private final String serviceUrl; private final TasksRunner tasksRunner; private final boolean responsePacked; private int placeCount; private static final String DEFAULT_KML_ICON = "/images/def_kml.png"; public KmlReader(final KmlElementsWaiter servicesHandler, final KmlService service, final String serviceUrl, final KmlStylesCache stylesCache, final TasksRunner tasksRunner) { this(servicesHandler, service, serviceUrl, stylesCache, tasksRunner, false); } public KmlReader(final KmlElementsWaiter servicesHandler, final KmlService service, final String serviceUrl, final KmlStylesCache stylesCache, final TasksRunner tasksRunner, final String defaultIcon) { this(servicesHandler, service, serviceUrl, stylesCache, tasksRunner, false, defaultIcon); } public KmlReader(final KmlElementsWaiter servicesHandler, final KmlService service, final String serviceUrl, final KmlStylesCache stylesCache, final TasksRunner tasksRunner, final boolean responsePacked){ this(servicesHandler, service, serviceUrl, stylesCache, tasksRunner, false, DEFAULT_KML_ICON); } public KmlReader(final KmlElementsWaiter servicesHandler, final KmlService service, final String serviceUrl, final KmlStylesCache stylesCache, final TasksRunner tasksRunner, final boolean responsePacked, final String defaultIcon) { this.servicesHandler = servicesHandler; this.service = service; this.serviceUrl = serviceUrl; this.stylesCache = stylesCache; this.tasksRunner = tasksRunner; this.responsePacked = responsePacked; try { defaultKmlIcon = Image.createImage(defaultIcon); } catch (final IOException e) { defaultKmlIcon = Image.createImage(18, 18); } } public String resourcePath() { return serviceUrl; } public void dataRetrieved(final byte[] data) { boolean guessPacked = false; if(data[0]==31 && data[1]==-117){ guessPacked=true; Log.debug("Guess response packed based on first 2 data bytes"); }else{ Log.debug("Response NOT packed based on first 2 data bytes"); } final byte[] finalData = (responsePacked || guessPacked) ? GZIP.inflate(data) : data; // remove BOM (ie the first 3 bytes) if(finalData[0]==0xef && finalData[1]==0xbb && finalData[2]==0xbf){ System.arraycopy(finalData, 3, finalData, 0, finalData.length-3); } final Reader reader = Utils.createInputStreamReader(finalData); // final ByteArrayInputStream bais = new ByteArrayInputStream(finalData); try { servicesHandler.addKmlPlaces(service, read(reader, service.maxResults())); } catch (final IOException e) { Log.printStackTrace(e); notifyError(); } finally { IOUtils.closeReader(reader); } Log.debug("KmlReader done, read places: "+placeCount); } public void notifyError() { //TODO jaanus : ignore? } protected KmlPlace[] read(final Reader reader, final int maxResults) throws IOException { final Vector places = new Vector(); final KXmlParser parser = new KXmlParser(); try { parser.setInput(reader); int eventType = parser.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT && places.size() < maxResults) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); if (PLACEMARK_TAG.equals(tagName)) { final KmlPlace place = readPlace(parser); if (place != null) { places.addElement(place); } } else if (STYLE_TAG.equals(tagName)) { stylesCache.addStyle(readStyle(parser)); } else if (STYLE_MAP_TAG.equals(tagName)) { stylesCache.addStyleMap(readStyleMap(parser)); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.error("KmlReader: read " + e.getMessage()); Log.printStackTrace(e); } final KmlPlace[] result = new KmlPlace[places.size()]; places.copyInto(result); placeCount = places.size(); return result; } private KmlStyleMap readStyleMap(final KXmlParser parser) throws IOException { KmlStyleMap map = null; try { map = new KmlStyleMap(parser.getAttributeValue(null, "id")); int eventType = parser.next(); while (!STYLE_MAP_TAG.equals(parser.getName())) { if (XmlPullParser.START_TAG == eventType) { if (STYLE_MAP_PAIR_TAG.equals(parser.getName())) { addStylePair(map, parser); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } return map; } private void addStylePair(final KmlStyleMap map, final KXmlParser parser) throws IOException { String key = null; String styleUrl = null; try { int eventType = parser.next(); while (!STYLE_MAP_PAIR_TAG.equals(parser.getName())) { if (XmlPullParser.START_TAG == eventType) { final String tagName = parser.getName(); if (KEY_TAG.equals(tagName)) { key = parser.nextText(); } else if (PLACEMARK_STYLE_URL_TAG.equals(tagName)) { styleUrl = parser.nextText().trim(); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } map.addPair(key, styleUrl); } private KmlStyle readStyle(final KXmlParser parser) throws IOException { String id = null; float scale = 0.0f; String href = null; LineStyle lineStyle = null; PolyStyle polyStyle = null; try { id = parser.getAttributeValue(null, "id"); int eventType = parser.next(); while (!STYLE_TAG.equals(parser.getName())) { if (XmlPullParser.START_TAG == eventType) { final String tagName = parser.getName(); if (STYLE_ICON_SCALE_TAG.equals(tagName)) { scale = Float.parseFloat(parser.nextText()); } else if (STYLE_ICON_HREF_TAG.equals(tagName)) { href = parser.nextText().trim(); } else if (LINE_STYLE_TAG.equals(tagName)) { lineStyle = readLineStyle(parser); } else if (POLY_STYLE_TAG.equals(tagName)) { polyStyle = readPolyStyle(parser); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } final KmlStyle result = new KmlStyle(id, scale, href); result.setLineStyle(lineStyle); result.setPolyStyle(polyStyle); return result; } private KmlPlace readPlace(final KXmlParser parser) throws IOException { String name = null; WgsPoint point = null; String description = null; String styleUrl = null; String address = null; String snippet = null; ExtendedDataMap extendedDataMap = new ExtendedDataMap(); final Vector kmlElements = new Vector(); try { int eventType = parser.next(); while (!PLACEMARK_TAG.equals(parser.getName())) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); if (PLACEMARK_NAME_TAG.equals(tagName)) { name = parser.nextText(); } else if (PLACEMARK_POINT_TAG.equals(tagName)) { point = readPoint(parser); } else if (PLACEMARK_DESCRIPTION_TAG.equals(tagName)) { description = parser.nextText(); } else if (PLACEMARK_STYLE_URL_TAG.equals(tagName)) { styleUrl = parser.nextText().trim(); } else if (PLACEMARK_ADDRESS_TAG.equals(tagName)) { address = parser.nextText(); } else if (PLACEMARK_SNIPPET_TAG.equals(tagName)) { snippet = parser.nextText(); } else if (PLACEMARK_XDATA_TAG.equals(tagName)) { extendedDataMap = readXData(parser); } else if (POLYGON_TAG.equals(tagName)) { kmlElements.addElement(readPolygon(parser)); } else if (LINE_STRING_TAG.equals(tagName)) { kmlElements.addElement(readLine(parser)); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.error("read place: " + e.getMessage()); Log.printStackTrace(e); return null; } final Place place = new Place(0, name, new PlaceIcon(defaultKmlIcon), point); final String placeIconUrl = stylesCache.resolveImageUrl(styleUrl); //TODO jaanus : check this if (tasksRunner != null && placeIconUrl != null && servicesHandler instanceof ImageWaiter) { tasksRunner.enqueueDownload(new ImageWaitingDownloadable((ImageWaiter) servicesHandler, placeIconUrl), Cache.CACHE_LEVEL_MEMORY | Cache.CACHE_LEVEL_PERSISTENT); } final OnMapElement[] elements = new OnMapElement[kmlElements.size()]; kmlElements.copyInto(elements); //TODO jaanus : maybe can find a better way for this final KmlStyle style = stylesCache.getStyle(styleUrl == null ? "" : styleUrl.substring(1)); final LineStyle lineStyle = style == null ? LineStyle.DEFAULT_STYLE : style.getLineStyle() == null ? LineStyle.DEFAULT_STYLE : style.getLineStyle(); final PolyStyle polyStyle = style == null ? PolyStyle.DEFAULT_STYLE : style.getPolyStyle() == null ? PolyStyle.DEFAULT_STYLE : style.getPolyStyle(); for (int i = 0; i < elements.length; i++) { if (elements[i] instanceof Line) { ((Line) elements[i]).setStyle(lineStyle); } else if (elements[i] instanceof Polygon) { ((Polygon) elements[i]).setStyle(polyStyle); } } place.setOnMapElements(elements); return new KmlPlace(place, styleUrl, description, address, snippet, extendedDataMap); } private ExtendedDataMap readXData(KXmlParser parser) { ExtendedDataMap out = new ExtendedDataMap(); String key = null; String value = null; int eventType = -1; String tagName=null; try { search: while (!PLACEMARK_XDATA_TAG.equals(tagName)){ // run over all key-value pairs // find key tagName = null; while (!DATA_TAG.equals(tagName)) { eventType = parser.next(); tagName = parser.getName(); if (PLACEMARK_XDATA_TAG.equals(tagName)) { break search; } } // key found from attribute key = parser.getAttributeValue(0); //System.out.println(key); tagName = null; // find value while (!DATA_TAG.equals(tagName)) { if (eventType == XmlPullParser.START_TAG && VALUE_TAG.equals(tagName)) { value = parser.nextText(); } eventType = parser.next(); tagName = parser.getName(); } // value found, write to output //System.out.println(value); out.addPair(key, value); } } catch (Exception e) { Log.printStackTrace(e); } return out; } /* private String readXDataKey(final KXmlParser parser) throws IOException { String result = null; try { int eventType = parser.next(); String name = parser.getName(); while (!DATA_TAG.equals(name)) { if (eventType == XmlPullParser.START_TAG && VALUE_TAG.equals(name)) { result = parser.getAttributeValue(0); } eventType = parser.next(); name = parser.getName(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } catch (final ArrayIndexOutOfBoundsException e) { Log.printStackTrace(e); // result will stay null } result = parser.getAttributeValue(0); return result; } private String readXDataValue(final KXmlParser parser) throws IOException { String result = null; try { int eventType = parser.next(); String name = parser.getName(); while (!DATA_TAG.equals(name)) { if (eventType == XmlPullParser.START_TAG && VALUE_TAG.equals(name)) { result = parser.nextText(); } eventType = parser.next(); name = parser.getName(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } catch (final ArrayIndexOutOfBoundsException e) { Log.printStackTrace(e); // result will stay null } return result; } */ protected Line readLine(final KXmlParser parser) throws IOException { WgsPoint[] coordinates = null; try { int eventType = parser.next(); while (!LINE_STRING_TAG.equals(parser.getName())) { if (eventType == XmlPullParser.START_TAG && PLACEMARK_POINT_COORDINATES_TAG.equals(parser.getName())) { coordinates = parseWgsCoordinates(parser.nextText()); } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } return new Line(coordinates); } private LineStyle readLineStyle(final KXmlParser parser) throws IOException { int color = LineStyle.DEFAULT_COLOR; int width = LineStyle.DEFAULT_WIDTH; try { int eventType = parser.next(); while (!LINE_STYLE_TAG.equals(parser.getName())) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); if (COLOR_TAG.equals(tagName)) { color = parseKmlColor(parser.nextText()); } else if (WIDTH_TAG.equals(tagName)) { width = Integer.parseInt(parser.nextText()); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } catch (final NumberFormatException ignore) { //values set at the beginning Log.printStackTrace(ignore); } return new LineStyle(color, width); } private PolyStyle readPolyStyle(final KXmlParser parser) throws IOException { int color = PolyStyle.DEFAULT_COLOR; try { int eventType = parser.next(); while (!POLY_STYLE_TAG.equals(parser.getName())) { if (eventType == XmlPullParser.START_TAG) { final String tagName = parser.getName(); if (COLOR_TAG.equals(tagName)) { color = parseKmlColor(parser.nextText()); } } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } catch (final NumberFormatException ignore) { //values set at the beginning Log.printStackTrace(ignore); } return new PolyStyle(color); } protected int parseKmlColor(final String color) { if (color.length() != 8) { return LineStyle.DEFAULT_COLOR; } //kml is using aabbggrr instead of aarrggbb final int alpha = Integer.parseInt(color.substring(0, 2), 16) & 0xFF; final int blue = Integer.parseInt(color.substring(2, 4), 16) & 0xFF; final int green = Integer.parseInt(color.substring(4, 6), 16) & 0xFF; final int red = Integer.parseInt(color.substring(6), 16) & 0xFF; return alpha << 24 | red << 16 | green << 8 | blue; } private OnMapElement readPolygon(final KXmlParser parser) throws IOException { WgsPoint[] coordinates = null; try { int eventType = parser.next(); while (!POLYGON_TAG.equals(parser.getName())) { if (eventType == XmlPullParser.START_TAG && PLACEMARK_POINT_COORDINATES_TAG.equals(parser.getName())) { coordinates = parseWgsCoordinates(parser.nextText()); } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } return new Polygon(coordinates); } private WgsPoint readPoint(final KXmlParser parser) throws IOException { WgsPoint result = null; try { int eventType = parser.next(); while (!PLACEMARK_POINT_TAG.equals(parser.getName())) { if (eventType == XmlPullParser.START_TAG && PLACEMARK_POINT_COORDINATES_TAG.equals(parser.getName())) { result = parseWgsCoordinatesForPoint(parser.nextText())[0]; } eventType = parser.next(); } } catch (final XmlPullParserException e) { Log.printStackTrace(e); } catch (final ArrayIndexOutOfBoundsException e) { Log.printStackTrace(e); //result will stay null } return result; } protected WgsPoint[] parseWgsCoordinates(final String kmlCoordinates) { final Vector result = new Vector(); if (kmlCoordinates == null) { return null; } //TODO jaanus : maybe can handle it better final String[] breakSplit = Utils.split(kmlCoordinates, "\n"); for (int i = 0; i < breakSplit.length; i++) { final String[] spaceSplit = Utils.split(breakSplit[i].trim(), " "); for (int j = 0; j < spaceSplit.length; j++) { try { final String line = spaceSplit[j]; if (line.indexOf(",") < 0) { continue; } final String[] split = Utils.split(line, ","); if (split.length < 2) { continue; } result.addElement(new WgsPoint(Double.parseDouble(split[0].trim()), Double .parseDouble(split[1].trim()))); } catch (final NumberFormatException e) { Log.printStackTrace(e); //TODO jaanus : is just ignore this point OK? continue; } } } final WgsPoint[] out = new WgsPoint[result.size()]; result.copyInto(out); return out; } protected WgsPoint[] parseWgsCoordinatesForPoint(final String kmlCoordinates) { final Vector result = new Vector(); if (kmlCoordinates == null) { return null; } try { final String[] split = Utils.split(kmlCoordinates, ","); result.addElement(new WgsPoint(Double.parseDouble(split[0].trim()), Double .parseDouble(split[1].trim()))); } catch (final NumberFormatException e) { Log.printStackTrace(e); //TODO jaanus : is just ignore this point OK? } final WgsPoint[] out = new WgsPoint[result.size()]; result.copyInto(out); return out; } public int getCachingLevel() { return Cache.CACHE_LEVEL_NONE; } }