Circle/Rectangle Shapes Drawing using ArcGIS Java SDK - javafx

so i've been working on a map related desktop application (in JavaFX) using ArcGIS Java SDK 100.6.0. I want to draw certain shapes on the map and save them. I've seen the documentation and as far as i know they provide a SketchEditor class for drawing on map. The class object allows me to draw freehand, polylines and polygons on the map. My application requires a complete drawing feature to draw various shapes. This class does not allow me to draw squares, rectanlges, circles. My question is that how can i draw these shapes on my mapview. Has anyone ever come up with any idea to draw shapes apart from the SketchEditor's available ones.
Edit: I think my question is not clear enough. I'll just share what my application requires and what i can actually implement:
These are are the shapes that i want my application to support
These are the shapes that ArcGIS Java SDK currently supports
Now I've found a workaround for circle from this link:
https://gis.stackexchange.com/questions/26636/draw-a-circle-in-arcgis-map
Now the shapes that remain are rectangle/square. I hope someone can share how they solved this problem in Java.

It sounds like you want to create graphic overlays. Squares, rectangles, circles = polygons. However, you could create the option to add square or circle marker symbols as points and then allow the user to scale the symbols to make them larger or smaller.
https://developers.arcgis.com/labs/net/display-point-line-and-polygon-graphics/
https://developers.arcgis.com/java/10-2/guide/add-graphics-and-text-to-graphic-overlays.htm

I tried to find workarounds for the drawing of those unsupported shapes. It was found that by using the polygon geometry, and by giving the right points, i can create those shapes (Circle, Square, Rectangle). Here is the code snippet that shows how i managed to draw those shapes (The methods that return graphic object for the shapes):
Circle
public static Graphic drawFullCircle(Point centerPoint, double radius, int borderColor) {
int ptCount = 240;
double slice = 2 * Math.PI / ptCount;
PointCollection pc = new PointCollection(SpatialReferences.getWgs84());
for (int i = 0; i <= ptCount; i++) {
double rad = slice * i;
double px = centerPoint.getX() + radius * Math.cos(rad);
double py = centerPoint.getY() + radius * Math.sin(rad);
pc.add(new Point(px, py));
}
Polygon poly = new Polygon(new PartCollection(pc));
SimpleFillSymbol sfs = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, 0x00FFFFFF, new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, borderColor, 1.5f));
Graphic g = new Graphic(poly, sfs);
return g;
}
I was able to draw rectangular shapes using the Envelop geometry.
Rectangle
public static Graphic drawRectangle(Point p1, Point p2, int borderColor) {
Envelope envelope = new Envelope(p1, p2); // start (p1) and end (p2) points of a diagonal
PointCollection pc = new PointCollection(SpatialReferences.getWgs84());
pc.add(new Point(envelope.getXMin(), envelope.getYMin()));
pc.add(new Point(envelope.getXMax(), envelope.getYMin()));
pc.add(new Point(envelope.getXMax(), envelope.getYMax()));
pc.add(new Point(envelope.getXMin(), envelope.getYMax()));
Polygon poly = new Polygon(new PartCollection(pc));
SimpleFillSymbol sfs = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, 0x00FFFFFF, new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, borderColor, 1.5f));
return new Graphic(poly, sfs);
}
Square
public static Graphic drawSquare(Point p1, Point p2, int borderColor) {
Envelope rectEnvelope = new Envelope(p1, p2); start (p1) and end (p2) points of a diagonal
PointCollection pc = new PointCollection(SpatialReferences.getWgs84());
// these conditions make sure that the square is created from the start point and not from any other point.
if(rectEnvelope.getWidth() > rectEnvelope.getHeight())
{
if(p2.getX() > p1.getX())
{
pc.add(new Point(rectEnvelope.getXMin(), rectEnvelope.getYMin()));
pc.add(new Point(rectEnvelope.getXMin()+rectEnvelope.getHeight(), rectEnvelope.getYMin()));
pc.add(new Point(rectEnvelope.getXMin()+rectEnvelope.getHeight(), rectEnvelope.getYMax()));
pc.add(new Point(rectEnvelope.getXMin(), rectEnvelope.getYMax()));
}
else
{
pc.add(new Point(rectEnvelope.getXMax()-rectEnvelope.getHeight(), rectEnvelope.getYMax()-rectEnvelope.getHeight()));
pc.add(new Point(rectEnvelope.getXMax(), rectEnvelope.getYMin()));
pc.add(new Point(rectEnvelope.getXMax(), rectEnvelope.getYMax()));
pc.add(new Point(rectEnvelope.getXMax()-rectEnvelope.getHeight(), rectEnvelope.getYMax()));
}
}
else
{
if(p2.getY() > p1.getY())
{
pc.add(new Point(rectEnvelope.getXMin(), rectEnvelope.getYMin()));
pc.add(new Point(rectEnvelope.getXMax(), rectEnvelope.getYMin()));
pc.add(new Point(rectEnvelope.getXMax(), rectEnvelope.getYMin()+rectEnvelope.getWidth()));
pc.add(new Point(rectEnvelope.getXMin(), rectEnvelope.getYMin()+rectEnvelope.getWidth()));
}
else
{
pc.add(new Point(rectEnvelope.getXMin(), rectEnvelope.getYMax()-rectEnvelope.getWidth()));
pc.add(new Point(rectEnvelope.getXMax(), rectEnvelope.getYMax()-rectEnvelope.getWidth()));
pc.add(new Point(rectEnvelope.getXMax(), rectEnvelope.getYMax()));
pc.add(new Point(rectEnvelope.getXMin(), rectEnvelope.getYMax()));
}
}
Polygon poly = new Polygon(new PartCollection(pc));
SimpleFillSymbol sfs = new SimpleFillSymbol(SimpleFillSymbol.Style.SOLID, 0x00FFFFFF, new SimpleLineSymbol(SimpleLineSymbol.Style.SOLID, borderColor, 1.5f));
return new Graphic(poly, sfs);
}
This might contain unoptimized code logic however this is all what we have for now with ArcGIS Java SDK 100.6.0. Any edits and suggestions would be appreciated.

