Java tutorial
/* * Copyright 2009-2016 DigitalGlobe, Inc. * * 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 org.mrgeo.image; import org.apache.commons.lang3.ArrayUtils; import org.codehaus.jackson.JsonGenerationException; import org.codehaus.jackson.annotate.JsonIgnore; import org.codehaus.jackson.map.JsonMappingException; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.util.DefaultPrettyPrinter; import org.mrgeo.data.raster.RasterUtils; import org.mrgeo.utils.LongRectangle; import org.mrgeo.utils.tms.Bounds; import org.mrgeo.utils.tms.Pixel; import org.mrgeo.utils.tms.TMSUtils; import org.mrgeo.utils.tms.TileBounds; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.awt.image.DataBuffer; import java.io.*; import java.util.HashMap; import java.util.Map; /** * MrsPyramidMetada is the class that describes a MrGeo Pyramid. * The contents of the pyramid are generally rasters. The * metadata object describes geospatial bounds, geospatial * tiled bounds, and pixel bounds of images. * * A pyramid of imagery is a set of images produced from a * base zoom level and then all derived image levels from * the base. For example, a set of images are used to produce * zoom level 12. This builds out images in some datastore. * The images of 12 are used to create image for zoom level 11 * and then zoom level 11 is used for 10 and so on until zoom level * 1 is created. So, the pyramid exists from 12 to 1. * */ public class MrsPyramidMetadata implements Serializable { private static final Logger log = LoggerFactory.getLogger(MrsPyramidMetadata.class); // version private static final long serialVersionUID = 1L; /** * TileMetadata is a container of the multiple types of * bounds for tiled data. */ public static class TileMetadata implements Serializable { private static final long serialVersionUID = 1L; // tile bounds (min tx, min ty, max tx, max ty) public LongRectangle tileBounds = null; // hdfs path or accumulo table for the image min, max, mean pixel values public String name = null; // basic constructor public TileMetadata() { } } // end TileMetadata /* * start globals section */ protected String pyramid; // base hdfs path or accumulo table for the pyramid // NOTE: bounds, and pixel bounds are for the exact image data, they do // not include the extra "slop" around the edges of the tile. protected Bounds bounds; // The bounds of the image in decimal degrees. protected int tilesize; // tile width/height, in pixels protected int maxZoomLevel; // maximum zoom level (minimum pixel size) protected Map<String, String> tags = new HashMap<>(); // freeform k,v pairs of tags protected String protectionLevel = ""; /* * end globals section */ public MrsPyramidMetadata() { } public MrsPyramidMetadata(MrsPyramidMetadata copy) { this.pyramid = copy.pyramid; this.bounds = copy.bounds.clone(); this.tilesize = copy.tilesize; this.maxZoomLevel = copy.maxZoomLevel; this.tags.putAll(copy.tags); this.protectionLevel = copy.protectionLevel; this.bands = copy.bands; this.defaultValues = ArrayUtils.clone(copy.defaultValues); this.tileType = copy.tileType; if (copy.quantiles == null) { this.quantiles = null; } else { quantiles = new double[copy.quantiles.length][]; for (int b = 0; b < copy.quantiles.length; b++) { this.quantiles[b] = copy.quantiles[b].clone(); } } this.classification = copy.classification; this.resamplingMethod = copy.resamplingMethod; this.imageData = new ImageMetadata[copy.imageData.length]; for (int i = 0; i < copy.imageData.length; i++) { this.imageData[i] = new ImageMetadata(); if (copy.imageData[i] != null) { this.imageData[i].name = copy.imageData[i].name; if (copy.imageData[i].stats != null) { this.imageData[i].stats = ArrayUtils.clone(copy.imageData[i].stats); } if (copy.imageData[i].pixelBounds != null) { this.imageData[i].pixelBounds = new LongRectangle(copy.imageData[i].pixelBounds); } if (copy.imageData[i].tileBounds != null) { this.imageData[i].tileBounds = new LongRectangle(copy.imageData[i].tileBounds); } } } this.stats = ArrayUtils.clone(copy.stats); } /* * start get section */ public Bounds getBounds() { return bounds; } public String getName(final int zoomlevel) { if (imageData != null && zoomlevel < imageData.length && imageData[zoomlevel].name != null) { return imageData[zoomlevel].name; } return null; } public int getMaxZoomLevel() { return maxZoomLevel; } @JsonIgnore public String getPyramid() { return pyramid; } public LongRectangle getTileBounds(final int zoomlevel) { if (imageData != null) { if (zoomlevel < imageData.length) { return imageData[zoomlevel].tileBounds; } // If we have _some_ tilebounds, calculate the bounds for the higher level return getOrCreateTileBounds(zoomlevel); } return null; } public int getTilesize() { return tilesize; } public Map<String, String> getTags() { return tags; } @JsonIgnore public String getTag(final String tag) { return getTag(tag, null); } @JsonIgnore public String getTag(final String tag, final String defaultValue) { if (tags.containsKey(tag)) { return tags.get(tag); } return defaultValue; } public String getProtectionLevel() { return protectionLevel; } /** * Returns the quantiles for the specified band for this image. Note that the return value * can be null. */ @JsonIgnore public double[] getQuantiles(int band) { if (quantiles != null && band < getBands()) { return quantiles[band]; } return null; } public double[][] getQuantiles() { return quantiles; } /* * end get section */ /* * start set section */ public void setBounds(final Bounds bounds) { this.bounds = bounds; } public void setName(final int zoomlevel) { setName(zoomlevel, Integer.toString(zoomlevel)); } public void setName(final int zoomlevel, final String name) { if (imageData == null || zoomlevel > maxZoomLevel) { setMaxZoomLevel(zoomlevel); } imageData[zoomlevel].name = name; } public void setPyramid(final String pyramid) { this.pyramid = pyramid; } public void setTilesize(final int size) { tilesize = size; } public void setTags(final Map<String, String> tags) { // make a copy of the tags... this.tags = new HashMap<>(tags); } @JsonIgnore public void setTag(final String tag, final String value) { tags.put(tag, value); } public void setProtectionLevel(final String protectionLevel) { this.protectionLevel = protectionLevel; } public void setQuantiles(double[][] quantiles) { this.quantiles = quantiles; } @JsonIgnore public void setQuantiles(final int band, final double[] quantiles) { if (this.quantiles == null) { this.quantiles = new double[getBands()][]; } this.quantiles[band] = quantiles.clone(); } /* * end set section */ /** * Return true if there is data at each of the pyramid levels. */ public boolean hasPyramids() { int levels = 0; if (getMaxZoomLevel() > 0) { for (int i = 1; i <= getMaxZoomLevel(); i++) { if (getName(i) != null) { levels++; } } } return (levels == getMaxZoomLevel()); } /** * ImageMetadata is a container of the multiple types of * bounds for images. */ public static class ImageMetadata extends MrsPyramidMetadata.TileMetadata { private static final long serialVersionUID = 1L; // pixel bounds of the image (0, 0, width, height) public LongRectangle pixelBounds = null; // statistics of the image public ImageStats[] stats = null; // basic constructor public ImageMetadata() { } // For backward compatibility with older image pyramids that // still have a reference to the "image" property. It was renamed // to "name". public void setImage(final String image) { name = image; } } // end ImageMetadata /* * start globals section */ private ImageMetadata[] imageData = null; // data specific to a single // pyramid-level image private int bands = 0; // number of bands in the image private double[][] quantiles; // quantiles computed in each band for the entire image pyramid // default (pixel) value, by band. Geotools calls these defaults, but they are // really // the nodata value for each pixel private double[] defaultValues; private int tileType; // pixel type for the image private ImageStats[] stats; // min, max, mean, std dev of pixel values by band for the source resolution level image private Classification classification = Classification.Continuous; private String resamplingMethod; /* * end globals section */ // types public enum Classification { Categorical, Continuous } /* * start methods */ /** * Loading a metadata file from the local file system. The objects of * the file are stored in a json format. This enables the ObjectMapper * to parse out the values correctly. * * @param file metadata file on the local file system to load * @return a valid MrsPyramidMetadata object * @throws JsonGenerationException * @throws JsonMappingException * @throws IOException */ @Deprecated public static MrsPyramidMetadata load(final File file) throws IOException { final ObjectMapper mapper = new ObjectMapper(); final MrsPyramidMetadata metadata = mapper.readValue(file, MrsPyramidMetadata.class); // make sure the name of the pyramid is set correctly for where the // metadata file was pulled metadata.setPyramid("file://" + file.getParentFile().getAbsolutePath()); return metadata; } // end load - File /** * Loading metadata from an InputStream. The objects of * the file are stored in a json format. This enables the ObjectMapper * to parse out the values correctly. * * @param stream - the stream attached to the metadata input * @return a valid MrsPyramidMetadata object * @throws JsonGenerationException * @throws JsonMappingException * @throws IOException */ public static MrsPyramidMetadata load(final InputStream stream) throws IOException { final ObjectMapper mapper = new ObjectMapper(); return mapper.readValue(stream, MrsPyramidMetadata.class); } // end load - InputStream /* * Helper functions for types used in rasters and databuffers */ public static int toBytes(final int tiletype) { switch (tiletype) { case DataBuffer.TYPE_BYTE: { return 1; } case DataBuffer.TYPE_FLOAT: { return RasterUtils.FLOAT_BYTES; } case DataBuffer.TYPE_DOUBLE: { return RasterUtils.DOUBLE_BYTES; } case DataBuffer.TYPE_INT: { return RasterUtils.INT_BYTES; } case DataBuffer.TYPE_SHORT: { return RasterUtils.SHORT_BYTES; } case DataBuffer.TYPE_USHORT: { return RasterUtils.USHORT_BYTES; } } return 0; } // end toBytes public static int toTileType(final String tiletype) { if (tiletype.equals("Byte")) { return DataBuffer.TYPE_BYTE; } if (tiletype.equals("Float")) { return DataBuffer.TYPE_FLOAT; } if (tiletype.equals("Double")) { return DataBuffer.TYPE_DOUBLE; } if (tiletype.equals("Int")) { return DataBuffer.TYPE_INT; } if (tiletype.equals("Short")) { return DataBuffer.TYPE_SHORT; } if (tiletype.equals("UShort")) { return DataBuffer.TYPE_USHORT; } return DataBuffer.TYPE_UNDEFINED; } // end toTileType public static String toTileTypeText(final int tiletype) { switch (tiletype) { case DataBuffer.TYPE_BYTE: { return "Byte"; } case DataBuffer.TYPE_FLOAT: { return "Float"; } case DataBuffer.TYPE_DOUBLE: { return "Double"; } case DataBuffer.TYPE_INT: { return "Int"; } case DataBuffer.TYPE_SHORT: { return "Short"; } case DataBuffer.TYPE_USHORT: { return "UShort"; } } return "Undefined"; } // end toTileTypeText /* * end helper functions */ public int getBands() { return bands; } @JsonIgnore public double getPixelHeight(int zoom) { return TMSUtils.resolution(zoom, tilesize); } @JsonIgnore public double getPixelWidth(int zoom) { return TMSUtils.resolution(zoom, tilesize); } @JsonIgnore public double getDefaultValue(final int band) { if (band < getBands()) { return defaultValues[band]; } return Double.NaN; } @JsonIgnore public byte getDefaultValueByte(final int band) { if (band < getBands()) { return Double.valueOf(defaultValues[band]).byteValue(); } return 0; } @JsonIgnore public double getDefaultValueDouble(final int band) { return getDefaultValue(band); } @JsonIgnore public float getDefaultValueFloat(final int band) { if (band < getBands()) { return Double.valueOf(defaultValues[band]).floatValue(); } return Float.NaN; } @JsonIgnore public int getDefaultValueInt(final int band) { if (band < getBands()) { return Double.valueOf(defaultValues[band]).intValue(); } return 0; } @JsonIgnore public long getDefaultValueLong(final int band) { if (band < getBands()) { return Double.valueOf(defaultValues[band]).longValue(); } return 0; } public double[] getDefaultValues() { return defaultValues; } @JsonIgnore public byte[] getDefaultValuesByte() { final byte[] defaults = new byte[bands]; for (int i = 0; i < bands; i++) { defaults[i] = Double.valueOf(defaultValues[i]).byteValue(); } return defaults; } @JsonIgnore public double[] getDefaultValuesDouble() { return getDefaultValues(); } @JsonIgnore public float[] getDefaultValuesFloat() { final float[] defaults = new float[bands]; for (int i = 0; i < bands; i++) { defaults[i] = Double.valueOf(defaultValues[i]).floatValue(); } return defaults; } @JsonIgnore public short getDefaultValueShort(final int band) { if (band < getBands()) { return Double.valueOf(defaultValues[band]).shortValue(); } return 0; } @JsonIgnore public int[] getDefaultValuesInt() { final int[] defaults = new int[bands]; for (int i = 0; i < bands; i++) { defaults[i] = Double.valueOf(defaultValues[i]).intValue(); } return defaults; } @JsonIgnore public long[] getDefaultValuesLong() { final long[] defaults = new long[bands]; for (int i = 0; i < bands; i++) { defaults[i] = Double.valueOf(defaultValues[i]).longValue(); } return defaults; } @JsonIgnore public short[] getDefaultValuesShort() { final short[] defaults = new short[bands]; for (int i = 0; i < bands; i++) { defaults[i] = Double.valueOf(defaultValues[i]).shortValue(); } return defaults; } @JsonIgnore public Number[] getDefaultValuesNumber() { final Number[] defaults = new Number[bands]; for (int i = 0; i < bands; i++) { defaults[i] = defaultValues[i]; } return defaults; } public ImageMetadata[] getImageMetadata() { return imageData; } public LongRectangle getPixelBounds(final int zoomlevel) { if (imageData != null) { if (zoomlevel < imageData.length) { return imageData[zoomlevel].pixelBounds; } return getOrCreatePixelBounds(zoomlevel); } return null; } public ImageStats[] getImageStats(final int zoomlevel) { if (imageData != null) { if (zoomlevel < imageData.length) { return imageData[zoomlevel].stats; } // if we don't have this level, return the highest level available return imageData[imageData.length - 1].stats; } return null; } public ImageStats getImageStats(final int zoomlevel, int band) { if (imageData != null) { if (zoomlevel < imageData.length) { if (imageData[zoomlevel].stats != null && band < imageData[zoomlevel].stats.length) { return imageData[zoomlevel].stats[band]; } } if (imageData[imageData.length - 1].stats != null && band < imageData[imageData.length - 1].stats.length) { return imageData[imageData.length - 1].stats[band]; } } return null; } public ImageStats getStats(int band) { return (stats == null) ? null : stats[band]; } public ImageStats[] getStats() { return stats; } public LongRectangle getOrCreateTileBounds(final int zoomlevel) { if (imageData != null && zoomlevel < imageData.length) { return imageData[zoomlevel].tileBounds; } LongRectangle tilebounds = getTileBounds(zoomlevel); if (tilebounds == null) { TileBounds tb = TMSUtils.boundsToTile(bounds, zoomlevel, tilesize); tilebounds = new LongRectangle(tb.w, tb.s, tb.e, tb.n); } return tilebounds; } public LongRectangle getOrCreatePixelBounds(final int zoomlevel) { if (imageData != null && zoomlevel < imageData.length) { return imageData[zoomlevel].pixelBounds; } // TMSUtils.Bounds b = new TMSUtils.Bounds(bounds.getMinX(), bounds.getMinY(), // bounds.getMaxX(), bounds.getMaxY()); Pixel ll = TMSUtils.latLonToPixels(bounds.w, bounds.s, zoomlevel, tilesize); Pixel ur = TMSUtils.latLonToPixels(bounds.e, bounds.n, zoomlevel, tilesize); return new LongRectangle(0, 0, ur.px - ll.px, ur.py - ll.py); } public int getTileType() { return tileType; } public Classification getClassification() { return classification; } /** * Return the minimum and maximum values from the statistics for the image at the requested zoom * level * @param zoomLevel requested zoom level * @return array with position one containing the statistical minimum value and position two * containing the maximum value */ public double[] getExtrema(final int zoomLevel) { double[] extrema = new double[3]; ImageStats st = getImageStats(zoomLevel, 0); if (st != null) { extrema[0] = Math.max(0.0, st.min); extrema[1] = st.max; } else { log.warn("No statistics have been calculated on the requested image " + pyramid + "/" + imageData[zoomLevel].name + ". Using default range of 0.0 to 1.0..."); extrema[0] = 0.0; extrema[1] = 1.0; } return extrema; } public String getResamplingMethod() { return resamplingMethod; } public void setResamplingMethod(String resamplingMethod) { this.resamplingMethod = resamplingMethod; } public void save(final OutputStream stream) throws IOException { final ObjectMapper mapper = new ObjectMapper(); try { DefaultPrettyPrinter pp = new DefaultPrettyPrinter(); pp.indentArraysWith(new DefaultPrettyPrinter.Lf2SpacesIndenter()); //mapper.prettyPrintingWriter(pp).writeValue(stream, this); mapper.writer(pp).writeValue(stream, this); } catch (NoSuchMethodError e) { // if we don't have the pretty printer, just write the json mapper.writeValue(stream, this); } } /* * start set section */ public void setBands(final int bands) { this.bands = bands; } public void setDefaultValues(final double[] defaultValues) { this.defaultValues = defaultValues; } //public void setDefaultValues(final Number[] defaultValues) //{ // this.defaultValues = new double[defaultValues.length]; // for (int i = 0; i < defaultValues.length; i++) // { // this.defaultValues[i] = defaultValues[i].doubleValue(); // } //} public void setImageMetadata(final ImageMetadata[] metadata) { // this will make sure the size of the image metadata matches the zoom, with empty levels as needed if (metadata == null) { imageData = null; for (int i = 0; i <= maxZoomLevel; i++) { imageData = ArrayUtils.add(imageData, new ImageMetadata()); } return; } // this could be the case when reading the data in, but the maxzoom comes after the imagedata // in the JSON if (maxZoomLevel <= 0) { setMaxZoomLevel(metadata.length - 1); } imageData = metadata; if ((maxZoomLevel + 1) < imageData.length) { imageData = ArrayUtils.subarray(metadata, 0, maxZoomLevel + 1); } else if (maxZoomLevel > imageData.length) { for (int i = imageData.length; i <= maxZoomLevel; i++) { imageData = ArrayUtils.add(imageData, new ImageMetadata()); } } } public void setMaxZoomLevel(final int zoomlevel) { if (imageData == null) { for (int i = 0; i <= zoomlevel; i++) { imageData = ArrayUtils.add(imageData, new ImageMetadata()); } } else if (zoomlevel < maxZoomLevel) { imageData = ArrayUtils.subarray(imageData, 0, zoomlevel + 1); } else if (zoomlevel > maxZoomLevel) { for (int i = maxZoomLevel + 1; i <= zoomlevel; i++) { imageData = ArrayUtils.add(imageData, new ImageMetadata()); } } this.maxZoomLevel = zoomlevel; } public void setPixelBounds(final int zoomlevel, final LongRectangle pixelBounds) { if (imageData == null || zoomlevel > maxZoomLevel) { setMaxZoomLevel(zoomlevel); } imageData[zoomlevel].pixelBounds = pixelBounds; } public void setTileBounds(final int zoomlevel, final LongRectangle tileBounds) { if (imageData == null || zoomlevel > maxZoomLevel) { setMaxZoomLevel(zoomlevel); } imageData[zoomlevel].tileBounds = tileBounds; } public void setImageStats(final int zoomlevel, final ImageStats[] stats) { if (imageData == null || zoomlevel > maxZoomLevel) { setMaxZoomLevel(zoomlevel); } imageData[zoomlevel].stats = stats; } public void setStats(final ImageStats[] stats) { this.stats = stats; } public void setTileType(final int tileType) { this.tileType = tileType; } /* Note: Commented out because having two versions of the setter, one with @JsonIgnore * and one without resulted in tileType being serialized on some boxes and not in others * On the other hand, removing the @JsonIgnore resulted in * JsonMappingException: Conflicting setter definitions for property tileType * Since there were no users of this method, commenting out made sense. */ // public void setTileType(final String tileType) // { // this.tileType = toTileType(tileType); // } public void setClassification(Classification classification) { this.classification = classification; } } // end MrsPyramidMetadata