Search code examples
javaworldwind

Worldwind SurfaceImage Deep/Batch Picking


I'm using WorldWind and trying to "pick" multiple surface images in the same layer and not understanding why it isn't working.

I was under the impression that calling this:

this.getWwd().getSceneController().setDeepPickEnabled(true);

Would enable me to pick multiple renderables in the same layer. This seems to work for all other cases other than SurfaceImage. I also noticed if I force the loaded SurfaceImage into different layers it works as expected.

This is the code I'm using to test this out:

public class SurfaceImageViewer extends ApplicationTemplate
{
    public static class AppFrame extends ApplicationTemplate.AppFrame
    {
        private JFileChooser fileChooser = new JFileChooser();
        private JSlider opacitySlider;
        private SurfaceImageLayer layer;
        private JLabel statusLabel = new JLabel("status: ready");

        public AppFrame()
        {
            super(true, true, false);

            this.getWwd().getSceneController().setDeepPickEnabled(true);

            try
            {
                this.layer = new SurfaceImageLayer();
                this.layer.setOpacity(1);
                this.layer.setPickEnabled(true);
                this.layer.setName("Surface Images");

                insertBeforeCompass(this.getWwd(), layer);

                this.getControlPanel().add(makeControlPanel(), BorderLayout.SOUTH);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }

            this.getWwd().addSelectListener(new SelectListener() {

                @Override
                public void selected(SelectEvent event) {
                    PickedObjectList pol = AppFrame.this.getWwd().getObjectsAtCurrentPosition();

                    if(event.isLeftClick()){
                        System.out.println("POL SIZE "+pol.size());
                    }

                }
            });


        }

        Action openElevationsAction = new AbstractAction("Open Elevation File...")
        {
            public void actionPerformed(ActionEvent e)
            {
                int status = fileChooser.showOpenDialog(AppFrame.this);
                if (status != JFileChooser.APPROVE_OPTION)
                    return;

                final File imageFile = fileChooser.getSelectedFile();
                if (imageFile == null)
                    return;

                Thread t = new Thread(new Runnable()
                {
                    public void run()
                    {
                        try
                        {
                            CompoundElevationModel cem
                                = (CompoundElevationModel) getWwd().getModel().getGlobe().getElevationModel();
                            LocalElevationModel em = new LocalElevationModel();
                            em.addElevations(imageFile.getPath());
                            cem.addElevationModel(em);
                        }
                        catch (IOException e1)
                        {
                            e1.printStackTrace();
                        }
                    }
                });
                t.setPriority(Thread.MIN_PRIORITY);
                t.start();
            }
        };

        Action openImageAction = new AbstractAction("Open Image File...")
        {
            public void actionPerformed(ActionEvent actionEvent)
            {
                int status = fileChooser.showOpenDialog(AppFrame.this);
                if (status != JFileChooser.APPROVE_OPTION)
                    return;

                final File imageFile = fileChooser.getSelectedFile();
                if (imageFile == null)
                    return;

                Thread t = new Thread(new Runnable()
                {
                    public void run()
                    {
                        try
                        {
                            statusLabel.setText("status: Loading image");
                            // TODO: proper threading
                            layer.addImage(imageFile.getAbsolutePath());

                            getWwd().redraw();
                            statusLabel.setText("status: ready");
                        }
                        catch (IOException e)
                        {
                            e.printStackTrace();
                        }
                    }
                });
                t.setPriority(Thread.MIN_PRIORITY);
                t.start();
            }
        };

