/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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 org.esa.snap.timeseries.export.kmz; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.CrsGeoCoding; import org.esa.snap.core.datamodel.GeoCoding; import org.esa.snap.core.datamodel.GeoPos; import org.esa.snap.core.datamodel.ImageInfo; import org.esa.snap.core.datamodel.MapGeoCoding; import org.esa.snap.core.datamodel.PixelPos; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.dataop.maptransf.IdentityTransformDescriptor; import org.esa.snap.core.dataop.maptransf.MapTransformDescriptor; import org.esa.snap.core.ui.product.ProductSceneView; import org.esa.snap.jai.ImageManager; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.SnapDialogs; import org.esa.snap.rcp.windows.ProductSceneViewTopComponent; import org.esa.snap.timeseries.core.TimeSeriesMapper; import org.esa.snap.timeseries.core.timeseries.datamodel.AbstractTimeSeries; import org.esa.snap.timeseries.core.timeseries.datamodel.TimeCoding; import org.esa.snap.timeseries.export.util.TimeSeriesExportHelper; import org.esa.snap.util.io.SnapFileFilter; import org.esa.snap.util.kmz.KmlFeature; import org.esa.snap.util.kmz.KmlFolder; import org.esa.snap.util.kmz.KmlGroundOverlay; import org.esa.snap.util.kmz.KmzExporter; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.opengis.geometry.BoundingBox; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionRegistration; import org.openide.util.ContextAwareAction; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.NbBundle; import org.openide.util.Utilities; import org.openide.util.WeakListeners; import javax.swing.AbstractAction; import javax.swing.Action; import java.awt.event.ActionEvent; import java.awt.image.RenderedImage; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.zip.ZipOutputStream; import static org.esa.snap.timeseries.export.util.TimeSeriesExportHelper.*; @ActionID( category = "File", id = "ExportTimeBasedKmz" ) @ActionRegistration( displayName = "#CTL_ExportTimeBasedKmzName" ) @ActionReference(path = "Menu/File/Export", position = 500) @NbBundle.Messages({ "CTL_ExportTimeBasedKmzName=Time Series as Google Earth KMZ" }) public class ExportTimeBasedKmz extends AbstractAction implements ContextAwareAction, LookupListener{ private static final String HELP_ID = "exportTimeBasedKmz"; private static final String IMAGE_EXPORT_DIR_PREFERENCES_KEY = "user.image.export.dir"; private final SnapFileFilter kmzFileFilter = new SnapFileFilter("KMZ", "kmz", "KMZ - Google Earth File Format"); private int level = 2; private ProductSceneView view; public ExportTimeBasedKmz() { this(Utilities.actionsGlobalContext()); } public ExportTimeBasedKmz(Lookup lkp) { super(Bundle.CTL_ExportTimeBasedKmzName()); Lookup.Result<ProductSceneViewTopComponent> result = lkp.lookupResult(ProductSceneViewTopComponent.class); result.addLookupListener(WeakListeners.create(LookupListener.class, this, result)); setEnabled(false); } @Override public Action createContextAwareInstance(Lookup actionContext) { return new ExportTimeBasedKmz(actionContext); } @Override public void resultChanged(LookupEvent ev) { setEnabled(SnapApp.getDefault().getSelectedProductSceneView() != null); } @Override public void actionPerformed(ActionEvent e) { view = SnapApp.getDefault().getSelectedProductSceneView(); final GeoCoding geoCoding = view.getProduct().getGeoCoding(); boolean isGeographic = false; if (geoCoding instanceof MapGeoCoding) { MapGeoCoding mapGeoCoding = (MapGeoCoding) geoCoding; MapTransformDescriptor transformDescriptor = mapGeoCoding.getMapInfo() .getMapProjection().getMapTransform().getDescriptor(); String typeID = transformDescriptor.getTypeID(); if (typeID.equals(IdentityTransformDescriptor.TYPE_ID)) { isGeographic = true; } } else if (geoCoding instanceof CrsGeoCoding) { isGeographic = CRS.equalsIgnoreMetadata(geoCoding.getMapCRS(), DefaultGeographicCRS.WGS84); } if (isGeographic) { final File output = fetchOutputFile(view); if (output == null) { return; } final String title = "KMZ Export"; final ProgressMonitorSwingWorker worker = new KmzSwingWorker(title, output); worker.executeWithBlocking(); } else { String message = "Product must be in ''Geographic Lat/Lon'' projection."; SnapDialogs.showInformation(message, null); } } protected File fetchOutputFile(ProductSceneView sceneView) { TimeSeriesExportHelper.FileWithLevel fileWithLevel = getOutputFileWithLevelOption(sceneView.getRaster(), "Export time series as time based KMZ", "time_series_", IMAGE_EXPORT_DIR_PREFERENCES_KEY, kmzFileFilter, HELP_ID); level = fileWithLevel.level; return fileWithLevel.file; } private KmlFeature createKmlFeature() { if (view.isRGB()) { return null; } TimeSeriesMapper timeSeriesMapper = TimeSeriesMapper.getInstance(); AbstractTimeSeries timeSeries = timeSeriesMapper.getTimeSeries(view.getProduct()); List<Band> bands = timeSeries.getBandsForVariable( AbstractTimeSeries.rasterToVariableName(view.getRaster().getName())); if (bands.isEmpty()) { return null; } RasterDataNode refRaster = bands.get(0); final KmlFolder folder = new KmlFolder(refRaster.getName(), refRaster.getDescription()); for (RasterDataNode raster : bands) { final GeoCoding geoCoding = raster.getGeoCoding(); final PixelPos upperLeftPP = new PixelPos(0, 0); final PixelPos lowerRightPP = new PixelPos(raster.getSceneRasterWidth(), raster.getSceneRasterHeight()); final GeoPos upperLeftGP = geoCoding.getGeoPos(upperLeftPP, null); final GeoPos lowerRightGP = geoCoding.getGeoPos(lowerRightPP, null); double north = upperLeftGP.getLat(); double south = lowerRightGP.getLat(); double east = lowerRightGP.getLon(); double west = upperLeftGP.getLon(); if (geoCoding.isCrossingMeridianAt180()) { east += 360; } final BoundingBox referencedEnvelope = new ReferencedEnvelope(west, east, north, south, DefaultGeographicCRS.WGS84); TimeCoding timeCoding = timeSeries.getRasterTimeMap().get(raster); if (timeCoding != null) { final ProductData.UTC startTime = timeCoding.getStartTime(); final ProductData.UTC endTime = timeCoding.getEndTime(); final ImageManager imageManager = ImageManager.getInstance(); final ImageInfo imageInfo = raster.getImageInfo(ProgressMonitor.NULL); final RenderedImage levelImage = imageManager.createColoredBandImage(new RasterDataNode[]{raster}, imageInfo, level); final String name = raster.getName(); final KmlGroundOverlay groundOverlay = new KmlGroundOverlay(name, levelImage, referencedEnvelope, startTime, endTime); groundOverlay.setIconName(name + raster.getProduct().getRefNo()); folder.addChild(groundOverlay); } } return folder; } private class KmzSwingWorker extends ProgressMonitorSwingWorker { private final String title; private final File output; private static final int ONE_MEGABYTE = 1012 * 1024; KmzSwingWorker(String title, File output) { super(ExportTimeBasedKmz.this.view, title); this.title = title; this.output = output; } @Override protected Object doInBackground(ProgressMonitor pm) throws Exception { KmlFeature kmlFeature = createKmlFeature(); final FileOutputStream fileOutputStream = new FileOutputStream(output); try (ZipOutputStream zipStream = new ZipOutputStream(new BufferedOutputStream(fileOutputStream, 5 * ONE_MEGABYTE))) { final KmzExporter exporter = new KmzExporter(); exporter.export(kmlFeature, zipStream, pm); } return null; } @Override protected void done() { Throwable exception = null; try { get(); } catch (InterruptedException e) { exception = e; } catch (ExecutionException e) { exception = e.getCause(); } if (exception != null) { String message = String.format("Error occurred while exporting to KMZ.%n%s", exception.getMessage()); SnapDialogs.showError(title, message); } } } }