Related

Preventing overlapping shapes while dragging on a Pane

I've looked at similar questions but they all are concerned with collision detection rather than preventing overlap. I've gotten most of it to work with the below code:
private final EventHandler<MouseEvent> onPress = mouseEvent -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : getAllShapes()) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
However, the problem is, once there is a tiniest bit of overlap, the Shape is no longer draggable at all. Meaning, if I drag a shape to another, once they become essentially tangent, neither of them are draggable anymore. What I want to happen is just that, for example, if you try to drag a circle onto another, the circle won't follow the mouse position as long as the future position of the drag will cause an overlap.
I can't figure out exactly how to accomplish this.
EDIT: Minimum Reproducible Example:
Main.java
public class Main extends Application {
static Circle circle1 = new DraggableCircle(100, 200);
static Circle circle2 = new DraggableCircle(200, 300);
static Circle[] circleList = new Circle[]{circle1, circle2};
#Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Hello World");
Pane pane = new Pane();
primaryStage.setScene(new Scene(pane, 300, 275));
primaryStage.show();
pane.getChildren().addAll(circle1, circle2);
}
public static void main(String[] args) {
launch(args);
}
}
DraggableCircle.java
public class DraggableCircle extends Circle {
private double xDrag, yDrag;
public DraggableCircle(double x, double y) {
super(x, y, 30);
this.setFill(Color.WHITE);
this.setStroke(Color.BLACK);
this.setStrokeWidth(1.5);
this.addEventHandler(MouseEvent.MOUSE_PRESSED, onPress);
this.addEventHandler(MouseEvent.MOUSE_DRAGGED, onDrag);
}
private final EventHandler<MouseEvent> onPress = (mouseEvent) -> {
xDrag = this.getCenterX() - mouseEvent.getX();
yDrag = this.getCenterY() - mouseEvent.getY();
};
private final EventHandler<MouseEvent> onDrag = mouseEvent -> {
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
return;
}
}
}
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
};
}
This also has an issue where dragging too quickly causes a noticeable overlap between the circles, before the drag detection ends.
A simple (imperfect) solution
The following algorithm will allow a node to continue to be dragged after an intersection has occurred:
Record the current draggable shape position.
Set the new position.
Check the intersection.
If an intersection is detected, reset the position to the original position.
An implementation replaces the drag handler in the supplied minimal example code from the question.
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Shape shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
this.setCenterX(priorCenterX);
this.setCenterY(priorCenterY);
return;
}
}
}
};
This handler does work better than what you had, it does at least allow you to continue dragging after the intersection.
But, yes, if you drag quickly it will leave a visible space between nodes when it has detected that the drag operation would cause an intersection, which isn't ideal.
Also, the additional requirement you added in your comment about having the dragged shape glide along a border would require a more sophisticated solution.
Other potential solutions
I don't offer code for these more sophisticated solutions here.
One potential brute force solution is to interpolate the prior center with the new center and then, in a loop, slowly move the dragged object along the interpolated line until an intersection is detected, then just back it out to the last interpolated value to prevent the intersection. You can do this by calculating and applying a normalized (1 unit distance) movement vector. That might fix space between intersected nodes.
Similarly to get the gliding, on the intersection, you could just update either the interpolated x or y value rather than both.
There may be more sophisticated methods with geometry math applied, especially if you know shape geometry along with movement vectors and surface normals.
+1 for #jewelsea answer.
On top of #jewelsea answer, I would like to provide a fix for the "space between nodes" issue.
So you might have already observed that when you drag fast, it will not cover each and every pixel in the drag path. It varies with the speed of the drag. So when you decide to move it to the previous recorded point, we will do a quick math, to see if there is any gap between the two nodes, if yes:
We will do a math "to determine a point along a line which is at distance d" and move the drag circle to that point. Here..
start point of line is : previous recorded point
end point of line is : the intersected shape center
d is : the gap between the two shapes.
So the updated code to the #jewelsea answer is as below:
private final EventHandler<MouseEvent> onDrag = (mouseEvent) -> {
double priorCenterX = getCenterX();
double priorCenterY = getCenterY();
this.setCenterX(mouseEvent.getX() + xDrag);
this.setCenterY(mouseEvent.getY() + yDrag);
for (Circle shape : Main.circleList) {
if (!this.equals(shape)) {
Shape intersect = Shape.intersect(shape, this);
if (intersect.getLayoutBounds().getWidth() > 0) {
Point2D cx = new Point2D(priorCenterX, priorCenterY);
Point2D px = new Point2D(shape.getCenterX(), shape.getCenterY());
double d = cx.distance(px);
if (d > getRadius() + shape.getRadius()) {
cx = pointAtDistance(cx, px, d - getRadius() - shape.getRadius());
}
this.setCenterX(cx.getX());
this.setCenterY(cx.getY());
return;
}
}
}
};
private Point2D pointAtDistance(Point2D p1, Point2D p2, double distance) {
double lineLength = p1.distance(p2);
double t = distance / lineLength;
double dx = ((1 - t) * p1.getX()) + (t * p2.getX());
double dy = ((1 - t) * p1.getY()) + (t * p2.getY());
return new Point2D(dx, dy);
}