        private JPanel makeControlPanel()
        {
            JPanel controlPanel = new JPanel(new GridLayout(0, 1, 5, 5));
            JButton openImageButton = new JButton(openImageAction);
            controlPanel.add(openImageButton);

            this.opacitySlider = new JSlider();
            this.opacitySlider.setMaximum(100);
            this.opacitySlider.setValue((int) (layer.getOpacity() * 100));
            this.opacitySlider.setEnabled(true);
            this.opacitySlider.addChangeListener(new ChangeListener()
            {
                public void stateChanged(ChangeEvent e)
                {
                    int value = opacitySlider.getValue();
                    layer.setOpacity(value / 100d);
                    getWwd().redraw();
                }
            });
            JPanel opacityPanel = new JPanel(new BorderLayout(5, 5));
            opacityPanel.setBorder(new EmptyBorder(0, 10, 0, 0));
            opacityPanel.add(new JLabel("Opacity"), BorderLayout.WEST);
            opacityPanel.add(this.opacitySlider, BorderLayout.CENTER);

            controlPanel.add(opacityPanel);

            JButton openElevationsButton = new JButton(openElevationsAction);
            controlPanel.add(openElevationsButton);

            controlPanel.add(statusLabel);
            controlPanel.setBorder(new EmptyBorder(15, 15, 15, 15));

            return controlPanel;
        }
    }

    public static void main(String[] args)
    {
        ApplicationTemplate.start("World Wind Surface Images", SurfaceImageViewer.AppFrame.class);
    }
}

These are 2 geotiffs that are layered on top of each other that I've been using to test this out. I would expect my println on the SelectListener to print out "3" when I single left click on both geotiffs. (I've uploaded the geotiffs into a zip available here)

The area where you will see these is in San Francisco, see screenshot:

enter image description here


Solution

  • Update:

    It was discovered that the examples for Batch Picking were oriented around AbstractSurfaceObject instances, which did not apply in this case. For the handling of SurfaceImage instances the property for setAlwaysOnTop should be configured to false which appears to let the selection event process all elements under the cursor.


    Reading through the examples for DeepPicking, there are actually 2 things that need to be done.

    1. setDeepPickEnabled(true); //This is done.
    2. Disable Batch picking on the desired elements

    https://github.com/nasa/World-Wind-Java/blob/master/WorldWind/src/gov/nasa/worldwindx/examples/DeepPicking.java

    In order to enable deep picking, any batch picking for the desired elements must be disabled and the SceneController's deep picking property must be enabled. See {@link gov.nasa.worldwind.SceneController#setDeepPickEnabled(boolean)

    Took me a little while to understand the second one, but it appears to be tied to the AbstractSurfaceObject class.


    • I am assuming that the things that you're drawing on the layer are a subclass of AbstractSurfaceObject

    I believe that in this situation, I would subclass the SurfaceImageLayer, and override the addRenderable methods. I would check the renderable if it was an instance of an AbstractSurfaceObject, and disable batch picking on it before forwarding it to the super class.

    This code may not be the best long-term solution, but it may provide quick results to determine if this is the underlying issue.

    import gov.nasa.worldwind.layers.SurfaceImageLayer;
    import gov.nasa.worldwind.render.AbstractSurfaceObject;
    import gov.nasa.worldwind.render.Renderable;
    
    /**
     * Very Rough extension of SurfaceImageLayer which disables batch picking on all AbstractSurfaceobjects.
     * @author http://stackoverflow.com/users/5407189/jeremiah
     * @since Nov 26, 2016
     *
     */
    public class MySurfaceImageLayer extends SurfaceImageLayer {
    
        
        @Override
        public void addRenderable(Renderable renderable) {
          if (renderable instanceof AbstractSurfaceObject) {
              ((AbstractSurfaceObject)renderable).setEnableBatchPicking(false);
          }
          super.addRenderable(renderable);
        }
        
        @Override
        public void addRenderables(Iterable<? extends Renderable> renderables) {
           for (Renderable r : renderables) {
               addRenderable(r);
           }
        }
    }
    

    IF the thing you want to have picked is the image directly, that appears to not be supported out-of-the-box. You would need to do something to get the SurfaceImage references from the SurfaceImageLayer to be visible to the RenderableLayer on doPick. That may come with a new set of problems to watch out for.

    • As a side-note, if you're rendering Icons then all you need to do is set the IconRenderer.setAllowBatchPicking(false)

    I hope that's at least somewhat helpful.
    Best of Luck.