package nl.wldelft.fews.system.plugin.dataImport;

import com.sun.media.jai.codec.ByteArraySeekableStream;
import com.sun.media.jai.codec.SeekableStream;
import nl.wldelft.util.ColorUtils;
import nl.wldelft.util.FileUtils;
import nl.wldelft.util.IOUtils;
import nl.wldelft.util.coverage.Geometry;
import nl.wldelft.util.coverage.RegularGridGeometry;
import nl.wldelft.util.geodatum.GeoDatum;
import nl.wldelft.util.geodatum.GeoPoint;
import nl.wldelft.util.io.BinaryParser;
import nl.wldelft.util.io.VirtualInputDir;
import nl.wldelft.util.io.VirtualInputDirConsumer;
import nl.wldelft.util.timeseries.DefaultTimeSeriesHeader;
import nl.wldelft.util.timeseries.TimeSeriesContentHandler;

import javax.media.jai.JAI;
import javax.media.jai.RenderedOp;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.awt.image.renderable.ParameterBlock;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GrayscaleImageTimeSeriesParser implements BinaryParser<TimeSeriesContentHandler>, VirtualInputDirConsumer, FileFilter {
    private BufferedImage bufferedImage = null;
    private Graphics2D graphics = null;
    private int[] rgbs = null;
    private float[] values = null;
    private VirtualInputDir virtualInputDir = null;
    private String virtualFileName = null;
    private TimeSeriesContentHandler contentHandler = null;


    @Override
    public boolean accept(File pathname) {
        return getWorldFileExt(FileUtils.getFileExt(pathname)) != null;
    }

    @Override
    public void setVirtualInputDir(VirtualInputDir virtualInputDir) {
        this.virtualInputDir = virtualInputDir;
    }

    @Override
    public void parse(BufferedInputStream inputStream, String virtualFileName, TimeSeriesContentHandler contentHandler) throws Exception {
        this.virtualFileName = virtualFileName;
        this.contentHandler = contentHandler;
        DefaultTimeSeriesHeader header = new DefaultTimeSeriesHeader();
        header.setParameterId("image");
        header.setLocationId("image");
        contentHandler.setNewTimeSeriesHeader(header);
        if (contentHandler.isCurrentTimeSeriesHeaderForAllTimesRejected()) return;

        contentHandler.setTime(getTime(new File(virtualFileName).getName(), contentHandler.getDefaultTimeZone()));
        if (contentHandler.isCurrentTimeSeriesHeaderForCurrentTimeRejected()) return;

        loadImage(inputStream, virtualFileName);
        Geometry geometry = loadGeometry();
        contentHandler.setGeometry(geometry);
        contentHandler.setValueResolution(1.0f);
        contentHandler.setCoverageValues(values);
        contentHandler.applyCurrentFields();
    }

    private Geometry loadGeometry() throws Exception {
        Geometry res = contentHandler.getOverrulingGeometry();
        if (res == null) res = readWorldFile();

        if (res.getCols() != bufferedImage.getWidth()) {
            throw new Exception("Width of image file (" + bufferedImage.getWidth()
                    + ") differs from number of cols (" + res.getCols() + ") in grid required geometry or world file");
        }

        if (res.getRows() != bufferedImage.getHeight()) {
            throw new Exception("Height of image file (" + bufferedImage.getHeight()
                    + ") differs from number of rows (" + res.getRows() + ") in grid required geometry or world file");
        }
        return res;
    }

    private void loadImage(InputStream inputStream, String fileName) throws Exception {
        String ext = FileUtils.getFileExt(fileName);
        String jaiFormatId = getJaiFormatId(ext);

        if (jaiFormatId == null)
            throw new Exception("Unsupported bitmap format " + inputStream);

        ParameterBlock parameterBlock = new ParameterBlock();
        SeekableStream seekableStream = new ByteArraySeekableStream(IOUtils.readBytes(inputStream));
        try {
            parameterBlock.add(seekableStream);
            RenderedOp image = JAI.create(jaiFormatId, parameterBlock);
            try {
                checkBuffers(image.getWidth(), image.getHeight());
                graphics.clearRect(0, 0, image.getWidth(), image.getHeight());
                graphics.drawImage(image.getAsBufferedImage(), 0, 0, null);
                for (int i = 0; i < values.length; i++) {
                    int rgb = rgbs[i];
                    int a = rgb >>> 24 & 0xff;
                    if (a == 0) {
                        values[i] = Float.NaN;
                        continue;
                    }
                    int r = rgb >>> 16 & 0xff;
                    int g = rgb >>> 8 & 0xff;
                    int b = rgb >>> 0 & 0xff;

                    values[i] = (r + g + b) / 3;
                }
            } finally {
                image.dispose();
            }
        } finally {
            seekableStream.close();
        }
    }

    private void checkBuffers(int width, int height) {
        if (bufferedImage != null && bufferedImage.getWidth() == width && bufferedImage.getHeight() == height) return;

        bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        graphics = bufferedImage.createGraphics();
        graphics.setBackground(ColorUtils.TRANSPARANT_COLOR);
        DataBufferInt dataBufferInt = (DataBufferInt) bufferedImage.getRaster().getDataBuffer();
        rgbs = dataBufferInt.getData();
        values = new float[width * height];
    }

    public static String getWorldFileExt(String bitMapExt) {
        if (bitMapExt.equalsIgnoreCase("tif")) return "tfw";
        if (bitMapExt.equalsIgnoreCase("gif")) return "gfw";
        if (bitMapExt.equalsIgnoreCase("png")) return "pgw";
        if (bitMapExt.equalsIgnoreCase("bmp")) return "bpw";
        if (bitMapExt.equalsIgnoreCase("pcx")) return "pxw";
        if (bitMapExt.equalsIgnoreCase("jpg")) return "jgw";

        return null;
    }

    private static String getJaiFormatId(String bitmapExt) {
        if (bitmapExt.equalsIgnoreCase("tif")) return "tiff";
        if (bitmapExt.equalsIgnoreCase("gif")) return "gif";
        if (bitmapExt.equalsIgnoreCase("bmp")) return "bmp";
        if (bitmapExt.equalsIgnoreCase("png")) return "png";
        if (bitmapExt.equalsIgnoreCase("jpg")) return "jpeg";

        return null;
    }

    private RegularGridGeometry readWorldFile() throws Exception {
        String worldFileExt = getWorldFileExt(FileUtils.getFileExt(virtualFileName));
        String worldFile = FileUtils.getPathWithOtherExtension(virtualFileName, worldFileExt);
        if (!virtualInputDir.exists(worldFile))
            throw new FileNotFoundException("Can not file world file " + worldFile);

        String[] values = IOUtils.readAllLines(virtualInputDir.getReader(worldFile));
        double cellWidth = Double.parseDouble(values[0]);
        double cellHeight = -Double.parseDouble(values[3]);
        double x = Double.parseDouble(values[4]);
        double y = Double.parseDouble(values[5]);
        GeoDatum geoDatum = contentHandler.getDefaultGeoDatum();
        GeoPoint bitMapUpperLeft = geoDatum.createXYZ(x, y, 0);

        return RegularGridGeometry.create(geoDatum, bitMapUpperLeft, cellWidth, cellHeight, bufferedImage.getHeight(), bufferedImage.getWidth());
    }

    public static long getTime(String fileName, TimeZone timeZone) throws Exception {
        Pattern p = Pattern.compile("\\d{8}.\\d{4}");
        Matcher matcher = p.matcher(fileName);
        boolean successfull = matcher.find();
        if (!successfull)
            throw new Exception("File name should contain date time yyyyMMdd_HHmm " + fileName);

        String dateTimeText = matcher.group();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd" + dateTimeText.charAt(8) + "HHmm");
        dateFormat.setTimeZone(timeZone);
        try {
            return dateFormat.parse(dateTimeText).getTime();
        } catch (ParseException e) {
            throw new Exception("File name should contain valid date time yyyyMMdd_HHmm " + fileName);
        }
    }
}
  • No labels