Fixed Background and pan-/zoom-able Foreground

83 views
Skip to first unread message

Ainawing

unread,
Aug 8, 2014, 6:28:36 AM8/8/14
to piccolo...@googlegroups.com
Hi there,

I'm trying to build a geo-referenced system using jmapviewer (swing) and piccolo. For this to happen, I have to get a setup, where I can interact (programmatically) with a foreground while maintaining a fixed background.
After some three weeks of experimentation and trying to work out how piccolo handles it's various classes and interactions, I have come quite far.
I'm currently using a background layer, where my jmapviewer is displayed and I added an internal camera on top of that to display my drawing layer.

In fact all that is left would be to get the scaling on the drawing layer right, but for some reason it's bugging out on me. I suspect now that my problem may be in how I setup my layers and cameras, so I hope someone here can have a look at my source and help me out.

!!! Please notice, that you may have to set a custom proxy in the source to run it correctly... The code is there, just enter the address and port!!!

Looking up the jmapviewer source I realized that I'm probably not using the current version of it. I hope no problems arise, but I'll try it out in the next few days...
JMapViewer source can be downloaded here: http://svn.openstreetmap.org/applications/viewer/jmapviewer/releases/

main class:

public class PiccolloSwingTest extends JFrame {
   
public PiccolloSwingTest() {

        getContentPane
().add(canvas);

   
}

   
private static final long serialVersionUID = 1L;

   
private static String proxyHost = "";
   
private static String proxyPort = "";
   
private static java.util.Properties systemProperties = System
           
.getProperties();

   
private static int WIDTH;
   
private static int HEIGHT;
   
private static int X;
   
private static int Y;
   
private static int CENTER_X;
   
private static int CENTER_Y;

   
private static PCamera camera = new PCamera();
   
   
private static JMapViewer gisMap = new JMapViewer();

   
private static PSwingCanvas canvas = new PSwingCanvas() {
       
public void setBounds(int x, int y, int width, int height) {
           
super.setBounds(x, y, width, height);

            osm
.setBounds(x, y, width, height);
            canvas
.getLayer().setBounds(x, y, width, height);
            gisMap
.setBounds(x, y, width, height);
            gisMap
.setPreferredSize(new Dimension(width, height));
            camera
.setBounds(0, 0, width, height);
            adjustPiccolo
(camera);
       
};
   
};
   
private static PSwing osm = new PSwing(gisMap);

   
private static PLayer display = new PLayer();

   
static int defaultZoom = gisMap.getZoom();

   
/**
     * @param args
     */

   
public static void main(final String[] args) {
        systemProperties
.setProperty("http.proxyHost", proxyHost);
        systemProperties
.setProperty("http.proxyPort", proxyPort);

       
// cleanup listeners
       
MouseWheelListener[] mwls = gisMap.getMouseWheelListeners();
       
for (int i = 0; i < mwls.length; i++) {
           
MouseWheelListener mWL = mwls[i];
            gisMap
.removeMouseWheelListener(mWL);
       
}
       
MouseListener[] mls = gisMap.getMouseListeners();
       
for (int i = 0; i < mls.length; i++) {
           
MouseListener mouseListener = mls[i];
            gisMap
.removeMouseListener(mouseListener);
       
}
       
MouseMotionListener[] mmls = gisMap.getMouseMotionListeners();
       
for (int i = 0; i < mmls.length; i++) {
           
MouseMotionListener mouseMotionListener = mmls[i];
            gisMap
.removeMouseMotionListener(mouseMotionListener);
       
}

       
for (PInputEventListener l : canvas.getInputEventListeners()) {
            canvas
.removeInputEventListener(l);
       
}

        camera
.addInputEventListener(new InteractionHandler2(gisMap, defaultZoom));

        X
= 0;
        Y
= 0;
        WIDTH
= OsmMercator.LonToX(180, defaultZoom);
        HEIGHT
= OsmMercator.LatToY(-90, defaultZoom);
        CENTER_X
= OsmMercator.LonToX(0, defaultZoom);
        CENTER_Y
= OsmMercator.LatToY(0, defaultZoom);

       
PPath world = PPath.createRectangle(X, Y, WIDTH, HEIGHT);
        world
.setStrokePaint(Color.black);
        display
.addChild(world);
        world
.setPaint(null);

       
PPath NW = PPath.createRectangle(X + 10, Y + 10, (WIDTH / 2) - 20,
               
(HEIGHT / 2) - 20);
        NW
.setStrokePaint(Color.red);
        NW
.setVisible(true);
        display
.addChild(NW);
        NW
.setPaint(null);

       
PPath NE = PPath.createRectangle(CENTER_X + 10, Y + 10, (WIDTH / 2) - 20,
               
(HEIGHT / 2) - 20);
        NE
.setStrokePaint(Color.green);
        display
.addChild(NE);
        NE
.setPaint(null);

       
PPath SW = PPath.createRectangle(X + 10, CENTER_Y + 10, (WIDTH / 2) - 20,
               
(HEIGHT / 2) - 20);
        SW
.setStrokePaint(Color.blue);
        SW
.setPaint(null);
        display
.addChild(SW);
        SW
.setPaint(null);

       
PPath SE = PPath.createRectangle(CENTER_X + 10, CENTER_Y + 10,
               
(WIDTH / 2) - 20, (HEIGHT / 2) - 20);
        SE
.setStrokePaint(Color.yellow);
        display
.addChild(SE);
        SE
.setPaint(null);

       
PPath NS = PPath.createLine(CENTER_X, Y, CENTER_X, Y + HEIGHT);
        NS
.setStrokePaint(Color.black);
        display
.addChild(NS);

       
PPath EW = PPath.createLine(X, CENTER_Y, X + WIDTH, CENTER_Y);
        EW
.setStrokePaint(Color.black);
        display
.addChild(EW);

        display
.addChild(PPath.createEllipse(CENTER_X - 5, CENTER_Y - 5, 10, 10));
        display
.addChild(PPath.createEllipse(X - 5, Y - 5, 10, 10));
        display
.addChild(PPath.createEllipse(WIDTH - 5, Y - 5, 10, 10));
        display
.addChild(PPath.createEllipse(X - 5, HEIGHT - 5, 10, 10));
        display
.addChild(PPath.createEllipse(WIDTH - 5, HEIGHT - 5, 10, 10));

        camera
.addLayer(display);

       
// get neutral lat/lon coords
       
Coordinate c1 = gisMap.getPosition(new Point(0, 0));

       
// translate into piccolo coordinates
       
Point p = new Point();
        p
.x = OsmMercator.LonToX(c1.getLon(), defaultZoom);
        p
.y = OsmMercator.LatToY(c1.getLat(), defaultZoom);

       
// adjust piccolo
//        camera.setBounds(0, 0, canvas.getCamera().getWidth(), canvas.getCamera()
//                .getHeight());
        camera
.setViewOffset(p.x, p.y);
       
        canvas
.getLayer().addChild(osm);
        canvas
.getLayer().addChild(camera);

        osm
.moveToBack();

       
// canvas.getCamera().repaint();
       
// frame setup

       
final PiccolloSwingTest frame = new PiccolloSwingTest();
        frame
.setBounds(50, 50, 200, 200);
        frame
.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame
.setVisible(true);

   
}

   
@Override
   
public void setBounds(int x, int y, int width, int height) {
       
super.setBounds(x, y, width, height);
        canvas
.setBounds(x, y, width, height);
   
}

   
private static void adjustPiccolo(PCamera camera) {

       
Point p1 = gisMap.getMapPosition(90, -180, false);
       
Point p2 = gisMap.getMapPosition(-90, 180, false);

       
// map is always square, so scale can be calculated without squaring for
       
// distance
       
double scaleMap = p2.x - p1.x + 1;
       
double scaleDef = OsmMercator.LonToX(180, defaultZoom) + 1;

       
// scale in map/default
       
double scale = Math.round(1000 * scaleMap / scaleDef) / 1000d;
       
double xMod = (double) p1.x / scale;
       
double yMod = (double) p1.y / scale;
        camera
.setViewOffset(xMod, yMod);
   
}
   
}


