<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">package usda.weru.gis.gui;

import java.awt.AlphaComposite;
import static java.awt.AlphaComposite.SRC_OVER;
import java.awt.Composite;
import org.locationtech.jts.geom.Coordinate;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import org.apache.log4j.Logger;
import org.geotools.data.memory.MemoryDataStore;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.map.Layer;
import org.geotools.map.MapContent;
import org.geotools.map.MapLayerListEvent;
import org.geotools.map.MapLayerListListener;
import org.geotools.map.MapViewport;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.label.LabelCacheImpl;
import org.geotools.renderer.lite.LabelCache;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.util.factory.Hints;
//import usda.weru.gis.render.StreamingRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.opengis.feature.Feature;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.openide.util.Exceptions;
import static usda.weru.gis.gui.MapViewer.WepsLayerOpacityKey;

/**
 * handles user's changes to the map, such as zooming and panning
 * * @author Joseph A. Levin &lt;joelevin@weru.ksu.edu&gt;
 */
public class MapRenderLayer extends JComponent implements Runnable, MapController, MapLayerListListener {

    private static final long serialVersionUID = 1L;
    public static final String PROP_ZOOMFACTOR = "zoomFactor";
    public static final String PROP_PANOFFSET = "panOffset";
    private MapContent contentShapesAboveImagery;
    private MapContent contentShapesBelowImagery;
    private MapContent contentImageryWms;
    private MapContent contentImageryWmsOsm;
    private StreamingRenderer rendererShapesBelowImagery;
    private StreamingRenderer rendererShapesAboveImagery;
    private StreamingRenderer rendererImageryWms;
    private StreamingRenderer rendererImageryWmsOsm;
    Raster rasterDataImagery;
    Raster rasterDataMap;
    BufferedImage imageBufferImageryWms;
    BufferedImage imageBufferImageryWmsOsm;
    boolean imageryWmsOsmOverZoomLimit;
    private final LabelCache c_labelCache;
    private Coordinate imageCenter;
    private int zoomFactor;
    private int c_zoomValue;
    private BufferedImage c_bufferImage;
    private double c_bufferFactor = .3d;
    private double c_bufferZoomFactor;
    private Coordinate c_bufferCenter;
    private static final Object RENDER_LOCK = new Object();
    private static final Object BUFFER_LOCK = new Object();
    private final ExecutorService c_renderService;
    private Future&lt;?&gt; c_renderTask;
    private MemoryDataStore c_highlight;
    private Layer c_highlightLayer;
    private boolean c_panning;
    private final double c_zoomAdjustment = 2d;    
    private boolean renderEnabled;    
    private MapViewer c_viewer;
    private Map&lt;String, Object&gt; hints;    
    AffineTransform prevTransform;
    
    static int zoomFactorMin = 5;
    static int zoomFactorMax = 50000;
    static int zoomMin = 0;
    static int zoomMax = 100;
    static int zoomInc = 2;
//    static int zoomAdj = zoomFactorMin - (1 * 4); // (((int)Math.pow(2.,(double)0)) * 4)
    static int zoomAdj = 5; // (((int)Math.pow(2.,(double)0)) * 4)


    public MapRenderLayer() {
        renderEnabled = false;
        addComponentListener(new InternalComponentListener());

        InternalPanAction panAction = new InternalPanAction();
        addMouseListener(panAction);
        addMouseMotionListener(panAction);

        InternalZoomAction zoomAction = new InternalZoomAction();
        addMouseWheelListener(zoomAction);

        c_renderService = Executors.newSingleThreadExecutor();

        imageCenter = new Coordinate(0, 0);
        setPreferredSize(new Dimension(500, 600));

        hints = new HashMap&lt;&gt;();
        //hints.put(StreamingRenderer.SCALE_COMPUTATION_METHOD_KEY, StreamingRenderer.SCALE_OGC);
        hints.put(StreamingRenderer.TEXT_RENDERING_KEY, StreamingRenderer.TEXT_RENDERING_STRING);
        // it looks like the 'optimized data loading' option no longer exists -max
        //hints.put(StreamingRenderer.OPTIMIZED_DATA_LOADING_KEY, true);
        hints.put(StreamingRenderer.ADVANCED_PROJECTION_HANDLING_KEY, true);
        hints.put("renderingBuffer", 20);
        c_labelCache = new LabelCacheImpl();
        hints.put(StreamingRenderer.LABEL_CACHE_KEY, c_labelCache);

        prevTransform = null;
        rasterDataImagery = null;
        imageBufferImageryWms = null;
        imageBufferImageryWmsOsm = null;
        
        contentShapesAboveImagery = null;
        contentShapesBelowImagery = null;
        contentImageryWms = null;
        contentImageryWmsOsm = null;
        rendererShapesAboveImagery = null;
        rendererShapesBelowImagery = null;
        rendererImageryWms = null;
        rendererImageryWmsOsm = null;
        
        imageryWmsOsmOverZoomLimit = false;
    }

