/* * Copyright (C) 2014 Alec Dhuse * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package co.foldingmap.mapImportExport; import co.foldingmap.map.MapProjection; import co.foldingmap.map.MercatorProjection; import co.foldingmap.map.DigitalMap; import co.foldingmap.map.Visibility; import co.foldingmap.map.Layer; import co.foldingmap.map.vector.LinearRing; import co.foldingmap.map.vector.Region; import co.foldingmap.map.vector.NetworkLayer; import co.foldingmap.map.vector.MultiGeometry; import co.foldingmap.map.vector.LatLonAltBox; import co.foldingmap.map.vector.VectorLayer; import co.foldingmap.map.vector.NodeMap; import co.foldingmap.map.vector.CoordinateList; import co.foldingmap.map.vector.VectorObject; import co.foldingmap.map.vector.VectorObjectList; import co.foldingmap.map.vector.LevelOfDetail; import co.foldingmap.map.vector.LineString; import co.foldingmap.map.vector.MapIcon; import co.foldingmap.map.vector.Polygon; import co.foldingmap.map.vector.Coordinate; import co.foldingmap.map.vector.MapPoint; import co.foldingmap.map.vector.InnerBoundary; import co.foldingmap.map.themes.PolygonStyle; import co.foldingmap.map.themes.ColorHelper; import co.foldingmap.map.themes.MapTheme; import co.foldingmap.map.themes.ColorStyle; import co.foldingmap.map.themes.LabelStyle; import co.foldingmap.map.themes.IconStyle; import co.foldingmap.map.themes.LineStyle; import co.foldingmap.GUISupport.ProgressBarPanel; import co.foldingmap.GUISupport.ProgressIndicator; import co.foldingmap.Logger; import co.foldingmap.map.raster.ImageOverlay; import co.foldingmap.map.tile.TileMath; import co.foldingmap.xml.XMLParser; import co.foldingmap.xml.XMLTag; import java.awt.Color; import java.awt.Font; import java.io.*; import java.util.ArrayList; import java.util.HashMap; import java.util.StringTokenizer; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** * Class for loading KML files. * * @author Alec */ public class KmlImport implements FormatImporter { public KmlImport() { } /** * Creates a new Visibility object from a Region. * If the region does not contain a LevelOfDetail then a Visibility object with max = 25 and * min = 0 is created. * * @param r * @return */ public static Visibility convertRegionToVisibility(Region r) { LevelOfDetail lod = r.getLevelOfDetail(); if (lod != null) { float max = r.calculateVectorZoomLevelForLOD(lod.getMaxLodPixels()); float min = r.calculateVectorZoomLevelForLOD(lod.getMinLodPixels()); return new Visibility(TileMath.getTileMapZoom(max), TileMath.getTileMapZoom(min)); } else { return new Visibility(25, 0); } } public static CoordinateList<Coordinate> getCoordinateList(NodeMap coordinateSet, String coordinateString) { Coordinate coordinate, existingCoordinate; CoordinateList<Coordinate> coordinates; long nodeId; String coordinateGroupString; StringTokenizer coordinateGroupTokenizer; try { coordinateGroupTokenizer = new StringTokenizer(coordinateString); coordinates = new CoordinateList<Coordinate>(coordinateGroupTokenizer.countTokens()); while (coordinateGroupTokenizer.hasMoreTokens()) { coordinateGroupString = coordinateGroupTokenizer.nextToken(); coordinate = new Coordinate(coordinateGroupString); if (coordinate != null) { //check to see if the coordinate already exists in the map nodeId = coordinateSet.findKey(coordinate); if (nodeId > 0) { //the coordinate already exists existingCoordinate = coordinateSet.get(nodeId); if (existingCoordinate != null) { coordinates.add(existingCoordinate); } else { Logger.log(Logger.ERR, "Error node id: " + nodeId + "not found"); } } else { coordinateSet.put(coordinate); coordinates.add(coordinate); } } else { Logger.log(Logger.ERR, "Error in KmlImport.getCoordinateList(NodeMap, String) - Error paring Coordinates."); } // null coordinate check } return coordinates; } catch (Exception e) { System.err.println("Error in KmlImporter.getCoordinateList(NodeMap, String - " + e); return new CoordinateList<Coordinate>(1); } } /** * Reads gxTags to create a HashMap of customDataFields. * This is the old way of reading in custom data in TrucGIS. * * @param gxTags * @return */ public static HashMap<String,String> getCustomDataFields(ArrayList<XMLTag> gxTags) { HashMap<String,String> customDataFields; String key, value; customDataFields = new HashMap<String,String>(); try { for (XMLTag currentGXTag: gxTags) { key = currentGXTag.getTagName().substring(3); value = currentGXTag.getTagContent(); if ( (!currentGXTag.getTagName().equalsIgnoreCase("gx:Timestamps")) && (!currentGXTag.getTagName().equalsIgnoreCase("gx:LineStroke")) && (!currentGXTag.getTagName().equalsIgnoreCase("gx:locked")) && (!currentGXTag.getTagName().equalsIgnoreCase("gx:Track")) && (!currentGXTag.getTagName().equalsIgnoreCase("gx:outlineColor")) ) { customDataFields.put(key, value); } } //end for loop } catch (Exception e) { System.err.println("Error in KmlImport.getCustomDataFields(ArrayList) - " + e); } return customDataFields; } /** * Reads the ExtendedData tag to read in object data * * @param extendedDataTag * @return */ public static HashMap<String, String> getExtendedData(XMLTag extendedDataTag) { HashMap<String,String> customDataFields; String key, value, tagName; customDataFields = new HashMap<String,String>(); try { for (XMLTag subtag: extendedDataTag.getSubtags()) { key = ""; value = ""; tagName = subtag.getTagName(); if (tagName.substring(0,4).equalsIgnoreCase("Data")) { key = subtag.getTagValue(); value = subtag.getSubtagContent("value"); customDataFields.put(key, value); } } } catch (Exception e) { System.err.println("Error in KmlImport. getExtendedData(XMLTag) - " + e); } return customDataFields; } /** * Loads a ColorStyle from a given style XMLTag */ public static ColorStyle getStyle(XMLTag styleTag) { ColorStyle style = null; LabelStyle labelStyle; XMLTag labelTag; try { if (styleTag.containsSubTag("IconStyle")) { style = getIconStyle(styleTag); } else if (styleTag.containsSubTag("LineStyle")) { style = getLineStyle(styleTag); } else if (styleTag.containsSubTag("PolyStyle")) { style = getPolygonStyle(styleTag); } if (styleTag.containsSubTag("labelStyle")) { labelTag = styleTag.getSubtag("labelStyle"); labelStyle = getLabelStyle(labelTag); if (labelStyle != null) style.setLabel(labelStyle); } return style; } catch (Exception e) { System.err.println("Error in KmlImport.getStyle(XMLTag) - " + e); return null; } } /** * Creates a ImageOverlay object from the ImageOverlay XML. * * @param tag * @return */ public static ImageOverlay getGroundOverlay(XMLTag tag) { ImageOverlay groundOverlay; LatLonAltBox bounds; MapIcon mapIcon; String id; XMLTag iconTag, latLonAltTag; id = tag.getTagValue(); iconTag = tag.getSubtag("Icon"); latLonAltTag = tag.getSubtag("LatLonBox"); mapIcon = getIcon(iconTag); bounds = getLatLonAltBox(latLonAltTag); if (id == null) id = "Ground Overlay"; groundOverlay = new ImageOverlay(id, mapIcon, bounds); return groundOverlay; } /** * Loads the Icon XML object into a MapIcon object. * * @param tag * @return */ public static MapIcon getIcon(XMLTag tag) { boolean hasRefreshInterval, hasRefreshMode, hasViewRefreshMode; Float refreshInterval; //in seconds MapIcon mapIcon; String address, id; String refreshMode, viewRefreshMode; id = tag.getTagValue(); address = tag.getSubtagContent("href"); mapIcon = new MapIcon(id, address); hasRefreshInterval = tag.containsSubTag("refreshInterval"); hasRefreshMode = tag.containsSubTag("refreshMode"); hasViewRefreshMode = tag.containsSubTag("viewRefreshMode"); if (hasRefreshInterval) { refreshInterval = Float.parseFloat(tag.getSubtagContent("refreshInterval")); mapIcon.setRefreshInterval(refreshInterval); } if (hasRefreshMode) { refreshMode = tag.getSubtagContent("refreshMode"); if (refreshMode.equalsIgnoreCase("onChange")) { mapIcon.setRefreshMode(MapIcon.ON_CHANGE); } else if (refreshMode.equalsIgnoreCase("onInterval")) { mapIcon.setRefreshMode(MapIcon.ON_INTERVAL); } else if (refreshMode.equalsIgnoreCase("onExpire")) { mapIcon.setRefreshMode(MapIcon.ON_EXPIRE); } } if (hasViewRefreshMode) { viewRefreshMode = tag.getSubtagContent("viewRefreshMode"); if (viewRefreshMode.equalsIgnoreCase("never")) { mapIcon.setViewRefreshMode(MapIcon.NEVER); } else if (viewRefreshMode.equalsIgnoreCase("onStop")) { mapIcon.setViewRefreshMode(MapIcon.ON_STOP); } else if (viewRefreshMode.equalsIgnoreCase("onRequest")) { mapIcon.setViewRefreshMode(MapIcon.ON_REQUEST); } else if (viewRefreshMode.equalsIgnoreCase("onRegion")) { mapIcon.setViewRefreshMode(MapIcon.ON_REGION); } } return mapIcon; } /** * Returns an IconStyle given it's KML code. * * @param styleTag * @return */ public static IconStyle getIconStyle(XMLTag styleTag) { try { boolean hasHeading, hasIcon, hasLabel, hasOutline, hasScale; boolean outline; Color fillColor, outlineColor; float heading, scale; IconStyle newIconStyle; int colorMode; LabelStyle labelStyle; String colorModeString, icon, styleID, styleTagName; XMLTag iconTag, iconStyleTag; styleTagName = styleTag.getTagName(); styleID = styleTag.getTagValue(); iconStyleTag = styleTag.getSubtag("IconStyle"); fillColor = ColorHelper.parseHexAlphabetical(iconStyleTag.getSubtagContent("color")); colorModeString = iconStyleTag.getSubtagContent("colorMode"); hasScale = iconStyleTag.containsSubTag("scale"); hasHeading = iconStyleTag.containsSubTag("heading"); hasOutline = iconStyleTag.containsSubTag("outline"); hasIcon = iconStyleTag.containsSubTag("Icon"); hasLabel = iconStyleTag.containsSubTag("LabelStyle"); if (colorModeString.equalsIgnoreCase("normal")) { colorMode = ColorStyle.NORMAL; } else { colorMode = ColorStyle.RANDOM; } newIconStyle = new IconStyle(styleID, fillColor); if (hasScale) { scale = Float.parseFloat(iconStyleTag.getSubtagContent("scale")); newIconStyle.setScale(scale); } if (hasHeading) { heading = Float.parseFloat(iconStyleTag.getSubtagContent("heading")); newIconStyle.setHeading(heading); } if (hasOutline) { outline = Boolean.parseBoolean(iconStyleTag.getSubtagContent("outline")); if (outline) { outlineColor = ColorHelper.parseHexAlphabetical(iconStyleTag.getSubtagContent("gx:outlineColor")); newIconStyle.setOutlineColor(outlineColor); } } newIconStyle.setColorMode(colorMode); if (hasIcon) { iconTag = iconStyleTag.getSubtag("Icon"); icon = iconTag.getSubtagContent("href"); newIconStyle.setImageFileName(icon); } if (hasLabel) { labelStyle = getLabelStyle(iconStyleTag.getSubtag("LabelStyle")); if (labelStyle != null) newIconStyle.setLabel(labelStyle); } return newIconStyle; } catch (Exception e) { System.err.println("Error in KmlImport.getIconStyle(XMLTag) - " + e); return null; } } /** * Creates an InnerBoundary when given an InnerBoundary KML tag. * * @param layer * @param ibTag * @return */ public static InnerBoundary getInnerBoundary(NodeMap nodeMap, VectorLayer layer, XMLTag ibTag) { CoordinateList<Coordinate> coordinates; InnerBoundary ib; String coordinateString; XMLTag linearRingTag; linearRingTag = ibTag.getSubtag("LinearRing"); coordinateString = linearRingTag.getSubtagContent("coordinates"); coordinates = getCoordinateList(nodeMap, coordinateString); ib = new InnerBoundary(coordinates); return ib; } /** * Reads in the KML for LabelStyle and returns an object. * * @param labelTag * @return */ public static LabelStyle getLabelStyle(XMLTag labelTag) { Color fillColor, outlineColor; Font labelFont; float scale; int fontSize; LabelStyle labelStyle; String scaleString; try { fillColor = ColorHelper.parseHexAlphabetical(labelTag.getSubtagContent("color")); outlineColor = new Color(60, 68, 75); fontSize = 12; if (labelTag.containsSubTag("scale")) { scaleString = labelTag.getSubtagContent("scale"); scale = Float.parseFloat(scaleString); fontSize = (int) (fontSize * scale); } if (fontSize >= 1) { labelFont = new Font(Font.SANS_SERIF, Font.BOLD, fontSize); labelStyle = new LabelStyle(fillColor, outlineColor, labelFont); return labelStyle; } else { Logger.log(Logger.WARN, "LabelStyle has font size of zero, disregarding style." ); return null; } } catch (Exception e) { Logger.log(Logger.ERR, "Error in KmlImport.getLabelStyle(XMLTag) - " + e); return null; } } /** * Reads in XML for LevelOfDetail and returns a LevelOfDetail object. * * @param styleTag * @return */ public static LevelOfDetail getLevelOfDetail(XMLTag lodTag) { float minLodPixels, maxLodPixels, minFadeExtent, maxFadeExtent; LevelOfDetail lod; minLodPixels = Float.parseFloat(lodTag.getSubtagContent("minLodPixels")); maxLodPixels = Float.parseFloat(lodTag.getSubtagContent("maxLodPixels")); minFadeExtent = Float.parseFloat(lodTag.getSubtagContent("minFadeExtent")); maxFadeExtent = Float.parseFloat(lodTag.getSubtagContent("maxFadeExtent")); lod = new LevelOfDetail(maxLodPixels, minLodPixels, maxFadeExtent, minFadeExtent); return lod; } /** * Creates a LatLonAltBox object from the XML LatLonAltBox. * * @param tag * @return */ public static LatLonAltBox getLatLonAltBox(XMLTag tag) { boolean hasMaxAltitude, hasMinAltitude; float north, south, east, west, minAlt, maxAlt; LatLonAltBox latLonAltBox; hasMaxAltitude = tag.containsSubTag("maxAltitude"); hasMinAltitude = tag.containsSubTag("minAltitude"); north = Float.parseFloat(tag.getSubtagContent("north")); south = Float.parseFloat(tag.getSubtagContent("south")); east = Float.parseFloat(tag.getSubtagContent("east")); west = Float.parseFloat(tag.getSubtagContent("west")); if (hasMinAltitude) { minAlt = Float.parseFloat(tag.getSubtagContent("minAltitude")); } else { minAlt = -1; } if (hasMaxAltitude) { maxAlt = Float.parseFloat(tag.getSubtagContent("maxAltitude")); } else { maxAlt = -1; } latLonAltBox = new LatLonAltBox(north, south, east, west,minAlt, maxAlt); return latLonAltBox; } /** * Reads in KML for a LinearRing and returns a LinearRing object. * * @param layer * @param placemarkTag * @return */ public static LinearRing getLinearRing(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { try { ArrayList<XMLTag> gxTags; boolean hasExtendedData, hasRegion, hasTimestamps; CoordinateList<Coordinate> coordinates; HashMap<String,String> customDataFields; LinearRing newRing; String altitudeMode, coordinateString, description; String objectName, styleUrl, timestamps; XMLTag extendedData, ringTag; objectName = placemarkTag.getSubtagContent("name"); description = removeCDataTag(placemarkTag.getSubtagContent("description")); styleUrl = placemarkTag.getSubtagContent("styleUrl"); hasExtendedData = placemarkTag.containsSubTag("ExtendedData"); hasRegion = placemarkTag.containsSubTag("Region"); ringTag = placemarkTag.getSubtagStartsWith("LinearRing"); altitudeMode = ringTag.getSubtagContent("altitudeMode"); coordinateString = ringTag.getSubtagContent("coordinates"); coordinates = getCoordinateList(nodeMap, coordinateString); hasTimestamps = ringTag.containsSubTag("gx:Timestamps"); gxTags = ringTag.getGxSubtags(); //Check for alternative format. if (styleUrl.equals("")) styleUrl = ringTag.getTagValue(); if (styleUrl.startsWith("#")) styleUrl = styleUrl.substring(1); newRing = new LinearRing(objectName, styleUrl, coordinates); newRing.setDescription(description); if (altitudeMode.equalsIgnoreCase("clampToGround")) { newRing.setAltitudeMode(VectorObject.CLAMP_TO_GROUND); } else if (altitudeMode.equalsIgnoreCase("relativeToGround")) { newRing.setAltitudeMode(VectorObject.RELATIVE_TO_GROUND); } else { newRing.setAltitudeMode(VectorObject.ABSOLUTE); } if (hasExtendedData) { extendedData = placemarkTag.getSubtag("ExtendedData"); customDataFields = getExtendedData(extendedData); newRing.setCustomDataFields(customDataFields); } else { //use old extended data method for compatibility customDataFields = getCustomDataFields(gxTags); newRing.setCustomDataFields(customDataFields); } if (hasRegion) { Region region = loadRegion(placemarkTag.getSubtag("Region")); Visibility vis = convertRegionToVisibility(region); newRing.setVisibility(vis); } if (hasTimestamps) { timestamps = ringTag.getSubtagContent("gx:Timestamps"); newRing.setTimestamps(timestamps); } return newRing; } catch (Exception e) { System.err.println("Error in KmlImport.getLinearRing(VectorLayer, XMLTag) - " + e); return null; } } /** * Reads KML for LineString and returns a LineString Object. * * @param lineStringTag * @return */ public static LineString getLineString(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { try { ArrayList<XMLTag> gxTags; boolean hasExtendedData, hasRegion, hasTimestamps; CoordinateList<Coordinate> coordinates; HashMap<String,String> customDataFields; LineString newLine; String altitudeMode, coordinateString, description; String objectName, styleUrl, timestamps; XMLTag extendedData, linestringTag; objectName = placemarkTag.getSubtagContent("name"); description = removeCDataTag(placemarkTag.getSubtagContent("description")); styleUrl = placemarkTag.getSubtagContent("styleUrl"); hasExtendedData = placemarkTag.containsSubTag("ExtendedData"); hasRegion = placemarkTag.containsSubTag("Region"); linestringTag = placemarkTag.getSubtagStartsWith("LineString"); altitudeMode = linestringTag.getSubtagContent("altitudeMode"); coordinateString = linestringTag.getSubtagContent("coordinates"); coordinates = getCoordinateList(nodeMap, coordinateString); hasTimestamps = linestringTag.containsSubTag("gx:Timestamps"); gxTags = linestringTag.getGxSubtags(); //Check for alternative format. if (styleUrl.equals("")) styleUrl = linestringTag.getTagValue(); if (styleUrl.startsWith("#")) styleUrl = styleUrl.substring(1); newLine = new LineString(objectName, styleUrl, coordinates); newLine.setDescription(description); if (altitudeMode.equalsIgnoreCase("clampToGround")) { newLine.setAltitudeMode(VectorObject.CLAMP_TO_GROUND); } else if (altitudeMode.equalsIgnoreCase("relativeToGround")) { newLine.setAltitudeMode(VectorObject.RELATIVE_TO_GROUND); } else { newLine.setAltitudeMode(VectorObject.ABSOLUTE); } if (hasExtendedData) { extendedData = placemarkTag.getSubtag("ExtendedData"); customDataFields = getExtendedData(extendedData); newLine.setCustomDataFields(customDataFields); } else { //use old extended data method for compatibility customDataFields = getCustomDataFields(gxTags); newLine.setCustomDataFields(customDataFields); } if (hasRegion) { Region region = loadRegion(placemarkTag.getSubtag("Region")); Visibility vis = convertRegionToVisibility(region); newLine.setVisibility(vis); } if (hasTimestamps) { timestamps = linestringTag.getSubtagContent("gx:Timestamps"); newLine.setTimestamps(timestamps); } if (coordinates.size() > 0) { return newLine; } else { System.err.println("Error in KmlImport.getLineString(VectorLayer, XMLTag) - No Coordinate Data For Object: " + objectName); return null; } } catch (Exception e) { System.err.println("Error in KmlImport.getLineString(VectorLayer, XMLTag) - " + e); return null; } } /** * Reads in LineStyle XML and returns a LineStyle Object. * * @param styleTag * @return */ public static LineStyle getLineStyle(XMLTag styleTag) { try { boolean hasStroke; Color fillColor, outlineColor; float lineWidth; int outline; LineStyle lineStyle; String lineStroke, styleID, styleTagName; XMLTag lineStyleTag; styleTagName = styleTag.getTagName(); styleID = styleTagName.substring(styleTagName.indexOf("id=\"") + 4, styleTagName.length() - 1); lineStyleTag = styleTag.getSubtag("LineStyle"); fillColor = ColorHelper.parseHexAlphabetical(lineStyleTag.getSubtagContent("color")); lineWidth = Float.parseFloat(lineStyleTag.getSubtagContent("width")); hasStroke = lineStyleTag.containsSubTag("gx:LineStroke"); if (lineStyleTag.containsSubTag("outline")) { outline = Integer.parseInt(lineStyleTag.getSubtagContent("outline")); } else { outline = 0; } if (hasStroke) { lineStroke = lineStyleTag.getSubtagContent("gx:LineStroke"); } else { lineStroke = LineStyle.SOLID; } lineStyle = new LineStyle(styleID, fillColor, lineWidth, lineStroke, true); if (outline == 1) { outlineColor = ColorHelper.parseHexAlphabetical(lineStyleTag.getSubtagContent("gx:outerColor")); lineStyle.setOutlineColor(outlineColor); lineStyle.setOutline(true); } else { lineStyle.setOutline(false); } return lineStyle; } catch (Exception e) { System.err.println("Error in KmlImport.getLineStyle(XMLTag) - " + e); return null; } } /** * Reads in KML for MapPoint and returns a MapPoint object. * * @param pointTag * @return */ public static MapPoint getMapPoint(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { try { ArrayList<XMLTag> gxTags; boolean hasExtendedData, hasRegion, hasTimestamps; CoordinateList<Coordinate> coordinates; HashMap<String,String> customDataFields; MapPoint newPoint; String coordinateString, description; String objectName, styleUrl, timestamps; XMLTag extendedData, pointTag; objectName = placemarkTag.getSubtagContent("name"); description = removeCDataTag(placemarkTag.getSubtagContent("description")); styleUrl = placemarkTag.getSubtagContent("styleUrl"); hasExtendedData = placemarkTag.containsSubTag("ExtendedData"); hasRegion = placemarkTag.containsSubTag("Region"); pointTag = placemarkTag.getSubtagStartsWith("Point"); coordinateString = pointTag.getSubtagContent("coordinates"); coordinates = getCoordinateList(nodeMap, coordinateString); hasTimestamps = pointTag.containsSubTag("gx:Timestamps"); gxTags = pointTag.getGxSubtags(); if (styleUrl.startsWith("#")) styleUrl = styleUrl.substring(1); newPoint = new MapPoint(objectName, styleUrl, description, coordinates); if (hasExtendedData) { extendedData = placemarkTag.getSubtag("ExtendedData"); customDataFields = getExtendedData(extendedData); newPoint.setCustomDataFields(customDataFields); } else { //use old extended data method for compatibility customDataFields = getCustomDataFields(gxTags); newPoint.setCustomDataFields(customDataFields); } if (hasRegion) { Region region = loadRegion(placemarkTag.getSubtag("Region")); Visibility vis = convertRegionToVisibility(region); newPoint.setVisibility(vis); } if (hasTimestamps) { timestamps = pointTag.getSubtagContent("gx:Timestamps"); newPoint.setTimestamps(timestamps); } if (coordinates.size() > 0) { return newPoint; } else { System.err.println("Error in KmlImport.getMapPoint(VectorLayer, XMLTag) - No Coordinate Data For Object: " + objectName); return null; } } catch (Exception e) { System.err.println("Error in KmlImport.getMapPoint(VectorLayer, XMLTag) - " + e); return null; } } /** * Reads in KML for MultiGeometry and returns a MultiGeometry object. * * @param layer * @param placemarkTag * @return */ public static MultiGeometry getMultiGeometry(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { ArrayList<XMLTag> gxTags; boolean hasExtendedData; HashMap<String,String> customDataFields; VectorObjectList<VectorObject> objects; MultiGeometry newMulti; String description, objectName; XMLTag extendedData, multiTag, placemarkNew; try { objectName = placemarkTag.getSubtagContent("name"); hasExtendedData = placemarkTag.containsSubTag("ExtendedData"); description = removeCDataTag(placemarkTag.getSubtagContent("description")); multiTag = placemarkTag.getSubtagStartsWith("MultiGeometry"); objects = new VectorObjectList<VectorObject>(); gxTags = multiTag.getGxSubtags(); //Load Objects if (multiTag.containsSubTag("PlaceMark")) { for (XMLTag tag: multiTag.getSubtagsByName("PlaceMark")) objects.add(loadPlacemark(nodeMap, layer, tag)); } else { for (XMLTag objectTag: multiTag.getSubtags()) { placemarkNew = new XMLTag("PlaceMark", objectTag); objects.add(loadPlacemark(nodeMap, layer, placemarkNew)); } } newMulti = new MultiGeometry(objectName, objects); if (hasExtendedData) { extendedData = placemarkTag.getSubtag("ExtendedData"); customDataFields = getExtendedData(extendedData); newMulti.setCustomDataFields(customDataFields); } else { //use old extended data method for compatibility customDataFields = getCustomDataFields(gxTags); newMulti.setCustomDataFields(customDataFields); } return newMulti; } catch (Exception e) { System.err.println("Error in KmlImport.getMultiGeometry(VectorLayer, XMLTag) - " + e); return null; } } /** * Creates a NetworkLayer object from the a NetworkLayer Xml tag. * * @param mapData * @param tag * @return */ public static NetworkLayer getNetworkLink(DigitalMap mapData, XMLTag tag) { boolean hasDescription, hasName, hasRegion; boolean hasRefreshInterval, hasRefreshMode; NetworkLayer networkLink; String address, description, name; String refreshInterval, refreshMode; XMLTag linkTag; try { hasDescription = tag.containsSubTag("description"); hasName = tag.containsSubTag("Name"); hasRegion = tag.containsSubTag("Region"); linkTag = tag.getSubtag("Link"); if (linkTag == null) linkTag = tag.getSubtag("Url"); address = linkTag.getSubtagContent("href"); hasRefreshInterval = linkTag.containsSubTag("refreshInterval"); hasRefreshMode = linkTag.containsSubTag("refreshMode"); if (hasName) { name = tag.getSubtagContent("name"); } else { name = "Network Link"; } networkLink = new NetworkLayer(name, address); if (mapData != null) networkLink.setParentMap(mapData); if (hasDescription) { description = tag.getSubtagContent("description"); networkLink.setLayerDescription(description); } if (hasRegion) { Region region = loadRegion(tag.getSubtag("Region")); Visibility vis = convertRegionToVisibility(region); networkLink.setRegion(region); } if (hasRefreshInterval) { refreshInterval = linkTag.getSubtagContent("refreshInterval"); networkLink.setRefreshInterval(Float.parseFloat(refreshInterval)); } if (hasRefreshMode) { refreshMode = linkTag.getSubtagContent("refreshMode"); networkLink.setRefreshMode(refreshMode); if (refreshMode.equalsIgnoreCase("onChange")) { networkLink.setRefreshMode(NetworkLayer.ON_CHANGE); } else if (refreshMode.equalsIgnoreCase("onInterval")) { networkLink.setRefreshMode(NetworkLayer.ON_INTERVAL); } else if (refreshMode.equalsIgnoreCase("onExpire")) { networkLink.setRefreshMode(NetworkLayer.ON_EXPIRE); } } return networkLink; } catch (Exception e) { System.err.println("Error in KmlImport.getNetworkLink(XmlTag) - " + e); return null; } } /** * Creates MapObjects from KML code. * * @param mapData - The Digital Map the will contain these objects * @param placeMarkTags - An ArrayList containing PlaceMark XMLTags * @return A collection of the new MapObjects created. */ public static VectorObjectList<VectorObject> getObjectsFromPlaceMarks(NodeMap nodeMap, VectorLayer layer, ArrayList<XMLTag> placeMarkTags) { try { VectorObjectList<VectorObject> newObjects; newObjects = new VectorObjectList<VectorObject>(); for (XMLTag currentPlaceMarkTag: placeMarkTags) { newObjects.add(loadPlacemark(nodeMap, layer, currentPlaceMarkTag)); } return newObjects; } catch (Exception e) { System.err.println("Error in KmlImport.getObjectsFromPlaceMarks(VectorLayer, ArrayList<XMLTag>) - " + e); return null; } } /** * Reads in KML for a Polygon and returns a Polygon Object. * * @param layer * @param placemarkTag * @return */ public static Polygon getPolygon(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { try { ArrayList<XMLTag> gxTags, innerBoundaryTags; boolean hasExtendedData, hasRegion, hasTimestamps; CoordinateList<Coordinate> coordinates; HashMap<String,String> customDataFields; Polygon newPolygon; String coordinateString, description; String objectName, styleUrl, timestamps; XMLTag extendedData, lineStringTag, outerBoundaryTag, polygonTag; objectName = placemarkTag.getSubtagContent("name"); description = removeCDataTag(placemarkTag.getSubtagContent("description")); styleUrl = placemarkTag.getSubtagContent("styleUrl"); hasExtendedData = placemarkTag.containsSubTag("ExtendedData"); hasRegion = placemarkTag.containsSubTag("Region"); polygonTag = placemarkTag.getSubtagStartsWith("Polygon"); outerBoundaryTag = polygonTag.getSubtag("outerBoundaryIs"); innerBoundaryTags = polygonTag.getSubtags("innerBoundaryIs"); lineStringTag = outerBoundaryTag.getSubtag("LinearRing"); coordinateString = lineStringTag.getSubtagContent("coordinates"); coordinates = getCoordinateList(nodeMap, coordinateString); hasTimestamps = polygonTag.containsSubTag("gx:Timestamps"); gxTags = polygonTag.getGxSubtags(); if (styleUrl.equals("")) styleUrl = placemarkTag.getTagValue(); if (styleUrl.startsWith("#")) styleUrl = styleUrl.substring(1); newPolygon = new Polygon(objectName, styleUrl, coordinates); if (hasExtendedData) { extendedData = placemarkTag.getSubtag("ExtendedData"); customDataFields = getExtendedData(extendedData); newPolygon.setCustomDataFields(customDataFields); } else { //use old extended data method for compatibility customDataFields = getCustomDataFields(gxTags); newPolygon.setCustomDataFields(customDataFields); } if (hasRegion) { Region region = loadRegion(placemarkTag.getSubtag("Region")); Visibility vis = convertRegionToVisibility(region); newPolygon.setVisibility(vis); } if (hasTimestamps) { timestamps = polygonTag.getSubtagContent("gx:Timestamps"); newPolygon.setTimestamps(timestamps); } for(XMLTag ibTag: innerBoundaryTags) newPolygon.addInnerBoundary(getInnerBoundary(nodeMap, layer, ibTag)); return newPolygon; } catch (Exception e) { System.err.println("Error in KmlImport.getPolygon(VectorLayer, XMLTag) - " + e); return null; } } /** * Reads PolygonStyle from KML and returns a PolygonStyle object. * * @param styleTag * @return */ public static PolygonStyle getPolygonStyle(XMLTag styleTag) { try { Color fillColor, outlineColor; int colorMode, fill, outline; PolygonStyle polygonStyle; String colorModeString, styleID, styleTagName; XMLTag polygonStyleTag; styleTagName = styleTag.getTagName(); styleID = styleTagName.substring(styleTagName.indexOf("id=\"") + 4, styleTagName.length() - 1); if (!styleTag.containsSubTag("PolyStyle")) { polygonStyleTag = styleTag; } else { polygonStyleTag = styleTag.getSubtag("PolyStyle"); } fillColor = ColorHelper.parseHexAlphabetical(polygonStyleTag.getSubtagContent("color")); colorModeString = polygonStyleTag.getSubtagContent("colorMode"); fill = Integer.parseInt(polygonStyleTag.getSubtagContent("fill")); outline = Integer.parseInt(polygonStyleTag.getSubtagContent("outline")); if (colorModeString.equalsIgnoreCase("normal")) { colorMode = ColorStyle.NORMAL; } else { colorMode = ColorStyle.RANDOM; } polygonStyle = new PolygonStyle(styleID, fillColor); polygonStyle.setColorMode(colorMode); if (fill == 1) { polygonStyle.setFill(true); } else { polygonStyle.setFill(false); } if (outline == 1) { if (polygonStyleTag.containsSubTag("gx:outlineColor")) { outlineColor = ColorHelper.parseHexAlphabetical(polygonStyleTag.getSubtagContent("gx:outlineColor")); polygonStyle.setOutlineColor(outlineColor); } polygonStyle.setOutline(true); } else { polygonStyle.setOutline(false); } return polygonStyle; } catch (Exception e) { System.err.println("Error in KmlImport.getPolygonStyle(XMLTag) - " + e); return null; } } /** * Reads in a gx:Track tag and converts it to a LineString * * @param layer * @param placemarkTag * @return */ public static LineString getTrack(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { ArrayList<XMLTag> times, coordinateTags; boolean hasExtendedData; Coordinate newCoordinate; CoordinateList<Coordinate> coordinates; HashMap<String,String> customDataFields; LineString newLine; String objectName, styleUrl; XMLTag extendedData, trackTag; try { objectName = placemarkTag.getSubtagContent("name"); styleUrl = placemarkTag.getSubtagContent("styleUrl"); trackTag = placemarkTag.getSubtag("gx:Track"); times = trackTag.getSubtagsByName("when"); coordinateTags = trackTag.getSubtagsByName("gx:coord"); coordinates = new CoordinateList<Coordinate>(); hasExtendedData = placemarkTag.containsSubTag("ExtendedData"); if (objectName == null) objectName = ""; for (int i = 0; i < coordinateTags.size(); i++) { XMLTag currentTag = coordinateTags.get(i); newCoordinate = new Coordinate(currentTag.getTagContent()); newCoordinate.setTimestamp(times.get(i).getTagContent()); coordinates.add(newCoordinate); nodeMap.put(newCoordinate); } newLine = new LineString(objectName, styleUrl, coordinates); if (hasExtendedData) { extendedData = placemarkTag.getSubtag("ExtendedData"); customDataFields = getExtendedData(extendedData); newLine.setCustomDataFields(customDataFields); } return newLine; } catch (Exception e) { System.err.println("Error in KmlImport.getTrack(VectorLayer, XMLTag) - " + e); return null; } } /** * Loads a layer from a file. * * @param openedMap * @param folderTag * @return */ public static void loadLayer(DigitalMap openedMap, NodeMap nodeMap, XMLTag folderTag) { try { ArrayList<XMLTag> tags; boolean hasGroundOverlay, layerLocked; VectorObject newObject; String layerDescription, layerName, timeSpanBegin, timeSpanEnd; VectorLayer newLayer; XMLTag descriptionTag, timeSpanTag; descriptionTag = folderTag.getSubtag("Description"); layerName = folderTag.getSubtagContent("Name"); layerDescription = removeCDataTag(folderTag.getSubtagContent("Description")); layerLocked = Boolean.parseBoolean(folderTag.getSubtagContent("gx:locked")); tags = folderTag.getTags("Placemark"); timeSpanTag = folderTag.getSubtag("TimeSpan"); hasGroundOverlay = folderTag.containsSubTag("GroundOverlay"); newLayer = new VectorLayer(layerName); //create new VectorLayer openedMap.addLayer(newLayer); //description uses HTML if (layerDescription.equals("") && (descriptionTag != null)) layerDescription = descriptionTag.getSubtagsAsString(); //load timespan if (timeSpanTag != null) { timeSpanBegin = timeSpanTag.getSubtagContent("begin"); timeSpanEnd = timeSpanTag.getSubtagContent("end"); newLayer.setTimeSpanBegin(timeSpanBegin); newLayer.setTimeSpanEnd(timeSpanEnd); } newLayer.setLocked(layerLocked); newLayer.setLayerDescription(layerDescription); for (XMLTag currentTag: tags) { if (currentTag.getTagName().equalsIgnoreCase("Placemark")) { newObject = loadPlacemark(nodeMap, newLayer, currentTag); if (newObject != null) newLayer.addObject(newObject); } //end placemark tag check } if (hasGroundOverlay) { for (XMLTag groundOverlayTag: folderTag.getSubtags("GroundOverlay")) newLayer.addOverlay(getGroundOverlay(groundOverlayTag)); } } catch (Exception e) { System.err.println("Error in KmlImport.loadLayer(XMLTag) - " + e); } } /** * Reads in a KML PlaceMark and returns a VectorObject representing it. * * @param layer * @param placemarkTag * @return */ public static VectorObject loadPlacemark(NodeMap nodeMap, VectorLayer layer, XMLTag placemarkTag) { VectorObject newObject; if (placemarkTag.getSubtagStartsWith("gx:Track") != null) { newObject = getTrack(nodeMap, layer, placemarkTag); } else if (placemarkTag.getSubtagStartsWith("MultiGeometry") != null) { newObject = getMultiGeometry(nodeMap, layer, placemarkTag); } else if (placemarkTag.getSubtagStartsWith("Polygon") != null) { newObject = getPolygon(nodeMap, layer, placemarkTag); } else if (placemarkTag.getSubtagStartsWith("Point") != null) { newObject = getMapPoint(nodeMap, layer, placemarkTag); } else if (placemarkTag.getSubtagStartsWith("LineString") != null) { newObject = getLineString(nodeMap, layer, placemarkTag); } else if (placemarkTag.getSubtagStartsWith("LinearRing") != null) { newObject = getLinearRing(nodeMap, layer, placemarkTag); } else { newObject = null; } return newObject; } /** * Creates a Region object from KML code. * * @param regionTag * @return */ public static Region loadRegion(XMLTag regionTag) { try { LatLonAltBox latLonAltBox; LevelOfDetail lod; Region newRegion; String regionName; XMLTag latLonAltBoxTag, lodTag; regionName = regionTag.getTagValue(); latLonAltBoxTag = regionTag.getSubtag("LatLonAltBox"); lodTag = regionTag.getSubtag("Lod"); lod = getLevelOfDetail(lodTag); latLonAltBox = getLatLonAltBox(latLonAltBoxTag); newRegion = new Region(regionName, latLonAltBox, lod); return newRegion; } catch (Exception e) { Logger.log(Logger.ERR, "Error in KmlImport.loadRegion(XMLTag) - " + e); return null; } } /** * Opens a KML file and returns a DigitalMap. * * @param fileKML * @return */ @Override public DigitalMap importAsMap(File mapFile, ProgressIndicator progressIndicator) throws IOException { DigitalMap newMap; String fileExtension, fileName; fileName = mapFile.getName(); fileExtension = fileName.substring(fileName.length() - 3); if (fileExtension.equalsIgnoreCase("kml")) { newMap = openKML(progressIndicator, mapFile, new NodeMap(2000)); } else if (fileExtension.equalsIgnoreCase("kmz")) { newMap = openKMZ(progressIndicator, mapFile); } else { throw new IOException("KMLImporter: Unsupported File Type."); } return newMap; } /** * Opens a KML file and returns a DigitalMap. * * @param progressBarPanel * @param fileKML * @param nodeMap * @return */ public static DigitalMap openKML(ProgressIndicator progressIndicator, File fileKML, NodeMap nodeMap) { boolean hasNetworkLink, zoomAltitude; ArrayList<XMLTag> networkLinks, tags; ColorStyle currentStyle; Coordinate lookAtCoordinate; DigitalMap openedMap; float mapLatitude, mapLongitude, zoomLevel; MapProjection projection; MapTheme mapTheme; String mapName; String currentTagName; VectorLayer currentLayer; XMLParser mapXMLParser; XMLTag documentTag; zoomLevel = 0.2f; zoomAltitude = false; currentLayer = new VectorLayer("New Layer"); if (progressIndicator == null) progressIndicator = new ProgressBarPanel(); //read in xml file try { mapXMLParser = new XMLParser(new FileReader(fileKML)); documentTag = mapXMLParser.parseDocument(); //get map data mapName = documentTag.getSubtagContent("Name"); tags = documentTag.getTagSubtags("LookAt"); mapLatitude = 0; mapLongitude = 0; for (XMLTag currentSubtag: tags) { progressIndicator.updateProgress("Reading Header", 10); if (currentSubtag.getTagName().equalsIgnoreCase("longitude")) { mapLongitude = Float.parseFloat(currentSubtag.getTagContent()); } else if (currentSubtag.getTagName().equalsIgnoreCase("latitude")) { mapLatitude = Float.parseFloat(currentSubtag.getTagContent()); } else if (currentSubtag.getTagName().equalsIgnoreCase("altitude")) { //TODO: write code to convert from eye at to the projection zoom. zoomAltitude = true; } else if (currentSubtag.getTagName().equalsIgnoreCase("gx:Zoomlevel")) { zoomLevel = Float.parseFloat(currentSubtag.getTagContent()); zoomAltitude = false; } } hasNetworkLink = documentTag.containsSubTag("NetworkLink"); lookAtCoordinate = new Coordinate(0, mapLatitude, mapLongitude); projection = new MercatorProjection(mapLatitude, mapLongitude, zoomLevel); openedMap = new DigitalMap(mapName, projection); openedMap.setLookAtCoordinate(lookAtCoordinate); openedMap.setMapFile(fileKML); //load styles mapTheme = new MapTheme("Map File Theme"); tags = documentTag.getTags("Style"); for (XMLTag currentTag: tags) { progressIndicator.updateProgress("Reading Styles", 25); currentTagName = currentTag.getTagName(); if (currentTagName.startsWith("Style")) { if (currentTag.getSubtag("BackColor") != null) { Color backColor = ColorHelper.parseHexAlphabetical(currentTag.getSubtagContent("BackColor")); mapTheme.setBackgroundColor(backColor); } else if (currentTag.getSubtag("BackColor") != null) { Color backColor = ColorHelper.parseHexAlphabetical("fff0f3f4"); mapTheme.setBackgroundColor(backColor); } else { currentStyle = getStyle(currentTag); if (currentStyle != null) mapTheme.addStyleElement(currentStyle); } } } openedMap.setTheme(mapTheme, null, progressIndicator); openedMap.getMapThemeManager().addTheme(mapTheme); //load layers or folders //TODO: currently dose not load nested layer, fix later! tags = documentTag.getTags("Folder"); progressIndicator.updateProgress("Reading Layers", 50); //Load placemarks if (tags.size() > 0) { for (XMLTag currentTag: tags) loadLayer(openedMap, nodeMap, currentTag); } else { //no folders / layers exist tags = documentTag.getTags("Placemark"); currentLayer = new VectorLayer("Default"); for (XMLTag currentTag: tags) { currentLayer.addObject(loadPlacemark(nodeMap, currentLayer, currentTag)); } openedMap.addLayer(currentLayer); } //read NetworkLinks if there are any if (hasNetworkLink) { networkLinks = documentTag.getSubtagsByName("NetworkLink"); for (XMLTag networkLink: networkLinks) { NetworkLayer newNetLayer = getNetworkLink(openedMap, networkLink); if (newNetLayer != null) { openedMap.addLayer(newNetLayer); } else { Logger.log(Logger.ERR, "Could Not open Network Link"); } } } LatLonAltBox bounds = openedMap.getBoundary(); //If view port posistion could not be read use NW corner. if (mapLatitude == 0 && mapLongitude == 0 && currentLayer != null) projection.setReference(bounds.getNorthWestCoordinate()); float southY = (float) projection.getY(bounds.getSouthWestCoordinate()); if (southY < 1) projection.setZoomLevel(southY * 600); openedMap.setNodeMap(nodeMap); progressIndicator.finish(); return openedMap; } catch (Exception e) { Logger.log(Logger.ERR, "Error in KmlImport.openKML(File) - " + e); return null; } } /** * Opens a KMZ file. * * @param fileKMZ * @return */ public static DigitalMap openKMZ(ProgressIndicator progressIndicator, File fileKMZ) { DigitalMap newMap; newMap = openKMZ(progressIndicator, fileKMZ, new NodeMap(2000)); return newMap; } /** * Opens a KMZ file. * * @param fileKMZ * @return */ public static DigitalMap openKMZ(ProgressIndicator progressIndicator, File fileKMZ, NodeMap nodeMap) { ArrayList<File> archiveFiles; boolean hasDocKML; byte[] buf; DigitalMap mainMap; File currentFile; FileInputStream fis; FileOutputStream fos; long length; String entryName; ZipInputStream zis; ZipEntry currentEntry; archiveFiles = new ArrayList<File>(); mainMap = new DigitalMap(); hasDocKML = false; try { fis = new FileInputStream(fileKMZ); zis = new ZipInputStream(new BufferedInputStream(fis)); while (zis.available() > 0) { currentEntry = zis.getNextEntry(); if (currentEntry != null) { currentFile = File.createTempFile(currentEntry.getName(), ""); currentFile.deleteOnExit(); archiveFiles.add(currentFile); fos = new FileOutputStream(currentFile); buf = new byte[1024]; while((length = zis.read(buf)) >= 0) fos.write(buf, 0, (int) length); fos.close(); entryName = currentEntry.getName(); if (entryName.equalsIgnoreCase("doc.kml")) { mainMap = openKML(progressIndicator, currentFile, nodeMap); hasDocKML = true; } else if (entryName.substring(entryName.length() - 3).equalsIgnoreCase("kml") && hasDocKML == false) { mainMap = openKML(progressIndicator, currentFile, nodeMap); } else { //System.out.println((currentEntry.getName())); } } } fis.close(); } catch (Exception e) { Logger.log(Logger.ERR, "Error in KmlImport.openKMZ - " + e); } return mainMap; } /** * Removes the CDataTag from the passed text. * * @param text * @return */ public static String removeCDataTag(String text) { String cleanText; cleanText = text.replace("<![CDATA[", ""); cleanText = cleanText.replace("]]>", ""); return cleanText; } /** * Imports objects from a given KML file and adds objects from the map to * the given VectorLayer. Any Network layers in the file will be added * directly to the given DigitalMap. * * @param mapFile The file containing the map to import. * @param nodeMap The NodeMap to add new Coordinates to. * @param layer The Layer to add imported objects to. * @param progressIndicator Optional, to display the progress of the import. */ @Override public void importToLayer(File mapFile, NodeMap nodeMap, Layer layer, ProgressIndicator progressIndicator) throws IOException { DigitalMap newMap; String fileExtension, fileName; fileName = mapFile.getName(); fileExtension = fileName.substring(fileName.length() - 3); if (fileExtension.equalsIgnoreCase("kml")) { newMap = openKML(progressIndicator, mapFile, nodeMap); } else if (fileExtension.equalsIgnoreCase("kmz")) { newMap = openKMZ(progressIndicator, mapFile, nodeMap); } else { throw new IOException("KMLImporter: Unsupported File Type."); } if (layer instanceof VectorLayer) { VectorLayer vecLayer = (VectorLayer) layer; try { /** Clear Overlays and Objects, this is useful because we might be * importing to a NetworkLayer and the items need to be cleared. * If we are importing to another layer type, it should be empty. */ vecLayer.clearOverlays(); vecLayer.getObjectList().clear(); //add any overlays and objects for (Layer l: newMap.getLayers()) { if (l instanceof NetworkLayer) { if (layer instanceof NetworkLayer) { vecLayer.addOverlays(((VectorLayer) l).getOverlays()); vecLayer.addAllObjects(((VectorLayer) l).getObjectList()); } else { //Add any NetworkLayers to the parent map //assume it has already been added // mapData.addLayer(layer); } } else if (l instanceof VectorLayer) { vecLayer.addOverlays(((VectorLayer) l).getOverlays()); vecLayer.addAllObjects(((VectorLayer) l).getObjectList()); } } //Transfer Styles ArrayList<ColorStyle> styles = newMap.getTheme().getAllStyles(); for (ColorStyle cs: styles) layer.getParentMap().getTheme().addStyleElement(cs); } catch (Exception e) { Logger.log(Logger.ERR, "Error in KmlImport.importToLayer(File, NodeMap, VectorLayer, ProgressBarPanel) - " + e); } } else { Logger.log(Logger.ERR, "Error in KmlImporter.importToLayer(File, NodeMap, Layer, ProgressIndicator) - Supplied Layer must be a VectorLayer."); } } }