/******************************************************************************* * Copyright 2012 Geoscience Australia * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package au.gov.ga.earthsci.worldwind.common.layers.geonames; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.LatLon; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.render.GeographicText; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.WWIO; import java.awt.Color; import java.awt.Font; import java.io.File; import java.io.IOException; import java.net.URL; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import au.gov.ga.earthsci.worldwind.common.util.ColorFont; /** * Represents a single GeoName from the geoname.org database. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class GeoName implements GeographicText { /** * GeoName name */ public final String name; /** * GeoName id (unique) */ public final int geonameId; /** * GeoName location */ public final LatLon latlon; /** * GeoName feature class */ public final String featureClass; /** * GeoName feature code */ public final String featureCode; /** * Level in the GeoName hierarchy (globe is -1, continent is 0, country is * 1, etc) */ public final int level; private final VisibilityCalculator visibilityCalculator; private final Position position; private final ColorFont font; private final ColorFontProvider fontProvider; private GeoName parent; private Collection<GeoName> children; private static Object fileLock = new Object(); private static Map<Integer, GeoName> geonameMap = new HashMap<Integer, GeoName>(); private static Object mapLock = new Object(); public GeoName(String name, int geonameId, LatLon latlon, String featureClass, String featureCode, int level, ColorFontProvider fontProvider, VisibilityCalculator visibilityCalculator) { this.name = name; this.geonameId = geonameId; this.latlon = latlon; this.position = new Position(latlon, 0); this.featureClass = featureClass; this.featureCode = featureCode; this.level = level; this.fontProvider = fontProvider; this.font = fontProvider.get(featureCode); this.visibilityCalculator = visibilityCalculator; } private String cacheFilename() { return "GeoNames/" + (level + 1) + "/" + geonameId + ".xml"; } private URL findCacheFile() { return WorldWind.getDataFileStore().findFile(cacheFilename(), true); } private File newCacheFile() { return WorldWind.getDataFileStore().newFile(cacheFilename()); } public boolean cacheFileExists() { return findCacheFile() != null; } public void saveChildren(ByteBuffer buffer) throws IOException { synchronized (fileLock) { WWIO.saveBuffer(buffer, newCacheFile()); } } public void loadChildren() { children = null; URL url = findCacheFile(); if (url != null) { try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(false); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = null; synchronized (fileLock) { document = documentBuilder.parse(url.openStream()); } Collection<GeoName> geonames = parseDocument(document); if (geonames != null) { children = new HashSet<GeoName>(); for (GeoName geoname : geonames) { synchronized (mapLock) { GeoName mapped = geonameMap.get(geoname.geonameId); if (mapped == null || mapped.parent == this) { geoname.parent = this; children.add(geoname); geonameMap.put(geoname.geonameId, geoname); } else { geoname = mapped; double distance1 = VisibilityCalculatorImpl.latlonDistanceSquared(latlon, geoname.latlon); double distance2 = VisibilityCalculatorImpl.latlonDistanceSquared(geoname.parent.latlon, geoname.latlon); if (distance1 < distance2) { geoname.parent.getChildren().remove(geoname); children.add(geoname); geoname.parent = this; } } } } } } catch (Exception e) { Logging.logger().log(java.util.logging.Level.SEVERE, "Deleting corrupt GeoNames .xml file " + url, e); WorldWind.getDataFileStore().removeFile(url); } } } private Collection<GeoName> parseDocument(Document document) { NodeList resultsCount = document.getElementsByTagName("totalResultsCount"); if (resultsCount.getLength() <= 0) { return null; } Collection<GeoName> geonames = new ArrayList<GeoName>(); NodeList nodes = document.getElementsByTagName("geoname"); if (nodes != null) { for (int i = 0; i < nodes.getLength(); i++) { Node node = nodes.item(i); NodeList children = node.getChildNodes(); String name = null; String featureClass = null; String featureCode = null; Integer geonameId = null; Double lat = null; Double lon = null; for (int j = 0; j < children.getLength(); j++) { try { Node child = children.item(j); if (child.getNodeName().equals("name")) { name = child.getTextContent(); } else if (child.getNodeName().equals("fcl")) { featureClass = child.getTextContent(); } else if (child.getNodeName().equals("fcode")) { featureCode = child.getTextContent(); } else if (child.getNodeName().equals("lat")) { lat = Double.valueOf(child.getTextContent()); } else if (child.getNodeName().equals("lng")) { lon = Double.valueOf(child.getTextContent()); } else if (child.getNodeName().equals("geonameId")) { geonameId = Integer.valueOf(child.getTextContent()); } } catch (Exception e) { } } if (lat != null && lon != null && geonameId != null && name != null && name.length() > 0) { LatLon latlon = new LatLon(Angle.fromDegreesLatitude(lat), Angle.fromDegreesLongitude(lon)); GeoName geoname = new GeoName(name, geonameId, latlon, featureClass, featureCode, level + 1, fontProvider, visibilityCalculator); geonames.add(geoname); } } } return geonames; } public boolean loadedChildren() { return children != null; } public Collection<GeoName> getChildren() { return children; } @Override public Color getBackgroundColor() { return font.backgroundColor; } @Override public Color getColor() { return font.color; } @Override public Font getFont() { return font.font; } @Override public Position getPosition() { return position; } @Override public CharSequence getText() { return name; } @Override public boolean isVisible() { return visibilityCalculator.isVisible(this); } @Override public double getPriority() { return 0; } @Override @Deprecated public void setBackgroundColor(Color background) { } @Override @Deprecated public void setColor(Color color) { } @Override @Deprecated public void setFont(Font font) { } @Override @Deprecated public void setPosition(Position position) { } @Override @Deprecated public void setText(CharSequence text) { } @Override @Deprecated public void setVisible(boolean visible) { } @Override @Deprecated public void setPriority(double d) { } @Override public String toString() { return name + " (" + geonameId + ") " + position; } @Override public int hashCode() { return geonameId ^ 1234321; } }