    public void setContentShapesAboveImagery(MapContent content)  {
        contentShapesAboveImagery = content;
        contentShapesAboveImagery.addMapLayerListListener(this);
        
        if (rendererShapesAboveImagery == null) {
            rendererShapesAboveImagery = new StreamingRenderer();
            rendererShapesAboveImagery.setRendererHints(hints);
        }
        rendererShapesAboveImagery.setMapContent(content);
    }

    public void setContentShapesBelowImagery(MapContent content)  {
        contentShapesBelowImagery = content;
        contentShapesBelowImagery.addMapLayerListListener(this);
        
        if (rendererShapesBelowImagery == null) {
            rendererShapesBelowImagery = new StreamingRenderer();
            rendererShapesBelowImagery.setRendererHints(hints);
        }
        rendererShapesBelowImagery.setMapContent(content);
    }

    public void setContentImageryWms(MapContent content) {
        contentImageryWms = content;
        contentImageryWms.addMapLayerListListener(this);
        
        if (rendererImageryWms == null) {
            rendererImageryWms = new StreamingRenderer();
            rendererImageryWms.setRendererHints(hints);
        }
        rendererImageryWms.setMapContent(content);
    }

    public void setContentImageryWmsOsm(MapContent content) {
        contentImageryWmsOsm = content;
        contentImageryWmsOsm.addMapLayerListListener(this);
        
        if (rendererImageryWmsOsm == null) {
            rendererImageryWmsOsm = new StreamingRenderer();
            rendererImageryWmsOsm.setRendererHints(hints);
        }
        rendererImageryWmsOsm.setMapContent(content);
    }

    public MapContent getContentShapesAboveImagery() {
        return contentShapesAboveImagery;
    }

    @Override
    public void setCenter(Coordinate center) {
        imageCenter = center;
        repaint();
    }

    @Override
    public Coordinate getCenter() {
        return imageCenter;
    }
    
    @Override
    public void setZoomMax (int max) {
        zoomMax = max;
    }
    
    @Override
    public double getZoomFactor() {
        return zoomFactor;
    }
    
    @Override
    public void setZoom(int zoomValue) {
        zoomValue = (zoomValue &lt; zoomMin) ? zoomMin : zoomValue;
        zoomValue = (zoomValue &gt; zoomMax) ? zoomMax : zoomValue;
        if (zoomValue != c_zoomValue) {
            if (zoomValue &gt; 20) {
                zoomFactor = (int)((double)zoomValue * 500.);
            } else {
                // y=(x/2)^4 (which, at 20, matches x * 500 above
                // this gives a better response at the smaller zoom values
                zoomFactor = (int)Math.pow(((double)zoomValue / 2.), 4.);                
                zoomFactor = (zoomFactor &lt; zoomAdj) ? zoomAdj : zoomFactor;
            }
            zoomFactor = (zoomFactor &gt; zoomFactorMax) ? zoomFactorMax : zoomFactor;
            c_zoomValue = zoomValue;
            // WmsOsm (Open Street Maps transparent) only returns data
            // up to this zoom: fails afterward.  Catch to prevent errors.
            imageryWmsOsmOverZoomLimit = zoomValue &gt; 18 ? true : false;
            repaint();
        }
    }


    @Override
    public int getZoom() {
        return c_zoomValue;
    }

    public Dimension getViewSize() {
        return getSize();
    }

    public void setBufferFactor(double factor) {
        if (factor &lt; 0) {
            throw new IllegalArgumentException("Buffer factor must be equal or greater than 0.");
        }
        c_bufferFactor = factor;
        repaint();
    }

    @Override
    public void setViewer(MapViewer v) {
        c_viewer = v;
    }
    
    public Dimension calculateBufferImageSize() {
        Dimension view = getViewSize();

        int width = (int) (view.width * (1 + c_bufferFactor));
        int height = (int) (view.height * (1 + c_bufferFactor));

        return new Dimension(width, height);
    }

    private void paintImage(BufferedImage image, double zoom, Coordinate center) {
        synchronized (BUFFER_LOCK) {
            //todo: fire property change events if needed
            c_bufferImage = image;
            c_bufferZoomFactor = zoom;
            c_bufferCenter = center;
        }
        setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        repaint();
    }

