/******************************************************************************* * 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.View; import gov.nasa.worldwind.WorldWind; import gov.nasa.worldwind.avlist.AVKey; import gov.nasa.worldwind.avlist.AVListImpl; import gov.nasa.worldwind.geom.Angle; import gov.nasa.worldwind.geom.LatLon; import gov.nasa.worldwind.geom.Position; import gov.nasa.worldwind.geom.Sector; import gov.nasa.worldwind.geom.Vec4; import gov.nasa.worldwind.globes.Earth; import gov.nasa.worldwind.layers.AbstractLayer; import gov.nasa.worldwind.render.DrawContext; import gov.nasa.worldwind.render.GeographicText; import gov.nasa.worldwind.render.GeographicTextRenderer; import gov.nasa.worldwind.retrieve.HTTPRetriever; import gov.nasa.worldwind.retrieve.RetrievalPostProcessor; import gov.nasa.worldwind.retrieve.Retriever; import gov.nasa.worldwind.retrieve.URLRetriever; import gov.nasa.worldwind.util.Logging; import java.awt.Color; import java.awt.Font; import java.net.HttpURLConnection; import java.net.URL; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Comparator; import java.util.Queue; import java.util.concurrent.PriorityBlockingQueue; import au.gov.ga.earthsci.worldwind.common.util.ColorFont; import au.gov.ga.earthsci.worldwind.common.util.IterableProxy; /** * Place name layer which uses place data from geonames.org. Uses * level-of-detail to download levels in the GeoName hirarchy according to * camera altitude. * * @author Michael de Hoog (michael.dehoog@ga.gov.au) */ public class GeoNamesLayer extends AbstractLayer { private final static String GEONAMES_CHILDREN = "http://ws.geonames.org/children"; private final static int GEONAMES_GLOBE_ID = 6295630; private final static String GEONAMES_USERNAME = "gam3dv"; private GeoName topGeoName; private VisibilityCalculatorImpl visibilityCalculator = new VisibilityCalculatorImpl(); private Queue<GeoName> requestQ; private final GeographicTextRenderer nameRenderer = new GeographicTextRenderer(); private Object lock = new Object(); //TODO different attributes for different feature codes (fcode) public GeoNamesLayer() { setName("GeoNames"); setPickEnabled(false); ColorFontProvider fontProvider = setupFontProvider(); topGeoName = new GeoName(null, GEONAMES_GLOBE_ID, LatLon.ZERO, null, null, -1, fontProvider, visibilityCalculator); requestQ = new PriorityBlockingQueue<GeoName>(64, new Comparator<GeoName>() { @Override public int compare(GeoName o1, GeoName o2) { double distance1 = visibilityCalculator.distanceSquaredFromEye(o1); double distance2 = visibilityCalculator.distanceSquaredFromEye(o2); return distance1 > distance2 ? 1 : distance1 == distance2 ? 0 : -1; } }); } private ColorFontProvider setupFontProvider() { ColorFont def = new ColorFont(Font.decode("Arial-PLAIN-10"), Color.lightGray, Color.black); ColorFont continents = new ColorFont(Font.decode("Arial-BOLD-12"), new Color(255, 255, 240), Color.black); ColorFont countries = new ColorFont(Font.decode("Arial-BOLD-11"), Color.white, Color.black); ColorFont states = new ColorFont(Font.decode("Arial-BOLD-10"), Color.yellow, Color.black); ColorFont admin = new ColorFont(Font.decode("Arial-BOLD-10"), Color.orange, Color.black); ColorFont capitals = new ColorFont(Font.decode("Arial-BOLD-10"), Color.red, Color.black); ColorFont firstorder = new ColorFont(Font.decode("Arial-PLAIN-10"), Color.green, Color.black); ColorFont towns = new ColorFont(Font.decode("Arial-PLAIN-10"), Color.cyan, Color.black); ColorFontProvider fontProvider = new ColorFontProvider(def); fontProvider.put("CONT", continents); fontProvider.put("PCL", countries); fontProvider.put("PCLD", countries); fontProvider.put("PCLF", countries); fontProvider.put("PCLI", countries); fontProvider.put("PCLIX", countries); fontProvider.put("PCLS", countries); fontProvider.put("TERR", countries); fontProvider.put("ADM1", states); fontProvider.put("ADM2", admin); fontProvider.put("ADM3", admin); fontProvider.put("ADM4", admin); fontProvider.put("ADMD", admin); fontProvider.put("PPLC", capitals); fontProvider.put("PPLA", firstorder); fontProvider.put("PPL", towns); fontProvider.put("PPLG", towns); fontProvider.put("PPLL", towns); fontProvider.put("PPLQ", towns); fontProvider.put("PPLR", towns); fontProvider.put("PPLS", towns); fontProvider.put("PPLW", towns); fontProvider.put("PPLX", towns); return fontProvider; } @Override public void doRender(DrawContext dc) { int levels = calculateLevel(dc); Position eye = dc.getView().getEyePosition(); Sector sector = dc.getVisibleSector(); visibilityCalculator.setSector(sector); visibilityCalculator.setLevels(levels); visibilityCalculator.setEye(eye); synchronized (lock) { render(dc, topGeoName); } sendRequests(); } public void sendRequests() { GeoName geoname = requestQ.poll(); while (geoname != null && !WorldWind.getTaskService().isFull()) { WorldWind.getTaskService().addTask(new RequestTask(geoname)); geoname = requestQ.poll(); } requestQ.clear(); } private class RequestTask implements Runnable { private GeoName geoname; public RequestTask(GeoName geoname) { this.geoname = geoname; } @Override public void run() { if (geoname.cacheFileExists()) { loadChildren(geoname); } else { download(geoname); } } } private int calculateLevel(DrawContext dc) { double altitude = computeAltitudeAboveGround(dc); return (int) (Earth.WGS84_EQUATORIAL_RADIUS / altitude) + 1; } private double computeAltitudeAboveGround(DrawContext dc) { View view = dc.getView(); Position eyePosition = view.getEyePosition(); Vec4 surfacePoint = getSurfacePoint(dc, eyePosition.getLatitude(), eyePosition.getLongitude()); return view.getEyePoint().distanceTo3(surfacePoint); } private Vec4 getSurfacePoint(DrawContext dc, Angle latitude, Angle longitude) { Vec4 surfacePoint = dc.getSurfaceGeometry().getSurfacePoint(latitude, longitude); if (surfacePoint == null) surfacePoint = dc.getGlobe().computePointFromPosition( new Position(latitude, longitude, dc.getGlobe().getElevation(latitude, longitude))); return surfacePoint; } public void render(DrawContext dc, GeoName geoname) { if (visibilityCalculator.isVisible(geoname)) { if (!geoname.loadedChildren()) { requestChildren(geoname); } else { Collection<GeoName> children = geoname.getChildren(); for (GeoName child : children) { render(dc, child); } nameRenderer.render(dc, new IterableProxy<GeographicText>(children)); } } } public void requestChildren(GeoName geoname) { requestQ.add(geoname); } private void loadChildren(GeoName geoname) { synchronized (lock) { geoname.loadChildren(); } } private void download(GeoName geoname) { if (!WorldWind.getRetrievalService().isAvailable()) return; URL url = null; try { url = new URL(GEONAMES_CHILDREN + "?username=" + GEONAMES_USERNAME + "&geonameId=" + geoname.geonameId); } catch (Exception e) { e.printStackTrace(); return; } if (WorldWind.getNetworkStatus().isHostUnavailable(url)) return; Retriever retriever; DownloadPostProcessor dpp = new DownloadPostProcessor(geoname); if ("http".equalsIgnoreCase(url.getProtocol())) { retriever = new HTTPRetriever(url, dpp); } else { Logging.logger().severe("UnknownRetrievalProtocol: " + url.toString()); return; } // Apply any overridden timeouts. Integer cto = AVListImpl.getIntegerValue(this, AVKey.URL_CONNECT_TIMEOUT); if (cto != null && cto > 0) retriever.setConnectTimeout(cto); Integer cro = AVListImpl.getIntegerValue(this, AVKey.URL_READ_TIMEOUT); if (cro != null && cro > 0) retriever.setReadTimeout(cro); Integer srl = AVListImpl.getIntegerValue(this, AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT); if (srl != null && srl > 0) retriever.setStaleRequestLimit(srl); WorldWind.getRetrievalService().runRetriever(retriever); } private class DownloadPostProcessor implements RetrievalPostProcessor { private GeoName geoname; public DownloadPostProcessor(GeoName geoname) { this.geoname = geoname; } @Override public ByteBuffer run(Retriever retriever) { ByteBuffer buffer = getBuffer(retriever); if (buffer != null) { try { if (!geoname.cacheFileExists()) { geoname.saveChildren(buffer); } loadChildren(geoname); } catch (Exception e) { Logging.logger().log(java.util.logging.Level.SEVERE, "Error saving GeoNames .xml", e); } } return buffer; } public ByteBuffer getBuffer(Retriever retriever) { if (retriever == null) { String msg = Logging.getMessage("nullValue.RetrieverIsNull"); Logging.logger().severe(msg); throw new IllegalArgumentException(msg); } if (!retriever.getState().equals(Retriever.RETRIEVER_STATE_SUCCESSFUL)) return null; URLRetriever r = (URLRetriever) retriever; ByteBuffer buffer = r.getBuffer(); if (retriever instanceof HTTPRetriever) { HTTPRetriever htr = (HTTPRetriever) retriever; if (htr.getResponseCode() != HttpURLConnection.HTTP_OK) { return null; } } if (buffer == null) return null; String contentType = r.getContentType(); if (contentType != null && (contentType.contains("xml") || contentType.contains("html") || contentType.contains("text"))) { return buffer; } return null; } } @Override public void dispose() { super.dispose(); } }