/* * Copyright (c) 2016 Fraunhofer IGD * * All rights reserved. This program and the accompanying materials are made * available under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution. If not, see <http://www.gnu.org/licenses/>. * * Contributors: * Fraunhofer IGD <http://www.igd.fraunhofer.de/> */ package de.fhg.igd.mapviewer.server.wms.overlay; import java.awt.AlphaComposite; import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage; import java.io.InputStream; import java.net.Proxy; import java.net.URI; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Set; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.jdesktop.swingx.graphics.GraphicsUtilities; import org.jdesktop.swingx.mapviewer.GeoConverter; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.jdesktop.swingx.mapviewer.GeotoolsConverter; import org.jdesktop.swingx.mapviewer.PixelConverter; import de.fhg.igd.geom.BoundingBox; import de.fhg.igd.mapviewer.AbstractTileOverlayPainter; import de.fhg.igd.mapviewer.MapKitTileOverlayPainter; import de.fhg.igd.mapviewer.server.wms.Messages; import de.fhg.igd.mapviewer.server.wms.WMSConfiguration; import de.fhg.igd.mapviewer.server.wms.capabilities.WMSBounds; import de.fhg.igd.mapviewer.server.wms.capabilities.WMSCapabilities; import de.fhg.igd.mapviewer.server.wms.capabilities.WMSCapabilitiesException; import de.fhg.igd.mapviewer.server.wms.capabilities.WMSUtil; import eu.esdihumboldt.util.http.ProxyUtil; /** * Tile overlay displaying WMS data * * @author Simon Templer */ public class WMSTileOverlay extends MapKitTileOverlayPainter { private static final Log log = LogFactory.getLog(WMSTileOverlay.class); /** * The preferences */ private static final Preferences PREF_OVERLAYS = Preferences .userNodeForPackage(WMSTileOverlay.class).node("overlays"); //$NON-NLS-1$ private static final Set<String> supportedFormats = new LinkedHashSet<String>(); static { // order is important, png is preferred supportedFormats.add("image/png"); //$NON-NLS-1$ supportedFormats.add("image/gif"); //$NON-NLS-1$ supportedFormats.add("image/jpeg"); //$NON-NLS-1$ } private PixelConverter lastConverter = null; /** * The WMS client configuration */ private final WMSConfiguration configuration = new WMSConfiguration() { @Override protected Preferences getPreferences() { return PREF_OVERLAYS; } }; private volatile WMSCapabilities capabilities = null; /** * Default constructor */ public WMSTileOverlay() { super(4); } /** * Constructor * * @param name the configuration name */ public WMSTileOverlay(String name) { this(); if (!configuration.load(name)) { throw new IllegalArgumentException("Error loading WMS configuration"); //$NON-NLS-1$ } } /** * @return the configuration */ public WMSConfiguration getConfiguration() { return configuration; } /** * @see AbstractTileOverlayPainter#getMaxOverlap() */ @Override protected int getMaxOverlap() { // no overlapping return 0; } /** * @see AbstractTileOverlayPainter#repaintTile(int, int, int, int, * PixelConverter, int) */ @Override public BufferedImage repaintTile(int posX, int posY, int width, int height, PixelConverter converter, int zoom) { // the first converter isn't regarded as a new converter because it's // always the empty map boolean isNewConverter = lastConverter != null && !converter.equals(lastConverter); lastConverter = converter; if (!converter.supportsBoundingBoxes()) { if (isNewConverter) { handleError(Messages.WMSTileOverlay_0 + configuration.getName() + Messages.WMSTileOverlay_1); } return null; } synchronized (this) { if (capabilities == null) { try { capabilities = WMSUtil.getCapabilities(configuration.getBaseUrl()); } catch (WMSCapabilitiesException e) { log.error("Error getting WMS capabilities"); //$NON-NLS-1$ } } } if (capabilities != null) { int mapEpsg = converter.getMapEpsg(); WMSBounds box; synchronized (this) { if (capabilities.getSupportedSRS().contains("EPSG:" + mapEpsg)) { //$NON-NLS-1$ // same SRS supported } else { // SRS not supported if (isNewConverter) { StringBuilder message = new StringBuilder(); message.append(Messages.WMSTileOverlay_2); message.append(configuration.getName()); message.append(Messages.WMSTileOverlay_3); boolean init = true; for (String srs : capabilities.getSupportedSRS()) { if (init) { init = false; } else { message.append(", "); //$NON-NLS-1$ } message.append(srs); } handleError(message.toString()); } return null; } box = WMSUtil.getBoundingBox(capabilities, mapEpsg); } String srs = box.getSRS(); if (srs.startsWith("EPSG:")) { //$NON-NLS-1$ // determine format String format = null; Iterator<String> itFormat = supportedFormats.iterator(); synchronized (this) { while (format == null && itFormat.hasNext()) { String supp = itFormat.next(); if (capabilities.getFormats().contains(supp)) { format = supp; } } } if (format == null) { // no compatible format return null; } try { // check if tile lies within the bounding box int epsg = Integer.parseInt(srs.substring(5)); GeoPosition topLeft = converter.pixelToGeo(new Point(posX, posY), zoom); GeoPosition bottomRight = converter .pixelToGeo(new Point(posX + width, posY + height), zoom); // WMS bounding box BoundingBox wms = new BoundingBox(box.getMinX(), box.getMinY(), -1, box.getMaxX(), box.getMaxY(), 1); GeoConverter geotools = GeotoolsConverter.getInstance(); GeoPosition bbTopLeft = geotools.convert(topLeft, epsg); GeoPosition bbBottomRight = geotools.convert(bottomRight, epsg); double minX = Math.min(bbTopLeft.getX(), bbBottomRight.getX()); double minY = Math.min(bbTopLeft.getY(), bbBottomRight.getY()); double maxX = Math.max(bbTopLeft.getX(), bbBottomRight.getX()); double maxY = Math.max(bbTopLeft.getY(), bbBottomRight.getY()); BoundingBox tile = new BoundingBox(minX, minY, -1, maxX, maxY, 1); // check if bounding box and tile overlap if (wms.intersectsOrCovers(tile) || tile.covers(wms)) { WMSBounds bounds; if (epsg == mapEpsg) { bounds = new WMSBounds(srs, minX, minY, maxX, maxY); } else { // determine bounds for request minX = Math.min(topLeft.getX(), bottomRight.getX()); minY = Math.min(topLeft.getY(), bottomRight.getY()); maxX = Math.max(topLeft.getX(), bottomRight.getX()); maxY = Math.max(topLeft.getY(), bottomRight.getY()); bounds = new WMSBounds("EPSG:" + mapEpsg, minX, minY, maxX, maxY); //$NON-NLS-1$ } URI uri; synchronized (this) { uri = WMSUtil.getMapURI(capabilities, configuration, width, height, bounds, null, format, true); } Proxy proxy = ProxyUtil.findProxy(uri); InputStream in = uri.toURL().openConnection(proxy).getInputStream(); BufferedImage image = GraphicsUtilities.loadCompatibleImage(in); // apply transparency to the image BufferedImage result = GraphicsUtilities.createCompatibleTranslucentImage( image.getWidth(), image.getHeight()); Graphics2D g = result.createGraphics(); try { AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.SRC, 0.5f); g.setComposite(ac); g.drawImage(image, 0, 0, null); } finally { g.dispose(); } return result; } } catch (Throwable e) { log.warn("Error painting WMS overlay", e); //$NON-NLS-1$ } } } return null; } /** * Handle errors messages * * @param message the error message */ private void handleError(final String message) { final Display display = PlatformUI.getWorkbench().getDisplay(); display.asyncExec(new Runnable() { @Override public void run() { MessageDialog.openWarning(display.getActiveShell(), Messages.WMSTileOverlay_5, message); } }); } /** * Remove the configuration with the given name * * @param name the name * * @return if removing the configuration succeeded */ public static boolean removeConfiguration(String name) { try { PREF_OVERLAYS.node(name).removeNode(); return true; } catch (BackingStoreException e) { log.error("Error removing configuration " + name, e); //$NON-NLS-1$ return false; } } /** * Get the names of the existing configurations * * @return the configuration names */ public static String[] getConfigurationNames() { try { return PREF_OVERLAYS.childrenNames(); } catch (BackingStoreException e) { return new String[] {}; } } }