/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.kml.decorator; import org.geoserver.kml.KmlEncodingContext; import org.geoserver.kml.utils.LookAtOptions; import org.geoserver.wms.WMSInfo; import com.vividsolutions.jts.geom.Envelope; import com.vividsolutions.jts.geom.Geometry; import de.micromata.opengis.kml.v_2_2_0.Document; import de.micromata.opengis.kml.v_2_2_0.Feature; import de.micromata.opengis.kml.v_2_2_0.Folder; import de.micromata.opengis.kml.v_2_2_0.LookAt; import de.micromata.opengis.kml.v_2_2_0.NetworkLink; import de.micromata.opengis.kml.v_2_2_0.Placemark; /** * Adds LookAt elements on Document, Folder and Placemark * * @author Andrea Aime - GeoSolutions */ public class LookAtDecoratorFactory implements KmlDecoratorFactory { @Override public KmlDecorator getDecorator(Class<? extends Feature> featureClass, KmlEncodingContext context) { // this decorator makes sense only for WMS if(!(context.getService() instanceof WMSInfo)) { return null; } if (Placemark.class.isAssignableFrom(featureClass)) { return new PlacemarkLookAtDecorator(); } else if (Folder.class.isAssignableFrom(featureClass) || NetworkLink.class.isAssignableFrom(featureClass)) { return new LayerLookAtDecorator(); } else if (Document.class.isAssignableFrom(featureClass)) { return new DocumentLookAtDecorator(); } else { return null; } } class DocumentLookAtDecorator implements KmlDecorator { @Override public Feature decorate(Feature feature, KmlEncodingContext context) { Document document = (Document) feature; Envelope bounds = context.getMapContent().getRenderingArea(); LookAt lookAt = buildLookAt(bounds, context.getLookAtOptions(), false); document.setAbstractView(lookAt); return document; } } class LayerLookAtDecorator implements KmlDecorator { @Override public Feature decorate(Feature feature, KmlEncodingContext context) { Envelope bounds = context.getCurrentLayer().getBounds(); LookAt lookAt = buildLookAt(bounds, context.getLookAtOptions(), false); feature.setAbstractView(lookAt); return feature; } } class PlacemarkLookAtDecorator implements KmlDecorator { @Override public Feature decorate(Feature feature, KmlEncodingContext context) { Placemark pm = (Placemark) feature; Geometry geometry = (Geometry) context.getCurrentFeature().getDefaultGeometry(); Envelope bounds = null; if (geometry != null) { bounds = geometry.getEnvelopeInternal(); } LookAt lookAt = buildLookAt(bounds, context.getLookAtOptions(), true); pm.setAbstractView(lookAt); return pm; } } public LookAt buildLookAt(Envelope bounds, LookAtOptions options, boolean forceBounds) { // get/build the target envelope Envelope lookAtEnvelope = bounds; Geometry lookAtGeometry = options.getLookAt(); if (!forceBounds && lookAtGeometry != null) { lookAtEnvelope = lookAtGeometry.getEnvelopeInternal(); } if (lookAtEnvelope == null || lookAtEnvelope.isNull()) { return null; } // compute the lookAt details double lon1 = lookAtEnvelope.getMinX(); double lat1 = lookAtEnvelope.getMinY(); double lon2 = lookAtEnvelope.getMaxX(); double lat2 = lookAtEnvelope.getMaxY(); double R_EARTH = 6.371 * 1000000; // meters double VIEWER_WIDTH = 22 * Math.PI / 180; // The field of view of the google maps // camera, in radians double[] p1 = getRect(lon1, lat1, R_EARTH); double[] p2 = getRect(lon2, lat2, R_EARTH); double[] midpoint = new double[] { (p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2, (p1[2] + p2[2]) / 2 }; midpoint = getGeographic(midpoint[0], midpoint[1], midpoint[2]); Double distance = options.getRange(); if (null == distance) { distance = distance(p1, p2); } double height = distance / (2 * Math.tan(VIEWER_WIDTH)); final Double tilt = options.getTilt() == null ? Double.valueOf(0) : options.getTilt(); final Double heading = options.getHeading() == null ? Double.valueOf(0) : options .getHeading(); final Double altitude = options.getAltitude() == null ? Double.valueOf(height) : options .getAltitude(); // build the lookat LookAt lookAt = new LookAt(); lookAt.setLongitude(midpoint[0]); lookAt.setLatitude(midpoint[1]); lookAt.setAltitude(altitude); lookAt.setRange(distance); lookAt.setTilt(tilt); lookAt.setHeading(heading); lookAt.setAltitudeMode(options.getAltitudeMode()); return lookAt; } private double[] getRect(double lat, double lon, double radius) { double theta = (90 - lat) * Math.PI / 180; double phi = (90 - lon) * Math.PI / 180; double x = radius * Math.sin(phi) * Math.cos(theta); double y = radius * Math.sin(phi) * Math.sin(theta); double z = radius * Math.cos(phi); return new double[] { x, y, z }; } private double[] getGeographic(double x, double y, double z) { double theta, phi, radius; radius = distance(new double[] { x, y, z }, new double[] { 0, 0, 0 }); theta = Math.atan2(Math.sqrt(x * x + y * y), z); phi = Math.atan2(y, x); double lat = 90 - (theta * 180 / Math.PI); double lon = 90 - (phi * 180 / Math.PI); return new double[] { (lon > 180 ? lon - 360 : lon), lat, radius }; } private double distance(double[] p1, double[] p2) { double dx = p1[0] - p2[0]; double dy = p1[1] - p2[1]; double dz = p1[2] - p2[2]; return Math.sqrt(dx * dx + dy * dy + dz * dz); } }