/* * Data HUb Service (DHuS) - For Space data distribution. * Copyright (C) 2013,2014,2015,2016 European Space Agency (ESA) * Copyright (C) 2013,2014,2015,2016 GAEL Systems * Copyright (C) 2013,2014,2015,2016 Serco Spa * * This file is part of DHuS software sources. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package fr.gael.drb.cortex.topic.sentinel2; import java.util.Collection; import java.util.Iterator; import java.util.TreeMap; import java.util.TreeSet; import org.apache.log4j.Logger; import com.ibm.icu.math.BigDecimal; import fr.gael.drbx.image.DrbImage; import gov.nasa.worldwind.geom.coords.MGRSCoord; import gov.nasa.worldwind.geom.coords.TMCoord; import gov.nasa.worldwind.geom.coords.UTMCoord; public class DrbImageManager { /** * A logger for this class. */ static private Logger logger = Logger.getLogger(DrbImageManager.class); /** * The layout ordered as follwoed: * |--utm_zone * | | * | |--lat * | | |-lon | lon | lon * | |--lat * | |-lon | lon | lon * | * |--utm_zone * | * |--lat * | |-lon | lon | lon * |--lat * |-lon | lon | lon * * TreeMap implemetation is used to keep increasing orders of values */ private Zones layout = new Zones();; public DrbImageManager() { } protected void add (DrbImage image, Zones zones) { zones.put(image); } public void add (DrbImage image) { this.add (image, this.layout); } public void addAll (Collection<DrbImage> images) { layout.setCentralMeridian(layout.getCentralMeridian(images)); Iterator<DrbImage>it_images = images.iterator(); while (it_images.hasNext()) { DrbImage image = it_images.next(); if (!checkImage(image, true)) { logger.error("Image \"" + image.getName() + "\" not supported: skiped."); continue; } this.add (image, this.layout); } } public Bands[][] getMosaic () { // Computes the table size int table_width = layout.getImageWidth()+1; int table_height = layout.getImageHeight()+1; Bands table[][] = new Bands[table_height][table_width]; // Initialize first column with nothing values BigDecimal northing = null; for (int irow=1; irow<table_height; irow++) { northing = layout.getNextNorthing(northing); logger.info ("Northing: " + northing); Bands b = new Bands(); b.put(northing.toString(), null); table [irow][0]=b; } BigDecimal easting = null; for (int icol=1; icol<table_width; icol++) { easting = layout.getNextEasting(easting); logger.info ("Easting: " + easting); Bands b = new Bands(); b.put(easting.toString(), null); table [0][icol]=b; } // Loop on zones for (Integer izone: layout.keySet()) { for (int irow=1; irow<table_height; irow++) for (int icol=1; icol<table_width; icol++) { if (table[irow][icol] == null) { table[irow][icol] = layout.getBands(izone, BigDecimal.valueOf( Double.parseDouble(table [irow][0].firstKey())), BigDecimal.valueOf( Double.parseDouble(table [0][icol].firstKey()))); } } } return table; } /** * Extracts the band if from the image name (filename). This method manages * both S2 old and new compact formats. * @param name the image filename. * @return the band identifier matching 'B\d\d' */ String getBandIdFromName (String name) { String band_id = "no_band"; String b = null; // Case of compact format if (!name.matches("S2._.*")) b = name.substring(23, 26); else // case of Old format b = name.substring(56, 59); if (b.matches("B\\d\\d")) band_id = b; return band_id; } /** * Extracts the MGRS code from the image name (filename). This method manages * both S2 old and new compact formats. * @param name the image filename. * @return the MGRS string */ String getMGRSFromName (String name) { String mgrs = null; // Case of compact format if (!name.matches("S2._.*")) mgrs = name.substring(1, 6); else // case of Old format mgrs = name.substring(50, 55); return mgrs; } /** * Check if the given image is well supported by this algorithm. * @param image the image to check. * @return true if supported, false otherwise. */ boolean checkImage (DrbImage image, boolean verbose) { if ((image == null) || (image.getName() == null)) { if (verbose) logger.error("Wrong input image."); return false; } return checkFilename(image.getName(), verbose); } boolean checkFilename (String filename, boolean verbose) { try { String mgrs = getMGRSFromName(filename); if (mgrs==null) new NullPointerException("MGRS string not extracted."); MGRSCoord.fromString(mgrs); } catch (Exception e) { if (verbose) { logger.error("Wrong input image file (" + e.getMessage() + ")."); } return false; } return true; } BigDecimal getNextNorthing (BigDecimal current, Zones map) { BigDecimal ret = current; return ret; } /** * List of image bands TreeMap * key is the band identifier. * */ class Bands extends TreeMap<String, DrbImage> { private static final long serialVersionUID = 1L; public int put(DrbImage image) { String name = image.getName(); // Extracts the band_ID String band_id = getBandIdFromName(name); if (!containsKey(band_id)) { put(band_id, image); return 0; } else { DrbImage replace = get(band_id); logger.warn("Image " + name + " has the same geo-position as " + replace.getName()); return -1; } } } /** * List of columns TreeMap * key is the easting value * */ class Columns extends TreeMap<BigDecimal, Bands> { private static final long serialVersionUID = 1L; public int put(DrbImage image, BigDecimal easting) { if (!containsKey(easting)) put(easting, new Bands()); Bands bands = get(easting); return bands.put(image); } public Bands getBands (BigDecimal easting) { return get(easting); } } /** * List of rows TreeMap * key is the northing value. */ class Rows extends TreeMap<BigDecimal, Columns> { private static final long serialVersionUID = 1L; public int put (DrbImage image, BigDecimal northing, BigDecimal easting) { if (!containsKey(northing)) put(northing, new Columns()); Columns column = get(northing); return column.put(image, easting); } public Bands getBands (BigDecimal northing, BigDecimal easting) { Columns c = get(northing); if (c!=null) return c.getBands (easting); else return null; } } /** * List of UTM zones * key is the utm horizontal zone. * */ class Zones extends TreeMap<Integer, Rows> { private static final long serialVersionUID = 1L; TreeSet<BigDecimal> northings = new TreeSet<BigDecimal>(); TreeSet<BigDecimal> eastings = new TreeSet<BigDecimal>(); private double centralMeridian = 0; /** * The method orders the passed images by their location ordered by rows. * The sort algorithm is based on MGRS (Military grid reference system) * definition (See * https://en.wikipedia.org/wiki/Military_grid_reference_system for * details). * Tile ID is composed by the following pattern: _TXXXxx (5 x), these 'x' * define MGRS location as followed: * XXX: Grid Zone Designation (UTM zone + latitude letter C-X omitting * I and O) * xx: the 100,000-meter square identifier. 100km square inside the GZD. * The identification consists of a column letter (A–Z, omitting I * and O) followed by a row letter (A–V, omitting I and O). * * i.e: S2A_OPER_MSI_L1C_TL_SGS__20150621T042634_A005374_T17UPU_B01.jp2 * or S2A_OPER_PVI_L1C_TL_SGS__20150621T042634_A005374_T17UPU.jp2 * The MGRS designator is located at [50-55] * * The use of TreeMap ensure keys are lexicographically ordered and * keep in memory the key as the grid zone descriptor, row index and * column index in this reference. * @param image the image add * @return -1 if image insertion not possible, 0 otherwise. */ public int put(DrbImage image) { String name = image.getName(); String mgrs = getMGRSFromName(name); // Retrieve lat/lon location of this image TMCoord coord = Coordinates.tmFromMgrs(mgrs, getCentralMeridian()); // As UTM images are 100km/100km no need to have a more fine precision. BigDecimal northing = BigDecimal.valueOf( round(coord.getNorthing()/100000,0)*100000); BigDecimal easting = BigDecimal.valueOf( round(coord.getEasting()/100000,0)*100000); // Save image coordinates northings.add(northing); eastings.add(easting); // Residu of UTM Integer hzone=0; if (!containsKey(hzone)) { put(hzone, new Rows()); } Rows rows = get(hzone); return rows.put(image, northing, easting); } private double round (double value, int digit) { double precision = Math.pow(10, digit); return (double)(((int)((value*precision)+0.5)))/precision; } public int getImageWidth () { return eastings.size(); } public int getImageHeight () { return northings.size(); } public BigDecimal getFirstEasting () { return eastings.first(); } public BigDecimal getNextEasting (BigDecimal current) { if (current == null) return getFirstEasting(); Iterator<BigDecimal>it = eastings.iterator(); while (it.hasNext()) { BigDecimal cursor = it.next(); if (cursor.equals(current)) { if (it.hasNext()) return it.next(); else return null; } } return null; } public BigDecimal getFirstNorthing () { return northings.last(); } public BigDecimal getNextNorthing (BigDecimal current) { if (current == null) return getFirstNorthing(); Iterator<BigDecimal>it = northings.descendingIterator(); while (it.hasNext()) { BigDecimal cursor = it.next(); if (cursor.equals(current)) { if (it.hasNext()) return it.next(); else return null; } } return null; } public Bands getBands (Integer zone, BigDecimal northing, BigDecimal easting) { Rows rows = get(zone); return rows.getBands (northing, easting); } public double getCentralMeridian() { return centralMeridian; } public void setCentralMeridian(double centralMeridian) { this.centralMeridian = centralMeridian; } /** * Computes the central meridian related to the passed image * @return */ public double getCentralMeridian(Collection<DrbImage> images) { TreeMap<Integer, UTMCoord>zones = new TreeMap<Integer, UTMCoord>(); for (DrbImage image:images) { // If image not supported, try next one... if (!checkImage(image, false)) continue; String name = image.getName(); String mgrs = getMGRSFromName(name); UTMCoord coord = Coordinates.utmFromMgrs(mgrs); zones.put(coord.getZone(), coord); } // Case of no image/no zone available. if (zones.isEmpty()) return 0.0; int zone_cursor = zones.size()/2; Integer zone = zones.keySet().iterator().next(); Iterator<Integer>zone_it = zones.keySet().iterator(); while ((zone_cursor>0) && (zone_it.hasNext())) { zone = zone_it.next(); zone_cursor--; } UTMCoord the_coord = zones.get(zone); return the_coord.getCentralMeridian().radians; } } }