How to get 2D coordinates on window for 3D object in javafx

In javafx if we have 2D HUD (made of Pane and then out of it we create SubScene object for 2D Hud) and 3D SubScene and inside 3D scene we have some object with coordinates (x,y,z) - how can we get 2D coordinates in our HUD of the object if it is in visual field of our perspective camera?
I tried to get first Scene coordinates of the object and then convert it (sceneToScreen) coordinates and the same for point (0,0) of Pane and then to subtract first point from second point but i don't get right result. Sorry because of my bad English.Can Someone help with this?
There is a way to convert the 3D coordinates of an object in a subScene to a 2D scene coordinates, but unfortunately it uses private API, so it is advised not to rely on it.
The idea is based on how the camera projection works, and it is based on the com.sun.javafx.scene.input.InputEventUtils.recomputeCoordinates() method that is used typically for input events from a PickResult.
Let's say you have a node in a sub scene. For a given point of that node, you can obtain its coordinates like:
Point3D coordinates = node.localToScene(Point3D.ZERO);
and you can find out about the sub scene of the node:
SubScene subScene = NodeHelper.getSubScene(node);
Now you can use the SceneUtils::subSceneToScene method that
Translates point from inner subScene coordinates to scene coordinates.
to get a new set of coordinates, referenced to the scene:
coordinates = SceneUtils.subSceneToScene(subScene, coordinates);
But these are still 3D coordinates.
The final step to convert those to 2D is with the use of CameraHelper::project:
final Camera effectiveCamera = SceneHelper.getEffectiveCamera(node.getScene());
Point2D p2 = CameraHelper.project(effectiveCamera, coordinates);
The following sample places 2D labels in the scene, at the exact same position of the 8 vertices of a 3D box in a subScene.
private final Rotate rotateX = new Rotate(0, Rotate.X_AXIS);
private final Rotate rotateY = new Rotate(0, Rotate.Y_AXIS);
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private Group root;
#Override
public void start(Stage primaryStage) {
Box box = new Box(150, 100, 50);
box.setDrawMode(DrawMode.LINE);
box.setCullFace(CullFace.NONE);
Group group = new Group(box);
PerspectiveCamera camera = new PerspectiveCamera(true);
camera.setNearClip(0.1);
camera.setFarClip(10000.0);
camera.setFieldOfView(20);
camera.getTransforms().addAll (rotateX, rotateY, new Translate(0, 0, -500));
SubScene subScene = new SubScene(group, 500, 400, true, SceneAntialiasing.BALANCED);
subScene.setCamera(camera);
root = new Group(subScene);
Scene scene = new Scene(root, 500, 400);
primaryStage.setTitle("HUD: 2D Labels over 3D SubScene");
primaryStage.setScene(scene);
primaryStage.show();
updateLabels(box);
scene.setOnMousePressed(event -> {
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
});
scene.setOnMouseDragged(event -> {
mousePosX = event.getSceneX();
mousePosY = event.getSceneY();
rotateX.setAngle(rotateX.getAngle() - (mousePosY - mouseOldY));
rotateY.setAngle(rotateY.getAngle() + (mousePosX - mouseOldX));
mouseOldX = mousePosX;
mouseOldY = mousePosY;
updateLabels(box);
});
}
private List<Point3D> generateDots(Node box) {
List<Point3D> vertices = new ArrayList<>();
Bounds bounds = box.getBoundsInLocal();
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMinY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMinY(), bounds.getMaxZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMaxY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMinX(), bounds.getMaxY(), bounds.getMaxZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMinY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMinY(), bounds.getMaxZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMaxY(), bounds.getMinZ())));
vertices.add(box.localToScene(new Point3D(bounds.getMaxX(), bounds.getMaxY(), bounds.getMaxZ())));
return vertices;
}
private void updateLabels(Node box) {
root.getChildren().removeIf(Label.class::isInstance);
SubScene oldSubScene = NodeHelper.getSubScene(box);
AtomicInteger counter = new AtomicInteger(1);
generateDots(box).stream()
.forEach(dot -> {
Point3D coordinates = SceneUtils.subSceneToScene(oldSubScene, dot);
Point2D p2 = CameraHelper.project(SceneHelper.getEffectiveCamera(box.getScene()), coordinates);
Label label = new Label("" + counter.getAndIncrement() + String.format(" (%.1f,%.1f)", p2.getX(), p2.getY()));
label.setStyle("-fx-font-size:1.3em; -fx-text-fill: blue;");
label.getTransforms().setAll(new Translate(p2.getX(), p2.getY()));
root.getChildren().add(label);
});
}
The FXyz3D library has another similar sample.
EDIT
A late edit of this answer, but it is worthwhile mentioning that there is no need for private API. There is public API in the Node::localToScene methods that allows traversing the subScene.
So this just works (note the true argument):
Point3D p2 = box.localToScene(dot, true);
According to the JavaDoc for Node::localToScene:
Transforms a point from the local coordinate space of this Node into the coordinate space of its scene. If the Node does not have any SubScene or rootScene is set to true, the result point is in Scene coordinates of the Node returned by getScene(). Otherwise, the subscene coordinates are used, which is equivalent to calling localToScene(Point3D).
Without true the conversion is within the subScene, but with it, the conversion goes from the current subScene to the scene. In this case, this methods calls SceneUtils::subSceneToScene, so we don't need to do it anymore.
With this, updateLabels gets simplified to:
private void updateLabels(Node box) {
root.getChildren().removeIf(Label.class::isInstance);
AtomicInteger counter = new AtomicInteger(1);
generateDots(box).stream()
.forEach(dot -> {
Point3D p2 = box.localToScene(dot, true);
Label label = new Label("" + counter.getAndIncrement() + String.format(" (%.1f,%.1f)", p2.getX(), p2.getY()));
label.setStyle("-fx-font-size:1.3em; -fx-text-fill: blue;");
label.getTransforms().setAll(new Translate(p2.getX(), p2.getY()));
root.getChildren().add(label);
});
}