    @Override
    protected void paintComponent(Graphics g) {
        if (c_bufferImage == null) {
            enqueueRender();
            return;
        }

        Graphics2D g2d = (Graphics2D) g;

        if (zoomFactor != c_bufferZoomFactor || (!c_panning &amp;&amp; !imageCenter.equals2D(c_bufferCenter))) {
            enqueueRender();
        }

        //make the temp zoom look pretty
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
        synchronized (BUFFER_LOCK) {
            //Scale the image while we wait for the new image to be rendered
            double zoom = zoomFactor / c_bufferZoomFactor;

            //draw the buffer image zoomed and panned
            int panX = (int) ((c_bufferCenter.x - imageCenter.x) * zoomFactor);
            int panY = (int) ((imageCenter.y - c_bufferCenter.y) * zoomFactor);

            int width = (int) (c_bufferImage.getWidth() * zoom);
            int height = (int) (c_bufferImage.getHeight() * zoom);

            g2d.drawImage(c_bufferImage, (getWidth() / 2) - (width / 2) + panX,
                    (getHeight() / 2) - (height / 2) + panY, width, height, this);
        }
    }

    @Override
    public void setRenderEnabled(boolean enabled) {
        renderEnabled = enabled;
//        enqueueRender();
    }
    
    private void enqueueRender() {
        if (renderEnabled) {
            if (c_renderTask != null &amp;&amp; !c_renderTask.isDone()) {
                c_renderTask.cancel(false);
            }
            c_renderTask = c_renderService.submit(this);
        }
    }

    @Override
    public void run() {
        try {
            if(c_viewer != null) {
                c_viewer.setLoadingGraphicVisible(true);
            }
            
            render();
            
            if(c_viewer != null) {
                c_viewer.setLoadingGraphicVisible(false);
            }
        } catch (Exception e) {
            Logger.getLogger(MapRenderLayer.class).error("Render error:", e);
        }
    }