Interaction Handler:
public class InteractionHandler2 extends PBasicInputEventHandler {

   
private JMapViewer map;
   
private int defaultZoom;

   
public InteractionHandler2(JMapViewer gisMap, int defaultZoom) {
       
this.map = gisMap;
       
this.defaultZoom = defaultZoom;

   
}

   
@Override
   
public void mouseDragged(PInputEvent event) {

       
double dx = event.getDelta().width;
       
double dy = event.getDelta().height;

        map
.moveMap(-(int) dx, -(int) dy);

        adjustPiccolo
(event.getCamera());
   
}

   
@Override
   
public void mouseWheelRotated(PInputEvent event) {
       
InputEvent src = event.getSourceSwingEvent();
        src
.consume();
       
// Event has to be marked as handled.
       
event.setHandled(true);
       
Point2D p2d = event.getCanvasPosition();
       
Point p = new Point((int) p2d.getX(), (int) p2d.getY());
       
for (int i = 0; i > event.getWheelRotation(); i--)
            map
.zoomIn(p);

       
for (int i = 0; i < event.getWheelRotation(); i++)
            map
.zoomOut(p);

        adjustPiccolo
(event.getCamera());
   
}

   
private void adjustPiccolo(PCamera camera) {

       
Point p1 = map.getMapPosition(90, -180, false);
       
Point p2 = map.getMapPosition(-90, 180, false);

       
// map is always square, so scale can be calculated without squaring for
       
// distance
       
double scaleMap = p2.x - p1.x + 1;
       
double scaleDef = OsmMercator.LonToX(180, defaultZoom) + 1;

       
// scale in map/default
       
double scale = Math.round(1000 * scaleMap / scaleDef) / 1000d;
       
double xMod = (double) p1.x * scale;
       
double yMod = (double) p1.y * scale;
        camera
.setViewOffset(xMod, yMod);

       
//TODO adjust scaling
       
   
}
}