How compare the orientation from one node to another

I am trying to get a node's point to connect to another node's point depending on the orientation of the first node. The problem is, I am having trouble thinking of the way a node call tell whether it is left, right , north or south given its X and Y positions and Width and Height.
Here is an image of the problem:
I have tried comparing the X and Y values, for example, if (child.Y > parent.Y) -> snap to southPoint, but it is too vague and will snap to north or south if it is placed right or left. Any idea on more specific conditions I can use to get the correct orientation? Thank you
You could do it by defining anchor points on your box:
public class UMLClassBox {
private Point2D north;
private Point2D east;
private Point2D south;
private Point2D west;
public List<Point> getAnchorPoints() {
...
}
}
As a second step you then can have a method to find the two nodes that are closest:
public Point2D[] findClosestNodes(UMLClassBox box1, UMLClassBox box2) {
Point2D[] closest = new Point2D[2];
double shortestDistance = Double.MAX_VALUE;
for (Point2D anchorBox1 : box1.getAnchorPoints()) {
for (Point2D anchorBox2 : box2.getAnchorPoints()) {
double distance = anchorBox1.distance(anchorBox2);
if (distance < shortestDistance) {
shortestDistance = distance;
closest[0] = anchorBox1;
closest[1] = anchorBox2;
}
}
}
return closest;
}
While there is a chance that two anchor points pairs have the same distance, this should not matter in this case because either of those would be equally valid.