    private void render() {
        
        // Render uses multiple renderers and multiple images:
        // multiple for imagery and one for the shapes.
        // The shapes image is the final one and the one that gets painted.
        // If the imagery image is visible, it gets added into the shapes image
        // in order to be painted.
        // See comment below for reason of separate imagery data.
        
        final double zoom = zoomFactor;
        final double x = imageCenter.x;
        final double y = imageCenter.y;
        
        Dimension imageSize;
        boolean renderImageryWms;
        boolean renderImageryWmsOsm;
        BufferedImage imageBufferMain;
        AffineTransform transform;
        Graphics2D graphicsMain;
        Graphics2D graphicsImagery;
        
        float opacity;
        Composite opacitySave;
        
        boolean imageryWmsVisible;
        boolean imageryWmsOsmVisible;

        imageSize = calculateBufferImageSize();
        imageBufferMain = new BufferedImage(imageSize.width, imageSize.height, BufferedImage.TYPE_INT_ARGB);

        // create the transform
        transform = new AffineTransform();
        // Translate to the center of the buffer image.
        transform.translate((imageBufferMain.getWidth() / 2), (imageBufferMain.getHeight() / 2));
        // Scale with negative y factor to correct the orientation.
        transform.scale(zoom, -zoom);
        // Translate to the center of the feature collection.
        transform.translate(-x, -y);
        
        imageryWmsVisible = false;
        if (contentImageryWms != null) {
             for (Layer l : contentImageryWms.layers()) {
                imageryWmsVisible = imageryWmsVisible || l.isVisible();
            }        
        }
        renderImageryWms = imageryWmsVisible;
        
        imageryWmsOsmVisible = false;
        if (contentImageryWmsOsm != null) {
            for (Layer l : contentImageryWmsOsm.layers()) {
                imageryWmsOsmVisible = imageryWmsOsmVisible || l.isVisible();
            }        
        }
        imageryWmsOsmVisible = imageryWmsOsmOverZoomLimit ? false : imageryWmsOsmVisible;  
        renderImageryWmsOsm = imageryWmsOsmVisible;  
        
        if (transform.equals(prevTransform)) {
            if (imageryWmsVisible &amp;&amp; imageBufferImageryWms != null) {
                // image same, so don't call paint on the imagery renderer
                renderImageryWms = false;
            }
            if (imageryWmsOsmVisible &amp;&amp; imageBufferImageryWmsOsm != null) {
                // image same, so don't call paint on the imagery renderer
                renderImageryWmsOsm = false;
            }
        } else {
            // image changed, so clear saved data
            if (imageBufferImageryWms != null) {
                imageBufferImageryWms.flush();
                imageBufferImageryWms = null;
            }
            if (imageBufferImageryWmsOsm != null) {
                imageBufferImageryWmsOsm.flush();
                imageBufferImageryWmsOsm = null;
            }
        }

        graphicsMain = imageBufferMain.createGraphics();
        graphicsImagery = null;
//        graphicsMap = null;

        if (contentImageryWms != null || contentImageryWmsOsm != null || 
            contentShapesBelowImagery != null || contentShapesAboveImagery != null) {
            synchronized (RENDER_LOCK) {
                c_labelCache.clear();
                Rectangle paintSize = new Rectangle(imageSize);
                try {
                    CoordinateReferenceSystem crsDefault = null;
                    CoordinateReferenceSystem crsWms = null;
                    Hints hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE);
                    CRSAuthorityFactory factory = ReferencingFactoryFinder.getCRSAuthorityFactory("EPSG", hints);
                    //crsDefault = factory.createCoordinateReferenceSystem("EPSG:3857");
                    crsDefault = factory.createCoordinateReferenceSystem("EPSG:4326");
                    crsWms = factory.createCoordinateReferenceSystem("EPSG:4326");

                    ReferencedEnvelope envelopeDefault = RendererUtilities.createMapEnvelope(
                            paintSize, transform, crsDefault);
                    ReferencedEnvelope envelopeImageryWms = RendererUtilities.createMapEnvelope(
                            paintSize, transform, crsWms);
                    
                    // Everything gets painted into the graphicsShapes object
                    if (rendererImageryWms != null) {
                        // The WMS imagery layer maintain its own image and render.
                        // the WMS imagery renderer cannot be called more than once with the
                        // same transform (throws a null pointer error)
                        // Once it is rendered, then keep that rendered image.
                        if (renderImageryWms) {
                            if (imageBufferImageryWms  != null) {
                                imageBufferImageryWms.flush();
                            }
                            imageBufferImageryWms = new BufferedImage(imageSize.width, imageSize.height, BufferedImage.TYPE_INT_ARGB);
                            graphicsImagery= imageBufferImageryWms.createGraphics();
                            rendererImageryWms.paint(graphicsImagery, paintSize, envelopeImageryWms, transform);
                            graphicsImagery.dispose();
                        }
                        if (imageryWmsVisible) {
                            // If visible, draw the imagery image into the shapes image
                            opacitySave = setOpacity (graphicsMain, rendererImageryWms);
                            graphicsMain.drawImage(imageBufferImageryWms, null, 0, 0);
                            resetOpacity (graphicsMain, opacitySave);
                        }
                    }
                    if (rendererShapesBelowImagery != null) {
                        // These settings are required for the TileLayers
                        MapViewport v = rendererShapesBelowImagery.getMapContent().getViewport();
                        v.setScreenArea(paintSize);
                        v.setCoordinateReferenceSystem(crsWms);
                        v.setBounds(envelopeDefault);
                        
                        rendererShapesBelowImagery.paint(graphicsMain, paintSize, envelopeDefault, transform);
                    }
                    if (rendererImageryWmsOsm != null) {
                        // The WMS imagery layer maintain its own image and render.
                        // the WMS imagery renderer cannot be called more than once with the
                        // same transform (throws a null pointer error)
                        // Once it is rendered, then keep that rendered image.
                        if (renderImageryWmsOsm) {
                            if (imageBufferImageryWmsOsm  != null) {
                                imageBufferImageryWmsOsm.flush();
                            }
                            imageBufferImageryWmsOsm = new BufferedImage(imageSize.width, imageSize.height, BufferedImage.TYPE_INT_ARGB);
                            graphicsImagery= imageBufferImageryWmsOsm.createGraphics();
                            rendererImageryWmsOsm.paint(graphicsImagery, paintSize, envelopeImageryWms, transform);
                            graphicsImagery.dispose();
                        }
                        if (imageryWmsOsmVisible) {
                            // If visible, draw the imagery image into the shapes image
                            opacitySave = setOpacity (graphicsMain, rendererImageryWmsOsm);
                            graphicsMain.drawImage(imageBufferImageryWmsOsm, null, 0, 0);
                            resetOpacity (graphicsMain, opacitySave);
                        }
                    }
                    if (rendererShapesAboveImagery != null) {
                        // These settings are required for the TileLayers
                        MapViewport v = rendererShapesAboveImagery.getMapContent().getViewport();
                        v.setScreenArea(paintSize);
                        v.setCoordinateReferenceSystem(crsWms);
                        v.setBounds(envelopeDefault);
                        
                        rendererShapesAboveImagery.paint(graphicsMain, paintSize, envelopeDefault, transform);
                    }
                } catch (FactoryException | NoninvertibleTransformException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }
        paintImage(imageBufferMain, zoom, new Coordinate(x, y));
        // save the cuurent transform to use
        // for comparison next time
        prevTransform = transform;
    }

    private float getContentFloatParam (GTRenderer rend, String key, float defVal) {
        Object o = rend.getMapContent().getUserData().get(key);
        if (o != null) {
            defVal = (Float)o;
        }
        return defVal;
    }
    
    private Composite setOpacity (Graphics2D graphics, GTRenderer rend) {
        Composite comp = graphics.getComposite();
        float opacity = getContentFloatParam (rend, WepsLayerOpacityKey, (float)1.);
        if (opacity &lt; 1.) {
            AlphaComposite newComp = AlphaComposite.getInstance(SRC_OVER, opacity);
            graphics.setComposite(newComp);
        }
        return comp;
    }
    
    private void resetOpacity (Graphics2D graphics, Composite comp) {
        graphics.setComposite(comp);
    }

    
    @Override
    public void zoomIn() {
//        setZoom(c_zoomValue+1);
        if(c_viewer != null) {
            c_viewer.setZoomBarExternal(c_zoomValue+zoomInc);           
        }
    }

    @Override
    public void zoomOut() {
//        setZoom(c_zoomValue-1);
        if(c_viewer != null) {
            c_viewer.setZoomBarExternal(c_zoomValue-zoomInc);           
        }
    }

    @Override
    public void pan(int x, int y) {
        Coordinate center = getCenter();
        double zoom = zoomFactor;
        double pX = x / zoom;
        double pY = y / zoom;
        Coordinate newCenter = new Coordinate(center.x - pX, center.y + pY);
        setCenter(newCenter);
    }

    @Override
    public boolean isPanning() {
        return c_panning;
    }

    @Override
    public void setPanning(boolean panning) {
        c_panning = panning;
    }

    /**
     * Handles setting the cursor high enough in the component tree so that every layer displays cursors.
     */
    @Override
    public void setCursor(Cursor cursor) {
        Container parent = SwingUtilities.getAncestorOfClass(MapPane.class, MapRenderLayer.this);
        if (parent == null) {
            parent = MapRenderLayer.this;
        }
        parent.setCursor(cursor);
    }

    public void highlight(Feature... features) {
//        if (c_map == null) {
//            throw new IllegalStateException("MapContent is null");
//        }
//        c_map.removeLayer(c_highlightLayer);
//        c_highlight = new MemoryDataStore();
    }

    @Override
    public void layerAdded(MapLayerListEvent event) {
        enqueueRender();
    }

    @Override
    public void layerRemoved(MapLayerListEvent event) {
        enqueueRender();
    }

    @Override
    public void layerChanged(MapLayerListEvent event) {
        enqueueRender();
    }

    @Override
    public void layerMoved(MapLayerListEvent event) {
        enqueueRender();
    }

    @Override
    public int getZoomMin() {
        return zoomMin;
    }

    @Override
    public int getZoomMax() {
        return zoomMax;
    }

    private class InternalPanAction extends MouseAdapter {

        private int c_ix, c_iy;

        @Override
        public void mousePressed(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                c_panning = true;
                c_ix = e.getX();
                c_iy = e.getY();
            }
        }

        @Override
        public void mouseReleased(MouseEvent e) {
            if (SwingUtilities.isLeftMouseButton(e)) {
                setCursor(Cursor.getDefaultCursor());
                c_panning = false;
                repaint();
            }
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            if (c_panning) {
                setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
                int x = e.getX();
                int y = e.getY();
                int dx = x - c_ix;
                int dy = y - c_iy;
                pan(dx, dy);
                c_ix = x;
                c_iy = y;
            }
        }
    }

    private class InternalZoomAction extends MouseAdapter {
        @Override
        public void mouseWheelMoved(MouseWheelEvent e) {
            //zoom bar
            // we do in fact have a map pane
            int units = e.getUnitsToScroll();
            if (units &lt; 0) {
                zoomIn();
            } else if (units &gt; 0) {
                zoomOut();
            }
            

        }
    }

    private class InternalComponentListener extends ComponentAdapter {

// MEH
        @Override
        public void componentResized(ComponentEvent e) {
            enqueueRender();
        }
    }

    @Override
    public void layerPreDispose(MapLayerListEvent arg0) {

    }
    
}
</pre></body></html>