Ainawing

unread,
Aug 18, 2014, 8:00:43 AM8/18/14
to piccolo...@googlegroups.com
Well, that wasn't a lot of feedback.

I'm now using "camera.getViewScale(scale);" to adjust the viewscale. I had some trouble using "event.getDelta()", but once I realized that was using the bottom camera and I switched to "event.getCanvasDelta()" everything worked just fine.

I hope this will be of use to somebody.

Cheers,

ainawing.

Michael Heuer

unread,
Aug 18, 2014, 11:04:14 AM8/18/14
to piccolo...@googlegroups.com
Hello Ainawing,

Thanks for the note; glad you got it working.

Sorry for not responding earlier, it sounded like you were going to
try a newer version of jmapviewer first.

michael
> --
> You received this message because you are subscribed to the Google Groups
> "Piccolo2D Users" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to piccolo2d-use...@googlegroups.com.
> To post to this group, send email to piccolo...@googlegroups.com.
> Visit this group at http://groups.google.com/group/piccolo2d-users.
> For more options, visit https://groups.google.com/d/optout.

Ainawing

unread,
Aug 20, 2014, 11:05:20 AM8/20/14
to piccolo...@googlegroups.com
Sorry. Reading through my first post again, I realized you were right. I didn't expect the problem to lie with JMapViewer (and it doesn't). Just forgot to mention that. (I didn't actually try out the new version yet... Too busy with other stuff to migrate.)

Anyways, I have run into another issue.
As I'm sure you guessed, I'm trying to draw stuff on top of my map. In this particular case it's basically a graph-like structure consisting of nodes (ellipses, resp. circles) and connections (Polygons). Setting the Viewscale (yes, that should have been "setViewScale()" in my last message) means, that I run into a scaling issue. On higher zoom levels ellipses are distorted and their borders approximated by polygons resulting in some really weird displays. I have yet to program a short example, but I will do so (probably tomorrow, but no promises). I wanted to keep my display data consistent in piccolo, but now I'm not sure I'll be able to do that.

Is this a problem you have encountered? If so, how did you solve it?

Greetings,

ainawing.

Michael Heuer

unread,
Aug 20, 2014, 11:21:12 AM8/20/14
to piccolo...@googlegroups.com
Hello Ainawing,

I can't say I have run into that problem before, it may actually be
some limitation of Java2D if shapes aren't being drawn correctly.

One thing you may try is to set all the rendering quality hints to high quality

PCanvas canvas = ...;
canvas.setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
canvas.setAnimatingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
canvas.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);