create a polyline in gluon mapLayer

Google maps API makes it possible to create a layer on the map containing a polyline linking points together.
I have searched where I could to find an example or an implementation for this for gluon's mapLayer.
Please advice
While there's no explicit API for drawing lines, polylines or polygons on top of a MapView, the MapLayer is a layer where you can draw any JavaFX Shape, providing you take care of scaling it to the map coordinates.
For that, if you have a look at the PoiLayer class, you can see that for any MapPoint (defined by latitude and longitude) you can get a 2D point (defined by x and y), and you can draw a node at that position:
MapPoint point = new MapPoint(37.396256,-121.953847);
Node icon = new Circle(5, Color.BLUE);
Point2D mapPoint = baseMap.getMapPoint(point.getLatitude(), point.getLongitude());
icon.setTranslateX(mapPoint.getX());
icon.setTranslateY(mapPoint.getY());
So if you want to create, for instance, a Polygon based on a set of points, you have to add a Polygon object to the layer:
public class PoiLayer extends MapLayer {
private final Polygon polygon;
public PoiLayer() {
polygon = new Polygon();
polygon.setStroke(Color.RED);
polygon.setFill(Color.rgb(255, 0, 0, 0.5));
this.getChildren().add(polygon);
}
#Override
protected void layoutLayer() {
polygon.getPoints().clear();
for (Pair<MapPoint, Node> candidate : points) {
MapPoint point = candidate.getKey();
Node icon = candidate.getValue();
Point2D mapPoint = baseMap.getMapPoint(point.getLatitude(), point.getLongitude());
icon.setTranslateX(mapPoint.getX());
icon.setTranslateY(mapPoint.getY());
polygon.getPoints().addAll(mapPoint.getX(), mapPoint.getY());
}
}
}
Now, on the demo class, create a set of mapPoints, and add them to the map:
private final List<MapPoint> polPoints = Arrays.asList(
new MapPoint(37.887242, -122.178799), new MapPoint(37.738729, -121.921567),
new MapPoint(37.441704, -121.921567), new MapPoint(37.293191, -122.178799),
new MapPoint(37.441704, -122.436031), new MapPoint(37.738729, -122.436031));
private MapLayer myDemoLayer () {
PoiLayer poi = new PoiLayer();
for (MapPoint mapPoint : polPoints) {
poi.addPoint(mapPoint, new Circle(5, Color.BLUE));
}
return poi;
}
And you will have a map with your geo-located polygon on top of it.

How can I animate a circle with PlayN?

This is a follow up to my last question:
How can I draw a circle to the screen with PlayN?
For my simple case, I want to programmatically create a single colored circle and move it across a 2-D plain (doesn't need to use box2d lib).
A real-world example would likely involve animating several circles. Two real-world examples for this case (sorry, I had to remove the links -- not enough karma!):
Browsmos for Chrome
Ants AI Challenge
It was suggested in response to my last question that I would want to use the ImmediateLayer class, so I am looking to understand how to properly incorporate this into my game loop.
Here's is my code sample:
public class SimpleCircleAnimation implements Game {
// Surface
private GroupLayer rootLayer;
private ImmediateLayer surface;
private Canvas canvas;
private Circle circle;
private CanvasImage circleImage;
#Override
public void init() {
// create root layer
rootLayer = graphics().rootLayer();
// a simple circle object
int circleX = 0; int circleY = 0;
int circleRadius = 20;
circle = new Circle(circleX, circleY, circleRadius);
// create an immediate layer and add to root layer
ImmediateLayer circleLayer = graphics().createImmediateLayer(new ImmediateLayer.Renderer() {
public void render (Surface surf) {
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
canvas = circleImage.canvas();
canvas.setFillColor(0xff0000eb);
canvas.fillCircle(circle.radius, circle.radius, circle.radius);
surf.drawImage(circleImage, circle.x, circle.y);
}
});
rootLayer.add(circleLayer);
}
#Override
public void paint(float alpha) {
}
#Override
public void update(float delta) {
// move circle
int newX = circle.x + 4; int newY = circle.y + 4;
circle.setPoint(newX, newY);
}
#Override
public int updateRate() {
return 25;
}
}
This successfully moves the circle diagonally down the screen from left to right. A couple questions:
Is this implemented properly?
In the case of multiple animated circles, is the idea with ImmediateLayer that you would create a circle image for each circle within the Renderer callback? Or would you perhaps create an Immediate Layer for each circle and add those to the root layer?
I would not use ImmediateLayer wiht render (Surface surf) adapter. Here u have, inside the render method creation of an image
circleImage = graphics().createImage(circle.radius*2, circle.radius*2);
just put this in the paint method
surf.drawImage(circleImage, circle.x, circle.y);
using the normal layer and u should be fine
Painting is done in paint method, and do not put calculations there
Update is for calculations, and physics oriented stuff
I discovered a detailed practical example of ImmediateLayer usage in the Cute Game source within the PlayN Samples:
CuteGame.java (code.google.com)

Resources