Only the default rendering quality hint is set to high quality by default

https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/PCanvas.java#149

michael
Message has been deleted

Ainawing

unread,
Aug 21, 2014, 5:57:45 AM8/21/14
to piccolo...@googlegroups.com
Hello, michael.

That somewhat changed the behaviour, but sadly not for the better.

I've inserted an example of what I'm talking about at the end (This will generate low scale ellipses in the top left corner, they're numbered for testing purposes).
You can try commenting the render quality lines, to see what changes, but basically, having them in is only making the behaviour more consistent...

So, my next steps will be trying to upscale the entire coordinate transformation, allowing to operate in higher scale regions while keeping the coordinate system consistent inside Piccolo. If that shouldn't work, I'll try to implement scaling in a different manner. I don't want to go so far as to break the coordinate consistency, though. If anyone has other ideas, I'd apreciate the help.

Greetings,

ainawing.

import java.awt.BasicStroke;
import java.awt.Color;

import javax.swing.JFrame;

import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.nodes.PPath;
import edu.umd.cs.piccolo.nodes.PText;
import edu.umd.cs.piccolo.util.PPaintContext;

public class PiccoloZoomTest extends JFrame {

   
private static final long serialVersionUID = 1L;


   
static PCanvas canvas = new PCanvas();

   
private PiccoloZoomTest() {

        getContentPane
().add(canvas);

   
}

   
public static void main(String[] args) {
        canvas
.setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
        canvas
.setAnimatingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
        canvas
.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
       
for (int i = 0; i < 100; i++) {
           
double factor = Math.pow(2, i);
           
double size = 1 / factor;
           
// generate ellipse and Text
           
PPath circle = PPath.createEllipse((float) (0 - size),
                   
(float) (0 - size), (float) size * 2, (float) size * 2);
            circle
.setPaint(null);
            circle
.setStrokePaint(Color.black);
            circle
.setStroke(new BasicStroke((float) (size/10)));
            canvas
.getLayer().addChild(circle);
           
           
PText text = new PText(Integer.toString(i));
            text
.centerBoundsOnPoint(0, 110);
            text
.setScale(size/100);
            canvas
.getLayer().addChild(text);
       
}
       
       
final PiccoloZoomTest frame = new PiccoloZoomTest();

Ainawing

unread,
Aug 21, 2014, 8:53:53 AM8/21/14
to piccolo...@googlegroups.com
Okay,

I tried shifting the coordinate system into a higher scale region, but that didn't work for some reason either. I'm currently suspecting, that I'm messing up somewhere along the line or that Java2D simply can't handle that kind of scaling with the degree of accuracy required. Since I have to make some adjustments to my zooming methods anyway, I'm going to make some changes that will allow piccolo to work on a linear scale. I hope that this will alleviate the issue that I encounter, but who knows...

Anyways... more about that tomorrow, I suppose.

Greetings, ainawing.

Ainawing

unread,
Aug 22, 2014, 8:18:01 AM8/22/14
to piccolo...@googlegroups.com
Well, I've run some additional tests on the scaling and here are the results. In one sentence: It doesn't work.

I was expecting the transform that is used in Piccolo to compensate for the problems inherent in using floating point coordinate systems (or any form of digital coordinate system, really), basically allowing for unlimited precision if used properly (the example posted earlier should work properly, if everything went allright), but apparently the transforms applied to the different nodes are resolved before rendering (or somewhere in between?).
The problem is, that I'm running into numbers so large, that they don't provide the precision necessary for drawing, no matter the scale. Just to get things straight, The scale I'm talking about goes to 2^19 in my case, but may go as far as 2^22 in general. Considering the area I have to cover, this appears to be beyond the scope of what Piccolo is able to handle...

So, back to my previous solution and keeping track of the coordinate transformation myself...

Greetings, ainawing.

Michael Heuer

unread,
Aug 22, 2014, 3:15:02 PM8/22/14
to piccolo...@googlegroups.com
Hello Ainawing,

As far as I know the transforms used in Piccolo2D are the same used in Java2D.

Are you using double-precision PPath everywhere?

PPath.Double ellipse = new PPath.Double(new Ellipse2D.Double(x, y,
width, height));

It appears not, since you're still using Piccolo2D version 1.x in the
code examples above. Even with Piccolo2D version 3.x all of the
helper methods default to using float precision

https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PPath.java#267

so you'll have to use the PPath.Double(...) constructors instead

https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PPath.java#157
https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PPath.java#167
https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PPath.java#178
https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PPath.java#188
https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PPath.java#199

This may help with the extreme transforms you're trying to do, but
keep in mind at some point you'll hit the precision limits of Java2D
itself.

I'll convert the last example you posted below to Piccolo2D 3.x with
double-precision and see if it helps.

michael

Michael Heuer

unread,
Aug 22, 2014, 3:26:51 PM8/22/14
to piccolo...@googlegroups.com
Here's the updated example

import java.awt.BasicStroke;
import java.awt.Color;

import java.awt.geom.Ellipse2D;

import javax.swing.JFrame;

import org.piccolo2d.PCanvas;
import org.piccolo2d.nodes.PPath;
import org.piccolo2d.nodes.PText;
import org.piccolo2d.util.PPaintContext;

public class Piccolo3xDoublePrecisionZoomTest extends JFrame {

private static final long serialVersionUID = 1L;

static PCanvas canvas = new PCanvas();


private Piccolo3xDoublePrecisionZoomTest() {

getContentPane().add(canvas);

}

public static void main(String[] args) {
canvas.setDefaultRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
canvas.setAnimatingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
canvas.setInteractingRenderQuality(PPaintContext.HIGH_QUALITY_RENDERING);
for (int i = 0; i < 100; i++) {
double factor = Math.pow(2, i);
double size = 1 / factor;
// generate ellipse and Text
PPath.Double circle = new PPath.Double(new
Ellipse2D.Double(0.0d - size, 0.0d - size, size * 2.0d, size * 2.0d));
circle.setPaint(null);
circle.setStrokePaint(Color.black);
circle.setStroke(new BasicStroke((float) (size/10)));
canvas.getLayer().addChild(circle);

PText text = new PText(Integer.toString(i));
text.centerBoundsOnPoint(0, 110);
text.setScale(size/100);
canvas.getLayer().addChild(text);
}

final Piccolo3xDoublePrecisionZoomTest frame = new
Piccolo3xDoublePrecisionZoomTest();

frame.setBounds(50, 50, 200, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}


I still see the same problem however, that at level 6 and beyond the
ellipse is drawn as a diamond.

michael

Michael Heuer

unread,
Aug 22, 2014, 3:40:00 PM8/22/14
to piccolo...@googlegroups.com
FYI

In Piccolo2D each node has a transform

https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/PNode.java#298

and that transform is applied to all of that node's children.

When that node is viewed through a camera onto a canvas, there is also
a view transform

https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/PCamera.java#46

All transforms are concatenated and set in the Graphics2D context
before painting a node, which in the case of PPath is handled here

https://code.google.com/p/piccolo2d/source/browse/piccolo2d.java/trunk/core/src/main/java/org/piccolo2d/nodes/PShape.java#214

There's no magic really, just Java2D stuff.

michael

Ainawing

unread,
Aug 25, 2014, 7:42:52 AM8/25/14
to piccolo...@googlegroups.com
Guess I'll try to update Piccolo later (it will be a while). Until then, I'll do a rollback to my old version. In that one, I let JMapViewer handle all the coordinate transformation and used Piccolo only for drawing things. Not the way I wanted it to go, but it works for now.

Anyway, I'll have to invest some time into other features for now and maybe look into the code at some point... I'll come back, once I've had the time to look into it once more.

Until then,

ainawing.
Reply all
Reply to author
Forward
0 new messages